diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 4ff3960d3..80ff26d23 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -17,6 +17,7 @@ on: pull_request: branches: - master + - 'release/**' env: SLN_PATH: ./src/graphql-aspnet.sln diff --git a/src/ancillary-projects/starwars/starwars-common/GraphControllers/SearchController.cs b/src/ancillary-projects/starwars/starwars-common/GraphControllers/SearchController.cs index 6d5ad6cbe..149e82b63 100644 --- a/src/ancillary-projects/starwars/starwars-common/GraphControllers/SearchController.cs +++ b/src/ancillary-projects/starwars/starwars-common/GraphControllers/SearchController.cs @@ -11,15 +11,12 @@ namespace GraphQL.AspNet.StarwarsAPI.Common.GraphControllers { using System.Collections.Generic; using System.ComponentModel; - using System.Linq; using System.Threading.Tasks; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.StarwarsAPI.Common.Model; using GraphQL.AspNet.StarwarsAPI.Common.Services; - using Microsoft.AspNetCore.Authorization; /// /// A controller to handle search queries across the spectrum of the star wars universe. @@ -42,7 +39,7 @@ public SearchController(IStarWarsDataService starWarsData) /// /// The text to search for. /// Task<IGraphActionResult>. - [QueryRoot("search","SearchResults", typeof(Droid), typeof(Human), typeof(Starship), TypeExpression = "[Type]")] + [QueryRoot("search", "SearchResults", typeof(Droid), typeof(Human), typeof(Starship), TypeExpression = "[Type]")] [Description("Searches for the specified text as the name of a starship or character (not case sensitive).")] public async Task GlobalSearch(string searchText = "*") { diff --git a/src/ancillary-projects/starwars/starwars-common/GraphControllers/StarshipsController.cs b/src/ancillary-projects/starwars/starwars-common/GraphControllers/StarshipsController.cs index 18d4b6600..e0a68d05f 100644 --- a/src/ancillary-projects/starwars/starwars-common/GraphControllers/StarshipsController.cs +++ b/src/ancillary-projects/starwars/starwars-common/GraphControllers/StarshipsController.cs @@ -21,6 +21,8 @@ namespace GraphQL.AspNet.StarwarsAPI.Common.GraphControllers using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.StarwarsAPI.Common.Model; using GraphQL.AspNet.StarwarsAPI.Common.Services; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; /// /// A graph controller responsible for managing starship related data. @@ -67,7 +69,6 @@ public async Task RetrieveStarship([FromGraphQL("id")] Graph return this.Ok(starship); } - /// /// Retrieves a droid in the system by their id. Note the use of a different name for the parameter /// between its exposure in the object graph vs. the formal parameter name used in the C# code. diff --git a/src/ancillary-projects/starwars/starwars-common/starwars-common.csproj b/src/ancillary-projects/starwars/starwars-common/starwars-common.csproj index cdf1f0fc5..68d76560f 100644 --- a/src/ancillary-projects/starwars/starwars-common/starwars-common.csproj +++ b/src/ancillary-projects/starwars/starwars-common/starwars-common.csproj @@ -1,7 +1,7 @@ - + - net8.0;net7.0;netstandard2.0; + net8.0;net7.0; latest $(NoWarn);1701;1702;1705;1591;NU1603 GraphQL.AspNet.StarwarsAPI.Common diff --git a/src/graphql-aspnet-subscriptions/Attributes/SubscriptionAttribute.cs b/src/graphql-aspnet-subscriptions/Attributes/SubscriptionAttribute.cs index 08608a266..0c0c2f2cf 100644 --- a/src/graphql-aspnet-subscriptions/Attributes/SubscriptionAttribute.cs +++ b/src/graphql-aspnet-subscriptions/Attributes/SubscriptionAttribute.cs @@ -82,7 +82,7 @@ public SubscriptionAttribute(string template, Type returnType) /// be sure to supply any additional concrete types so that they may be included in the object graph. /// Any additional types to include in the object graph on behalf of this method. public SubscriptionAttribute(string template, Type returnType, params Type[] additionalTypes) - : base(false, SchemaItemCollections.Subscription, template, returnType.AsEnumerable().Concat(additionalTypes).ToArray()) + : base(false, SchemaItemCollections.Subscription, template, (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray()) { this.EventName = null; } @@ -93,15 +93,14 @@ public SubscriptionAttribute(string template, Type returnType, params Type[] add /// The template naming scheme to use to generate a graph field from this method. /// Name of the union type. /// The first of two required types to include in the union. - /// The second of two required types to include in the union. /// Any additional union types to include. - public SubscriptionAttribute(string template, string unionTypeName, Type unionTypeA, Type unionTypeB, params Type[] additionalUnionTypes) + public SubscriptionAttribute(string template, string unionTypeName, Type unionTypeA, params Type[] additionalUnionTypes) : base( false, SchemaItemCollections.Subscription, template, unionTypeName, - unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).ToArray()) + new Type[] { unionTypeA }.Concat(additionalUnionTypes ?? Enumerable.Empty()).ToArray()) { this.EventName = null; } diff --git a/src/graphql-aspnet-subscriptions/Attributes/SubscriptionRootAttribute.cs b/src/graphql-aspnet-subscriptions/Attributes/SubscriptionRootAttribute.cs index 57833d0b6..6184f7ed2 100644 --- a/src/graphql-aspnet-subscriptions/Attributes/SubscriptionRootAttribute.cs +++ b/src/graphql-aspnet-subscriptions/Attributes/SubscriptionRootAttribute.cs @@ -82,7 +82,7 @@ public SubscriptionRootAttribute(string template, Type returnType) /// be sure to supply any additional concrete types so that they may be included in the object graph. /// Any additional types to include in the object graph on behalf of this method. public SubscriptionRootAttribute(string template, Type returnType, params Type[] additionalTypes) - : base(true, SchemaItemCollections.Subscription, template, returnType.AsEnumerable().Concat(additionalTypes).ToArray()) + : base(true, SchemaItemCollections.Subscription, template, (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray()) { this.EventName = null; } @@ -93,15 +93,14 @@ public SubscriptionRootAttribute(string template, Type returnType, params Type[] /// The template naming scheme to use to generate a graph field from this method. /// Name of the union type. /// The first of two required types to include in the union. - /// The second of two required types to include in the union. /// Any additional union types. - public SubscriptionRootAttribute(string template, string unionTypeName, Type unionTypeA, Type unionTypeB, params Type[] additionalUnionTypes) + public SubscriptionRootAttribute(string template, string unionTypeName, Type unionTypeA, params Type[] additionalUnionTypes) : base( true, SchemaItemCollections.Subscription, template, unionTypeName, - unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).ToArray()) + (new Type[] { unionTypeA }).Concat(additionalUnionTypes ?? Enumerable.Empty()).ToArray()) { this.EventName = null; } diff --git a/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions.cs b/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions.cs new file mode 100644 index 000000000..41ebe64d6 --- /dev/null +++ b/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions.cs @@ -0,0 +1,185 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration +{ + using System; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions; + + /// + /// Extension methods for defining subscriptions via minimal api. + /// + public static partial class GraphQLRuntimeSubscriptionDefinitionExtensions + { + /// + /// Creates a new, explicitly resolvable field in the Subscription root object with the given path. This field cannot be + /// further extended or nested with other fields via the Mapping API. + /// + /// The builder representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// IGraphQLFieldTemplate. + public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition MapSubscription(this ISchemaBuilder schemaBuilder, string template) + { + Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder)); + + return MapSubscription( + schemaBuilder.Options, + template, + null, // unionName + null as Delegate); + } + + /// + /// Creates a new field in the Subscription object with the given path. This field can act as a + /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself. + /// + /// The builder representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// The resolver method to execute when this + /// field is requested. + /// IGraphQLFieldTemplate. + public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition MapSubscription(this ISchemaBuilder schemaBuilder, string template, Delegate resolverMethod) + { + Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder)); + return MapSubscription( + schemaBuilder.Options, + template, + null, // unionName + resolverMethod); + } + + /// + /// Creates a new field in the Subscription object with the given path. This field can act as a + /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself. + /// + /// The builder representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// Provide a name and this field will be declared to return a union. Use to declare union members. + /// The resolver method to execute when this + /// field is requested. + /// IGraphQLFieldTemplate. + public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition MapSubscription( + this ISchemaBuilder schemaBuilder, + string template, + string unionName, + Delegate resolverMethod) + { + Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder)); + + return MapSubscription( + schemaBuilder.Options, + template, + unionName, + resolverMethod); + } + + /// + /// Creates a new field in the Subscription root object with the given path. This field can act as a + /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself. + /// + /// The options representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// IGraphQLFieldTemplate. + public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition MapSubscription( + this SchemaOptions schemaOptions, + string template) + { + return MapSubscription( + schemaOptions, + template, + null, // unionMethod + null as Delegate); + } + + /// + /// Creates a new, explicitly resolvable field in the Subscription root object with the given path. This field cannot be + /// further extended or nested with other fields via the Mapping API. + /// + /// The options representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// The resolver method to execute when + /// this field is requested by a caller. + /// IGraphQLResolvedFieldTemplate. + public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition MapSubscription( + this SchemaOptions schemaOptions, + string template, + Delegate resolverMethod) + { + return MapSubscription( + schemaOptions, + template, + null, // unionMethod + resolverMethod); + } + + /// + /// Creates a new, explicitly resolvable field in the Subscription root object with the given path. This field cannot be + /// further extended or nested with other fields via the Mapping API. + /// + /// The options representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// Provide a name and this field will be declared to return a union. Use to declare union members. + /// The resolver method to execute when + /// this field is requested by a caller. + /// IGraphQLResolvedFieldTemplate. + public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition MapSubscription( + this SchemaOptions schemaOptions, + string template, + string unionName, + Delegate resolverMethod) + { + schemaOptions = Validation.ThrowIfNullOrReturn(schemaOptions, nameof(schemaOptions)); + template = Validation.ThrowIfNullWhiteSpaceOrReturn(template, nameof(template)); + + var fieldTemplate = new RuntimeSubscriptionEnabledFieldGroupTemplate( + schemaOptions, + template); + + var resolvedField = RuntimeSubscriptionEnabledResolvedFieldDefinition.FromFieldTemplate(fieldTemplate); + schemaOptions.AddRuntimeSchemaItem(resolvedField); + + if (!string.IsNullOrWhiteSpace(unionName)) + resolvedField.AddAttribute(new UnionAttribute(unionName.Trim())); + + resolvedField.AddResolver(unionName, resolverMethod); + + schemaOptions.ServiceCollection?.AddSubscriptionRuntimeFieldExecutionSupport(); + return resolvedField; + } + + /// + /// Defines a custom event name for this subscription field. This event name is the key value + /// that must be published from a mutation to invoke this subscription for any subscribers. + /// + /// + /// This event name must be unique for this schema. + /// + /// The field to update. + /// Name of the event. + /// IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition. + public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition WithEventName( + this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition field, + string eventName) + { + field.EventName = eventName; + return field; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions_ResolvedFields.cs b/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions_ResolvedFields.cs new file mode 100644 index 000000000..207159c67 --- /dev/null +++ b/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions_ResolvedFields.cs @@ -0,0 +1,170 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration +{ + using System; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using Microsoft.AspNetCore.Authorization; + + /// + /// Extension methods for defining subscriptions via minimal api. + /// + public partial class GraphQLRuntimeSubscriptionDefinitionExtensions + { + /// + /// Adds policy-based authorization requirements to the field. + /// + /// + /// This is similar to adding the to a controller method + /// + /// The field being built. + /// The name of the policy to assign via this requirement. + /// A comma-seperated list of roles to assign via this requirement. + /// IGraphQLFieldBuilder. + public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition RequireAuthorization( + this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldBuilder, + string policyName = null, + string roles = null) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + GraphQLRuntimeSchemaItemDefinitionExtensions.RequireAuthorization(fieldBuilder, policyName, roles); + return fieldBuilder; + } + + /// + /// Indicates that the field should allow anonymous access. This will override any potential authorization requirements setup via + /// the "MapGroup" methods if this field was created within a group. + /// + /// + /// This is similar to adding the to a controller method + /// + /// The field being built. + /// IGraphQLFieldBuilder. + public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition AllowAnonymous(this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldBuilder) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + GraphQLRuntimeSchemaItemDefinitionExtensions.AllowAnonymous(fieldBuilder); + return fieldBuilder; + } + + /// + /// Adds a set of possible return types for this field. This is synonymous to using the + /// on a controller's action method. + /// + /// + /// This method can be called multiple times. Any new types will be appended to the field. All types added + /// must be coercable to the declared return type of the assigned resolver for this field unless this field returns a union; in + /// which case the types will be added as union members. + /// + /// The field being built. + /// The first possible type that might be returned by this + /// field. + /// Any number of additional possible types that + /// might be returned by this field. + /// IGraphQLFieldBuilder. + public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition AddPossibleTypes(this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldBuilder, Type firstPossibleType, params Type[] additionalPossibleTypes) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + GraphQLRuntimeSchemaItemDefinitionExtensions.AddPossibleTypes(fieldBuilder, firstPossibleType, additionalPossibleTypes); + return fieldBuilder; + } + + /// + /// Clears all extra defined possible types this field may declare. This will not affect the core type defined by the resolver, if + /// a resolver has been defined for this field. + /// + /// The field builder. + /// IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition. + public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition ClearPossibleTypes(this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldBuilder) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + GraphQLRuntimeSchemaItemDefinitionExtensions.ClearPossibleTypes(fieldBuilder); + return fieldBuilder; + } + + /// + /// Assigns a custom internal name to this field. This value will be used in error + /// messages and log entries instead of an anonymous method name. This can significantly increase readability + /// while trying to debug an issue. This value has no bearing on the runtime use of this field. It is cosmetic only. + /// + /// + /// This value does NOT affect the field name as it would appear in a schema. It only effects the internal + /// name used in log messages and exception text. + /// + /// The field being built. + /// The value to use as the internal name for this field definition when its + /// added to the schema. + /// IGraphQLFieldBuilder. + public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition WithInternalName(this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldBuilder, string internalName) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + GraphQLRuntimeSchemaItemDefinitionExtensions.WithInternalName(fieldBuilder, internalName); + return fieldBuilder; + } + + /// + /// Indicates this field will return a union and sets the resolver to be used when this field is requested at runtime. The provided + /// resolver should return a . + /// + /// The field being built. + /// Provide a name and this field will be declared to return a union. Use to declare union members. + /// The delegate to assign as the resolver. This method will be + /// parsed to determine input arguments for the field on the target schema. + /// IGraphQLFieldBuilder. + public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition AddResolver( + this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldBuilder, + string unionName, + Delegate resolverMethod) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + GraphQLRuntimeSchemaItemDefinitionExtensions.AddResolver(fieldBuilder, unionName, resolverMethod); + return fieldBuilder; + } + + /// + /// Sets the resolver to be used when this field is requested at runtime. + /// + /// + /// If this method is called more than once the previously set resolver will be replaced. + /// + /// The field being built. + /// The delegate to assign as the resolver. This method will be + /// parsed to determine input arguments for the field on the target schema. + /// IGraphQLFieldBuilder. + public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition AddResolver(this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldBuilder, Delegate resolverMethod) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + GraphQLRuntimeSchemaItemDefinitionExtensions.AddResolver(fieldBuilder, resolverMethod); + return fieldBuilder; + } + + /// + /// Sets the resolver to be used when this field is requested at runtime. + /// + /// + /// If this method is called more than once the previously set resolver will be replaced. + /// + /// The expected, primary return type of the field. Must be provided + /// if the supplied delegate returns an . + /// The field being built. + /// The delegate to assign as the resolver. This method will be + /// parsed to determine input arguments for the field on the target schema. + /// IGraphQLFieldBuilder. + public static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition AddResolver(this IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldBuilder, Delegate resolverMethod) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + GraphQLRuntimeSchemaItemDefinitionExtensions.AddResolver(fieldBuilder, resolverMethod); + return fieldBuilder; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions_VirtualFields.cs b/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions_VirtualFields.cs new file mode 100644 index 000000000..f864ea604 --- /dev/null +++ b/src/graphql-aspnet-subscriptions/Configuration/GraphQLRuntimeSubscriptionsDefinitionExtensions_VirtualFields.cs @@ -0,0 +1,46 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration +{ + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions; + + /// + /// Extension methods for defining subscriptions via minimal api. + /// + public static partial class GraphQLRuntimeSubscriptionDefinitionExtensions + { + /// + /// Begins a new field group for the subscription schema object. All fields created using + /// this group will be nested underneath it and inherit any set parameters such as authorization requirements. + /// + /// The builder to append the Subscription group to. + /// The template path for this group. + /// IGraphQLRuntimeFieldDefinition. + public static IGraphQLRuntimeFieldGroupDefinition MapSubscriptionGroup(this ISchemaBuilder schemaBuilder, string template) + { + return MapSubscriptionGroup(schemaBuilder?.Options, template); + } + + /// + /// Begins a new field group for the subscription schema object. All fields created using + /// this group will be nested underneath it and inherit any set parameters such as authorization requirements. + /// + /// The schema options to append the Subscription group to. + /// The template path for this group. + /// IGraphQLRuntimeFieldDefinition. + public static IGraphQLRuntimeFieldGroupDefinition MapSubscriptionGroup(this SchemaOptions schemaOptions, string template) + { + schemaOptions?.ServiceCollection?.AddSubscriptionRuntimeFieldExecutionSupport(); + return new RuntimeSubscriptionEnabledFieldGroupTemplate(schemaOptions, template); + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Configuration/ServiceCollectionExtensions.cs b/src/graphql-aspnet-subscriptions/Configuration/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..f9c31dc87 --- /dev/null +++ b/src/graphql-aspnet-subscriptions/Configuration/ServiceCollectionExtensions.cs @@ -0,0 +1,38 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration +{ + using GraphQL.AspNet.Controllers; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.DependencyInjection.Extensions; + + /// + /// Helpful extension methods for the + /// when dealing with subscriptions. + /// + public static class ServiceCollectionExtensions + { + /// + /// Adds support for the execution of runtime subscription field declarations (e.g. minimal api + /// defined fields). + /// + /// The service collection. + /// IServiceCollection. + public static IServiceCollection AddSubscriptionRuntimeFieldExecutionSupport(this IServiceCollection serviceCollection) + { + if (serviceCollection != null) + { + serviceCollection.TryAddTransient(); + } + + return serviceCollection; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Configuration/SubscriptionBuilderExtensions.cs b/src/graphql-aspnet-subscriptions/Configuration/SubscriptionBuilderExtensions.cs index 825cd0541..69ba97882 100644 --- a/src/graphql-aspnet-subscriptions/Configuration/SubscriptionBuilderExtensions.cs +++ b/src/graphql-aspnet-subscriptions/Configuration/SubscriptionBuilderExtensions.cs @@ -46,7 +46,7 @@ public static ISchemaBuilder AddSubscriptions( // then publishing adds one additional middleware component return schemaBuilder .AddSubscriptionServer(options) - .AddSubscriptionPublishing(options); + .AddSubscriptionPublishing(); } /// @@ -54,11 +54,9 @@ public static ISchemaBuilder AddSubscriptions( /// /// The type of schema being configured. /// The schema builder. - /// An action function to configure the subscription options. - /// GraphQL.AspNet.Interfaces.Configuration.ISchemaBuilder<TSchema>. + /// ISchemaBuilder<TSchema>. public static ISchemaBuilder AddSubscriptionPublishing( - this ISchemaBuilder schemaBuilder, - Action> options = null) + this ISchemaBuilder schemaBuilder) where TSchema : class, ISchema { Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder)); diff --git a/src/graphql-aspnet-subscriptions/Controllers/ActionResults/CompleteSubscriptionGraphActionResult.cs b/src/graphql-aspnet-subscriptions/Controllers/ActionResults/CompleteSubscriptionGraphActionResult.cs index 6b5e5b85f..77af70ae7 100644 --- a/src/graphql-aspnet-subscriptions/Controllers/ActionResults/CompleteSubscriptionGraphActionResult.cs +++ b/src/graphql-aspnet-subscriptions/Controllers/ActionResults/CompleteSubscriptionGraphActionResult.cs @@ -23,7 +23,7 @@ namespace GraphQL.AspNet.Controllers.ActionResults /// additional events will be raised. /// [DebuggerDisplay("Has Object: {_result?.GetType().FriendlyName()}")] - public class CompleteSubscriptionGraphActionResult : ObjectReturnedGraphActionResult + public class CompleteSubscriptionGraphActionResult : OperationCompleteGraphActionResult { /// /// Applies the appropriate session information to the field context to instruct diff --git a/src/graphql-aspnet-subscriptions/Controllers/ActionResults/SubscriptionEvents.cs b/src/graphql-aspnet-subscriptions/Controllers/ActionResults/SubscriptionEvents.cs new file mode 100644 index 000000000..b4937248a --- /dev/null +++ b/src/graphql-aspnet-subscriptions/Controllers/ActionResults/SubscriptionEvents.cs @@ -0,0 +1,72 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Controllers.ActionResults +{ + using System.Collections.Generic; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Execution.Contexts; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.SubscriptionServer; + + /// + /// A helper class to allow the use of common methods + /// with non-controller based resolvers for subscription related results. + /// + public static class SubscriptionEvents + { + /// + /// Publishes an instance of the internal event, informing all graphql-subscriptions that + /// are subscribed to the event. If the is + /// null the event is automatically canceled. + /// + /// The resolution context of the field where the event is being published. + /// Name of the well-known event to be raised. + /// The data object to pass with the event. + public static void PublishSubscriptionEvent(this SchemaItemResolutionContext context, string eventName, object dataObject) + { + Validation.ThrowIfNull(context, nameof(context)); + Validation.ThrowIfNull(dataObject, nameof(dataObject)); + eventName = Validation.ThrowIfNullWhiteSpaceOrReturn(eventName, nameof(eventName)); + + var contextData = context.Session.Items; + + // add or reference the list of events on the active context + var eventsCollectionFound = contextData + .TryGetValue( + SubscriptionConstants.ContextDataKeys.RAISED_EVENTS_COLLECTION, + out var listObject); + + if (!eventsCollectionFound) + { + listObject = new List(1); + contextData.TryAdd( + SubscriptionConstants.ContextDataKeys.RAISED_EVENTS_COLLECTION, + listObject); + } + + var eventList = listObject as IList; + if (eventList == null) + { + throw new GraphExecutionException( + $"Unable to cast the context data item '{SubscriptionConstants.ContextDataKeys.RAISED_EVENTS_COLLECTION}' " + + $"(type: {listObject?.GetType().FriendlyName() ?? "unknown"}), into " + + $"{typeof(IList).FriendlyName()}. Event '{eventName}' could not be published.", + context.Request.Origin); + } + + lock (eventList) + { + eventList.Add(new SubscriptionEventProxy(eventName, dataObject)); + } + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Controllers/ActionResults/SubscriptionGraphActionResult.cs b/src/graphql-aspnet-subscriptions/Controllers/ActionResults/SubscriptionGraphActionResult.cs new file mode 100644 index 000000000..56866ff92 --- /dev/null +++ b/src/graphql-aspnet-subscriptions/Controllers/ActionResults/SubscriptionGraphActionResult.cs @@ -0,0 +1,59 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Controllers.ActionResults +{ + using GraphQL.AspNet.Interfaces.Controllers; + + /// + /// A helper class to allow the use of common methods + /// with non-controller based resolvers for subscription related results. + /// + public static class SubscriptionGraphActionResult + { + /// + /// When used as an action result from subscription, indicates that the subscription should be skipped + /// and the connected client should receive NO data, as if the event never occured. + /// + /// if set to true, instructs that the + /// subscription should also be gracefully end such that no additional events + /// are processed after the event is skipped. The client may be informed of this operation if + /// supported by its negotiated protocol. + /// + /// If used as an action result for a non-subscription action (i.e. a query or mutation) a critical + /// error will be added to the response and the query will end. + /// + /// An action result indicating that all field resolution results should be skipped + /// and no data should be sent to the connected client. + public static IGraphActionResult SkipSubscriptionEvent(bool completeSubscirption = false) + { + return new SkipSubscriptionEventGraphActionResult(completeSubscirption); + } + + /// + /// When used as an action result from subscription, resolves the field with the given object + /// and indicates that to the client that the subscription should gracefully end when this event completes. + /// Once completed, the subscription will be unregsitered and no additional events will + /// be raised to this client. The client will be informed of this operation if supported + /// by its negotiated protocol. + /// + /// The object to resolve the field with. + /// + /// If used as an action result for a non-subscription action (i.e. a query or mutation) a critical + /// error will be added to the response and the query will end. + /// + /// An action result indicating a successful field resolution with the supplied + /// and additional information to instruct the subscription server to close the subscription + /// once processing is completed. + public static IGraphActionResult OkAndComplete(object item = null) + { + return new CompleteSubscriptionGraphActionResult(item); + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Controllers/GraphControllerExtensions.cs b/src/graphql-aspnet-subscriptions/Controllers/GraphControllerExtensions.cs index 7a4b0d80f..63924231a 100644 --- a/src/graphql-aspnet-subscriptions/Controllers/GraphControllerExtensions.cs +++ b/src/graphql-aspnet-subscriptions/Controllers/GraphControllerExtensions.cs @@ -9,14 +9,8 @@ namespace GraphQL.AspNet.Controllers { - using System; - using System.Collections.Generic; - using GraphQL.AspNet.Common; - using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Controllers.ActionResults; - using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.SubscriptionServer; /// /// Extension methods to expose subscription to graph controllers. @@ -33,39 +27,7 @@ public static class GraphControllerExtensions /// The data object to pass with the event. public static void PublishSubscriptionEvent(this GraphController controller, string eventName, object dataObject) { - Validation.ThrowIfNull(dataObject, nameof(dataObject)); - eventName = Validation.ThrowIfNullWhiteSpaceOrReturn(eventName, nameof(eventName)); - - var contextData = controller.Context.Session.Items; - - // add or reference the list of events on the active context - var eventsCollectionFound = contextData - .TryGetValue( - SubscriptionConstants.ContextDataKeys.RAISED_EVENTS_COLLECTION, - out var listObject); - - if (!eventsCollectionFound) - { - listObject = new List(1); - contextData.TryAdd( - SubscriptionConstants.ContextDataKeys.RAISED_EVENTS_COLLECTION, - listObject); - } - - var eventList = listObject as IList; - if (eventList == null) - { - throw new GraphExecutionException( - $"Unable to cast the context data item '{SubscriptionConstants.ContextDataKeys.RAISED_EVENTS_COLLECTION}' " + - $"(type: {listObject?.GetType().FriendlyName() ?? "unknown"}), into " + - $"{typeof(IList).FriendlyName()}. Event '{eventName}' could not be published.", - controller.Request.Origin); - } - - lock (eventList) - { - eventList.Add(new SubscriptionEventProxy(eventName, dataObject)); - } + controller?.Context?.PublishSubscriptionEvent(eventName, dataObject); } /// @@ -85,7 +47,7 @@ public static void PublishSubscriptionEvent(this GraphController controller, str /// and no data should be sent to the connected client. public static IGraphActionResult SkipSubscriptionEvent(this GraphController controller, bool completeSubscirption = false) { - return new SkipSubscriptionEventGraphActionResult(completeSubscirption); + return SubscriptionGraphActionResult.SkipSubscriptionEvent(completeSubscirption); } /// @@ -106,7 +68,7 @@ public static IGraphActionResult SkipSubscriptionEvent(this GraphController cont /// once processing is completed. public static IGraphActionResult OkAndComplete(this GraphController controller, object item = null) { - return new CompleteSubscriptionGraphActionResult(item); + return SubscriptionGraphActionResult.OkAndComplete(item); } } } \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Controllers/SubscriptionEnabledRuntimeFieldExecutionController.cs b/src/graphql-aspnet-subscriptions/Controllers/SubscriptionEnabledRuntimeFieldExecutionController.cs new file mode 100644 index 000000000..42ca9f07a --- /dev/null +++ b/src/graphql-aspnet-subscriptions/Controllers/SubscriptionEnabledRuntimeFieldExecutionController.cs @@ -0,0 +1,22 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Controllers +{ + using GraphQL.AspNet.Attributes; + + /// + /// A special controller instance for executing subscription runtime configured controller + /// actions (e.g. minimal api defined fields). + /// + [GraphRoot] + internal class SubscriptionEnabledRuntimeFieldExecutionController : GraphController + { + } +} \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledGraphQLSchemaFactory.cs b/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledGraphQLSchemaFactory.cs new file mode 100644 index 000000000..184172912 --- /dev/null +++ b/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledGraphQLSchemaFactory.cs @@ -0,0 +1,50 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Engine +{ + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Schemas.TypeMakers; + + /// + /// A schema creation factory that understands and can generate subscription types. + /// + /// The type of the schema being generated. + public class SubscriptionEnabledGraphQLSchemaFactory : DefaultGraphQLSchemaFactory + where TSchema : class, ISchema + { + /// + protected override GraphTypeMakerFactory CreateMakerFactory() + { + return new SubscriptionEnabledGraphTypeMakerFactory(this.Schema); + } + + /// + protected override void AddRuntimeFieldDefinition(IGraphQLRuntimeResolvedFieldDefinition fieldDefinition) + { + Validation.ThrowIfNull(fieldDefinition, nameof(fieldDefinition)); + + if (fieldDefinition is IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition subField) + { + var template = new RuntimeSubscriptionGraphControllerTemplate(subField); + template.Parse(); + template.ValidateOrThrow(); + + this.AddController(template); + return; + } + + base.AddRuntimeFieldDefinition(fieldDefinition); + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledGraphTypeMakerProvider.cs b/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledGraphTypeMakerProvider.cs deleted file mode 100644 index 5dfb03d58..000000000 --- a/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledGraphTypeMakerProvider.cs +++ /dev/null @@ -1,28 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Engine -{ - using GraphQL.AspNet.Engine.TypeMakers; - using GraphQL.AspNet.Interfaces.Engine; - using GraphQL.AspNet.Interfaces.Schema; - - /// - /// An upgraded "type maker" factory that adds low level subscription field support - /// to the type system. - /// - public class SubscriptionEnabledGraphTypeMakerProvider : DefaultGraphTypeMakerProvider - { - /// - public override IGraphFieldMaker CreateFieldMaker(ISchema schema) - { - return new SubscriptionEnabledGraphFieldMaker(schema); - } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledTypeTemplateProvider.cs b/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledTypeTemplateProvider.cs deleted file mode 100644 index 9e9214e43..000000000 --- a/src/graphql-aspnet-subscriptions/Engine/SubscriptionEnabledTypeTemplateProvider.cs +++ /dev/null @@ -1,39 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Engine -{ - using System; - using GraphQL.AspNet.Common; - using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal.TypeTemplates; - using GraphQL.AspNet.Schemas.TypeSystem; - - /// - /// A template provider that adds the ability to parse subscription marked fields on graph controllers - /// in order to add them to the schema. - /// - public class SubscriptionEnabledTypeTemplateProvider : DefaultTypeTemplateProvider - { - /// - /// Makes a graph template from the given type. - /// - /// Type of the object. - /// The kind of graph type to parse for. - /// IGraphItemTemplate. - protected override IGraphTypeTemplate MakeTemplate(Type objectType, TypeKind kind) - { - if (Validation.IsCastable(objectType)) - return new SubscriptionGraphControllerTemplate(objectType); - - return base.MakeTemplate(objectType, kind); - } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Interfaces/Schema/RuntimeDefinitions/IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition.cs b/src/graphql-aspnet-subscriptions/Interfaces/Schema/RuntimeDefinitions/IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition.cs new file mode 100644 index 000000000..e0c24ca39 --- /dev/null +++ b/src/graphql-aspnet-subscriptions/Interfaces/Schema/RuntimeDefinitions/IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition.cs @@ -0,0 +1,24 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions +{ + /// + /// A special marker interface to indicate a runtime resolved field is subscription enabled. + /// + public interface IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition : IGraphQLRuntimeResolvedFieldDefinition + { + /// + /// Gets or sets the name of the event that must be published from a mutation to invoke this + /// subscription. + /// + /// The name of the published event that identifies this subscription. + string EventName { get; set; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeSubscriptionEnabledFieldGroupTemplate.cs b/src/graphql-aspnet-subscriptions/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeSubscriptionEnabledFieldGroupTemplate.cs new file mode 100644 index 000000000..496e584a2 --- /dev/null +++ b/src/graphql-aspnet-subscriptions/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeSubscriptionEnabledFieldGroupTemplate.cs @@ -0,0 +1,63 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions +{ + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + + /// + /// A with the ability to create subscription + /// enabled minimal api fields. + /// + public sealed class RuntimeSubscriptionEnabledFieldGroupTemplate : RuntimeFieldGroupTemplateBase + { + /// + /// Initializes a new instance of the class. + /// + /// The primary schema options where this field (and its children) will be + /// instantiated. + /// The path template describing this field. + public RuntimeSubscriptionEnabledFieldGroupTemplate( + SchemaOptions options, string pathTemplate) + : base( + options, + SchemaItemCollections.Subscription, + pathTemplate) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The field from which this entity is being added. + /// The partial path template to be appended to + /// the parent's already defined template. + public RuntimeSubscriptionEnabledFieldGroupTemplate( + IGraphQLRuntimeFieldGroupDefinition parentField, string fieldSubTemplate) + : base(parentField, fieldSubTemplate) + { + } + + /// + public override IGraphQLRuntimeFieldGroupDefinition MapChildGroup(string pathTemplate) + { + return new RuntimeSubscriptionEnabledFieldGroupTemplate(this, pathTemplate); + } + + /// + public override IGraphQLRuntimeResolvedFieldDefinition MapField(string pathTemplate) + { + var field = new RuntimeSubscriptionEnabledResolvedFieldDefinition(this, pathTemplate, null); + this.Options.AddRuntimeSchemaItem(field); + return field; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeSubscriptionEnabledResolvedFieldDefinition.cs b/src/graphql-aspnet-subscriptions/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeSubscriptionEnabledResolvedFieldDefinition.cs new file mode 100644 index 000000000..8f8648a47 --- /dev/null +++ b/src/graphql-aspnet-subscriptions/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeSubscriptionEnabledResolvedFieldDefinition.cs @@ -0,0 +1,122 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions +{ + using System; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas.Structural; + + /// + /// A runtime field definition that supports additional required data items to properly setup a subscription via + /// a "minimal api" call. + /// + public class RuntimeSubscriptionEnabledResolvedFieldDefinition : RuntimeResolvedFieldDefinition, IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition + { + private string _customEventName; + + /// + /// Converts the unresolved field into a resolved field. The newly generated field + /// will NOT be attached to any schema and will not have an assigned resolver. + /// + /// The field template. + /// IGraphQLResolvedFieldTemplate. + internal static IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition FromFieldTemplate(IGraphQLRuntimeFieldGroupDefinition fieldTemplate) + { + Validation.ThrowIfNull(fieldTemplate, nameof(fieldTemplate)); + var field = new RuntimeSubscriptionEnabledResolvedFieldDefinition( + fieldTemplate.Options, + fieldTemplate.Route, + null); + + foreach (var attrib in fieldTemplate.Attributes) + field.AddAttribute(attrib); + + return field; + } + + /// + /// Initializes a new instance of the class. + /// + /// The parent field builder to which this new, resolved field + /// will be appended. + /// The template part to append to the parent field's template. + /// Name of the event that must be published to invoke this subscription + /// field. + public RuntimeSubscriptionEnabledResolvedFieldDefinition( + IGraphQLRuntimeFieldGroupDefinition parentFieldBuilder, + string fieldSubTemplate, + string eventName) + : base(parentFieldBuilder, fieldSubTemplate) + { + this.EventName = eventName?.Trim(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The schema options to which this field is being added. + /// The full route to use for this item. + /// Name of the event that must be published to invoke this subscription + /// field. + private RuntimeSubscriptionEnabledResolvedFieldDefinition( + SchemaOptions schemaOptions, + SchemaItemPath route, + string eventName) + : base(schemaOptions, route) + { + this.EventName = eventName?.Trim(); + } + + /// + protected override Attribute CreatePrimaryAttribute() + { + var (collection, path) = this.Route; + if (collection == SchemaItemCollections.Subscription) + { + return new SubscriptionRootAttribute(path, this.ReturnType) + { + InternalName = this.InternalName, + EventName = this.EventName, + }; + } + + return base.CreatePrimaryAttribute(); + } + + /// + public string EventName + { + get + { + return _customEventName; + } + + set + { + _customEventName = value?.Trim(); + if (!string.IsNullOrWhiteSpace(_customEventName)) + return; + + var (_, path) = this.Route; + + // turns path1/path2/path3 => path1_path2_path3 + _customEventName = string.Join( + "_", + path.Split( + Constants.Routing.PATH_SEPERATOR, + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)); + } + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/RuntimeSubscriptionGraphControllerTemplate.cs b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/RuntimeSubscriptionGraphControllerTemplate.cs new file mode 100644 index 000000000..f2d2b8eed --- /dev/null +++ b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/RuntimeSubscriptionGraphControllerTemplate.cs @@ -0,0 +1,72 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates +{ + using System.Collections.Generic; + using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + + /// + /// A "controller template" representing a single runtime configured subscription field (e.g. minimal api). + /// This template is never cached. + /// + internal class RuntimeSubscriptionGraphControllerTemplate : SubscriptionGraphControllerTemplate + { + private readonly IMemberInfoProvider _fieldProvider; + private readonly IGraphQLRuntimeResolvedFieldDefinition _fieldDefinition; + + /// + /// Initializes a new instance of the class. + /// + /// A single, runtime configured, field definition + /// to templatize for a specfic schema. + public RuntimeSubscriptionGraphControllerTemplate(IGraphQLSubscriptionEnabledRuntimeResolvedFieldDefinition fieldDefinition) + : base(typeof(SubscriptionEnabledRuntimeFieldExecutionController)) + { + _fieldDefinition = fieldDefinition; + if (fieldDefinition.Resolver?.Method != null) + { + _fieldProvider = new MemberInfoProvider( + fieldDefinition.Resolver.Method, + new RuntimeSchemaItemAttributeProvider(fieldDefinition)); + } + } + + /// + protected override IEnumerable GatherPossibleFieldTemplates() + { + yield return _fieldProvider; + } + + /// + protected override bool CouldBeGraphField(IMemberInfoProvider fieldProvider) + { + return fieldProvider != null && fieldProvider == _fieldProvider; + } + + /// + public override void ValidateOrThrow(bool validateChildren = true) + { + if (_fieldDefinition?.Resolver?.Method == null) + { + throw new GraphTypeDeclarationException( + $"Unable to templatize the runtime field definition of '{_fieldDefinition?.Route.Path ?? "~null~"}' the resolver " + + $"is not properly configured."); + } + + base.ValidateOrThrow(validateChildren); + } + + /// + public override ItemSource TemplateSource => ItemSource.Runtime; + } +} \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionControllerActionGraphFieldTemplate.cs b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionControllerActionGraphFieldTemplate.cs similarity index 80% rename from src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionControllerActionGraphFieldTemplate.cs rename to src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionControllerActionGraphFieldTemplate.cs index 10d854b22..5df0cff9a 100644 --- a/src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionControllerActionGraphFieldTemplate.cs +++ b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionControllerActionGraphFieldTemplate.cs @@ -7,12 +7,13 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Diagnostics; using System.Linq; using System.Reflection; + using GraphQL.AspNet; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Controllers; @@ -32,8 +33,8 @@ public class SubscriptionControllerActionGraphFieldTemplate : ControllerActionGr /// /// Initializes a new instance of the class. /// - /// The parent. - /// The method information. + /// The controller that owns this action. + /// The method information to be templatized. public SubscriptionControllerActionGraphFieldTemplate( IGraphControllerTemplate parent, MethodInfo methodInfo) @@ -42,6 +43,22 @@ public SubscriptionControllerActionGraphFieldTemplate( this.EventName = null; } + /// + /// Initializes a new instance of the class. + /// + /// The controller that owns this action. + /// The method information to be templatized. + /// A custom, external attribute provider to use instead for extracting + /// configuration attributes instead of the provider on . + public SubscriptionControllerActionGraphFieldTemplate( + IGraphControllerTemplate parent, + MethodInfo methodInfo, + ICustomAttributeProvider attributeProvider) + : base(parent, methodInfo, attributeProvider) + { + this.EventName = null; + } + /// protected override void ParseTemplateDefinition() { @@ -82,14 +99,14 @@ public override void ValidateOrThrow(bool validateChildren = true) if (string.IsNullOrWhiteSpace(this.EventName)) { throw new GraphTypeDeclarationException( - $"Invalid subscription action declaration. The method '{this.InternalFullName}' does not " + + $"Invalid subscription action declaration. The method '{this.InternalName}' does not " + $"have an event name."); } if (!Constants.RegExPatterns.NameRegex.IsMatch(this.EventName)) { throw new GraphTypeDeclarationException( - $"Invalid subscription action declaration. The method '{this.InternalFullName}' declares " + + $"Invalid subscription action declaration. The method '{this.InternalName}' declares " + $"a custom event name of '{this.EventName}'. However, the event name must conform to " + $"standard graphql naming rules. (Regex: {Constants.RegExPatterns.NameRegex} )"); } @@ -98,17 +115,17 @@ public override void ValidateOrThrow(bool validateChildren = true) if (this.Method.GetParameters().Where(x => x.HasAttribute()).Count() > 1) { throw new GraphTypeDeclarationException( - $"Invalid subscription action declaration. The method '{this.InternalFullName}' decorates more" + + $"Invalid subscription action declaration. The method '{this.InternalName}' decorates more" + $"than one parameter with {typeof(SubscriptionSourceAttribute).FriendlyName()}. At most one parameter " + $"can be attributed with {typeof(SubscriptionSourceAttribute).FriendlyName()}"); } // ensure there is only one param marked as the source object - var sourceArgument = this.Arguments.SingleOrDefault(x => x.ArgumentModifiers.HasFlag(GraphArgumentModifiers.ParentFieldResult)); + var sourceArgument = this.Arguments.SingleOrDefault(x => x.ArgumentModifier.HasFlag(GraphArgumentModifiers.ParentFieldResult)); if (sourceArgument == null) { throw new GraphTypeDeclarationException( - $"Invalid subscription action declaration. The method '{this.InternalFullName}' must " + + $"Invalid subscription action declaration. The method '{this.InternalName}' must " + $"declare 1 (and only 1) parameter of type '{this.SourceObjectType.FriendlyName()}' which will be populated" + "with the source data raised by a subscription event at runtime. Alternately use " + $"{typeof(SubscriptionSourceAttribute).FriendlyName()} to explicitly assign a source data parameter."); @@ -118,7 +135,7 @@ public override void ValidateOrThrow(bool validateChildren = true) } /// - protected override GraphArgumentTemplate CreateInputArgument(ParameterInfo paramInfo) + protected override GraphArgumentTemplate CreateArgument(ParameterInfo paramInfo) { if (this.Route.RootCollection == Execution.SchemaItemCollections.Subscription) { @@ -128,7 +145,7 @@ protected override GraphArgumentTemplate CreateInputArgument(ParameterInfo param _explicitlyDeclaredAsSubscriptionSourceType != null); } - return base.CreateInputArgument(paramInfo); + return base.CreateArgument(paramInfo); } /// diff --git a/src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionGraphArgumentTemplate.cs b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionGraphArgumentTemplate.cs similarity index 96% rename from src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionGraphArgumentTemplate.cs rename to src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionGraphArgumentTemplate.cs index 05483c3bd..3f37ff266 100644 --- a/src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionGraphArgumentTemplate.cs +++ b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionGraphArgumentTemplate.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System.Reflection; using GraphQL.AspNet.Attributes; diff --git a/src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionGraphControllerTemplate.cs b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionGraphControllerTemplate.cs similarity index 62% rename from src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionGraphControllerTemplate.cs rename to src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionGraphControllerTemplate.cs index 624df3f99..2fd4bc100 100644 --- a/src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionGraphControllerTemplate.cs +++ b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionGraphControllerTemplate.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Diagnostics; @@ -33,21 +33,20 @@ public SubscriptionGraphControllerTemplate(Type controllerType) { } - /// - /// When overridden in a child, allows the class to create custom template that inherit from - /// to provide additional functionality or garuntee a certian type structure for all methods on this object template. - /// - /// The method information. - /// IGraphFieldTemplate. - protected override IGraphFieldTemplate CreateMethodFieldTemplate(MethodInfo methodInfo) + /// + protected override IGraphFieldTemplate CreateFieldTemplate(IMemberInfoProvider member) { - if (methodInfo == null) + if (member?.MemberInfo == null || !(member.MemberInfo is MethodInfo)) return null; - if (methodInfo.HasAttribute() || methodInfo.HasAttribute()) - return new SubscriptionControllerActionGraphFieldTemplate(this, methodInfo); + if (member?.MemberInfo != null && + member.MemberInfo is MethodInfo methodInfo && + (member.AttributeProvider.HasAttribute() || member.AttributeProvider.HasAttribute())) + { + return new SubscriptionControllerActionGraphFieldTemplate(this, methodInfo, member.AttributeProvider); + } - return base.CreateMethodFieldTemplate(methodInfo); + return base.CreateFieldTemplate(member); } } } \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Engine/TypeMakers/SubscriptionEnabledGraphFieldMaker.cs b/src/graphql-aspnet-subscriptions/Schemas/TypeMakers/SubscriptionEnabledGraphFieldMaker.cs similarity index 70% rename from src/graphql-aspnet-subscriptions/Engine/TypeMakers/SubscriptionEnabledGraphFieldMaker.cs rename to src/graphql-aspnet-subscriptions/Schemas/TypeMakers/SubscriptionEnabledGraphFieldMaker.cs index 2456c393f..e6a019715 100644 --- a/src/graphql-aspnet-subscriptions/Engine/TypeMakers/SubscriptionEnabledGraphFieldMaker.cs +++ b/src/graphql-aspnet-subscriptions/Schemas/TypeMakers/SubscriptionEnabledGraphFieldMaker.cs @@ -6,15 +6,17 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Engine.TypeMakers + +namespace GraphQL.AspNet.Schemas.TypeMakers { using System.Collections.Generic; using GraphQL.AspNet.Configuration.Formatting; using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Engine; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.TypeTemplates; - using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Security; @@ -25,16 +27,17 @@ namespace GraphQL.AspNet.Engine.TypeMakers public class SubscriptionEnabledGraphFieldMaker : GraphFieldMaker { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The schema. - public SubscriptionEnabledGraphFieldMaker(ISchema schema) - : base(schema) + /// The schema instance to reference when creating fields. + /// A maker that can make arguments declared on this field. + public SubscriptionEnabledGraphFieldMaker(ISchema schema, IGraphArgumentMaker argMaker) + : base(schema, argMaker) { } /// - protected override MethodGraphField InstantiateField( + protected override MethodGraphField CreateFieldInstance( GraphNameFormatter formatter, IGraphFieldTemplate template, List securityGroups) @@ -46,10 +49,14 @@ protected override MethodGraphField InstantiateField( { var directives = template.CreateAppliedDirectives(); + var schemaTypeName = this.PrepareTypeName(template); + var typeExpression = template.TypeExpression.CloneTo(schemaTypeName); + return new SubscriptionMethodGraphField( formatter.FormatFieldName(template.Name), - template.TypeExpression.CloneTo(formatter.FormatGraphTypeName(template.TypeExpression.TypeName)), + typeExpression, template.Route, + template.InternalName, template.ObjectType, template.DeclaredReturnType, template.Mode, @@ -59,7 +66,7 @@ protected override MethodGraphField InstantiateField( directives); } - return base.InstantiateField(formatter, template, securityGroups); + return base.CreateFieldInstance(formatter, template, securityGroups); } } } \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Schemas/TypeMakers/SubscriptionEnabledGraphTypeMakerFactory.cs b/src/graphql-aspnet-subscriptions/Schemas/TypeMakers/SubscriptionEnabledGraphTypeMakerFactory.cs new file mode 100644 index 000000000..61df8b054 --- /dev/null +++ b/src/graphql-aspnet-subscriptions/Schemas/TypeMakers/SubscriptionEnabledGraphTypeMakerFactory.cs @@ -0,0 +1,61 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.TypeMakers +{ + using System; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Interfaces.Engine; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// An upgraded "type maker" factory that adds low level subscription field support + /// to the type system. + /// + public class SubscriptionEnabledGraphTypeMakerFactory : GraphTypeMakerFactory + { + private readonly ISchema _schemaInstance; + + /// + /// Initializes a new instance of the class. + /// + /// The schema instance to reference when making + /// types. + public SubscriptionEnabledGraphTypeMakerFactory(ISchema schemaInstance) + : base(schemaInstance) + { + _schemaInstance = Validation.ThrowIfNullOrReturn(schemaInstance, nameof(schemaInstance)); + } + + /// + public override IGraphTypeTemplate MakeTemplate(Type objectType, TypeKind? kind = null) + { + if (Validation.IsCastable(objectType)) + { + var template = new SubscriptionGraphControllerTemplate(objectType); + template.Parse(); + template.ValidateOrThrow(); + return template; + } + + return base.MakeTemplate(objectType, kind); + } + + /// + public override IGraphFieldMaker CreateFieldMaker() + { + return new SubscriptionEnabledGraphFieldMaker(_schemaInstance, this.CreateArgumentMaker()); + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/Schemas/TypeSystem/SubscriptionMethodGraphField.cs b/src/graphql-aspnet-subscriptions/Schemas/TypeSystem/SubscriptionMethodGraphField.cs index 1d9a156d9..1f9434078 100644 --- a/src/graphql-aspnet-subscriptions/Schemas/TypeSystem/SubscriptionMethodGraphField.cs +++ b/src/graphql-aspnet-subscriptions/Schemas/TypeSystem/SubscriptionMethodGraphField.cs @@ -27,17 +27,20 @@ public class SubscriptionMethodGraphField : MethodGraphField, ISubscriptionGraph /// Name of the field in the type declaration.. /// The meta data about how this type field is implemented. /// The formal route to this field in the object graph. + /// The fully qualified name of the method this field respresents, as it was declared + /// in C# code. /// The .NET type of the item or items that represent the graph type returned by this field. /// The .NET type as it was declared on the property which generated this field.. /// The execution mode of this field. /// The resolver. - /// The security policies. + /// The security policies applied to this field. /// Alterante name of the event that has been assigned to this field. /// The directives to be applied to this field when its added to a schema. public SubscriptionMethodGraphField( string fieldName, GraphTypeExpression typeExpression, SchemaItemPath route, + string internalFullName, Type objectType = null, Type declaredReturnType = null, Execution.FieldResolutionMode mode = Execution.FieldResolutionMode.PerSourceItem, @@ -45,7 +48,7 @@ public SubscriptionMethodGraphField( IEnumerable securityPolicies = null, string eventName = null, IAppliedDirectiveCollection directives = null) - : base(fieldName, typeExpression, route, objectType, declaredReturnType, mode, resolver, securityPolicies, directives) + : base(fieldName, internalFullName, typeExpression, route, declaredReturnType, objectType, mode, resolver, securityPolicies, directives) { this.EventName = eventName; } @@ -57,6 +60,7 @@ protected override MethodGraphField CreateNewInstance(IGraphType parent) this.Name, this.TypeExpression.Clone(), parent.Route.CreateChild(this.Name), + this.InternalName, this.ObjectType, this.DeclaredReturnType, this.Mode, diff --git a/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionPublisherSchemaExtension.cs b/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionPublisherSchemaExtension.cs index 8133410cf..2b37eb246 100644 --- a/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionPublisherSchemaExtension.cs +++ b/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionPublisherSchemaExtension.cs @@ -10,12 +10,16 @@ namespace GraphQL.AspNet.SubscriptionServer { using System; + using System.Linq; using GraphQL.AspNet.Configuration; using GraphQL.AspNet.Engine; using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Engine; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas.TypeSystem; using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.DependencyInjection.Extensions; /// /// A schema extension encapsulating the ability for a given schema to publish subscription events from @@ -45,24 +49,6 @@ public void Configure(SchemaOptions options) { _primaryOptions = options; _primaryOptions.DeclarationOptions.AllowedOperations.Add(GraphOperationType.Subscription); - - // swap out the master providers for the ones that includes - // support for the subscription action type - if (!(GraphQLProviders.TemplateProvider is SubscriptionEnabledTypeTemplateProvider)) - GraphQLProviders.TemplateProvider = new SubscriptionEnabledTypeTemplateProvider(); - - if (!(GraphQLProviders.GraphTypeMakerProvider is SubscriptionEnabledGraphTypeMakerProvider)) - GraphQLProviders.GraphTypeMakerProvider = new SubscriptionEnabledGraphTypeMakerProvider(); - } - - /// - /// Invokes this instance to perform any final setup requirements as part of - /// its configuration during startup. - /// - /// The application builder, no middleware will be registered if not supplied. - /// The service provider to use. - public void UseExtension(IApplicationBuilder app = null, IServiceProvider serviceProvider = null) - { - } + } } } \ No newline at end of file diff --git a/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionReceiverSchemaExtension.cs b/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionReceiverSchemaExtension.cs index facb10443..bdefe3bee 100644 --- a/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionReceiverSchemaExtension.cs +++ b/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionReceiverSchemaExtension.cs @@ -10,10 +10,13 @@ namespace GraphQL.AspNet.SubscriptionServer { using System; + using System.Linq; using GraphQL.AspNet.Common; + using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Configuration; using GraphQL.AspNet.Engine; using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Engine; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Logging; using GraphQL.AspNet.Interfaces.Schema; @@ -89,16 +92,27 @@ public void Configure(SchemaOptions options) $"authorization method. (Current authorization method is \"{_schemaBuilder.Options.AuthorizationOptions.Method}\")"); } - // swap out the master templating provider for the one that includes - // support for the subscription action type if and only if the developer has not - // already registered their own custom one - if (GraphQLProviders.TemplateProvider == null || GraphQLProviders.TemplateProvider.GetType() == typeof(DefaultTypeTemplateProvider)) - GraphQLProviders.TemplateProvider = new SubscriptionEnabledTypeTemplateProvider(); + // swap out the master schema factory to one that includes + // support for the subscription action type + var existingFactories = _schemaBuilder + .Options + .ServiceCollection + .FirstOrDefault(x => x.ServiceType == typeof(IGraphQLSchemaFactory)); - // swap out the master graph type maker to its "subscription enabled" version - // if and only if the developer has not already registered their own custom instance - if (GraphQLProviders.GraphTypeMakerProvider == null || GraphQLProviders.GraphTypeMakerProvider.GetType() == typeof(DefaultGraphTypeMakerProvider)) - GraphQLProviders.GraphTypeMakerProvider = new SubscriptionEnabledGraphTypeMakerProvider(); + if (existingFactories != null) + { + _schemaBuilder.Options + .ServiceCollection + .RemoveAll(typeof(IGraphQLSchemaFactory)); + } + + _schemaBuilder.Options + .ServiceCollection + .TryAdd( + new ServiceDescriptor( + typeof(IGraphQLSchemaFactory), + typeof(SubscriptionEnabledGraphQLSchemaFactory), + ServiceLifetime.Transient)); // Update the query execution pipeline // ------------------------------------------ diff --git a/src/graphql-aspnet-subscriptions/graphql-aspnet-subscriptions.csproj b/src/graphql-aspnet-subscriptions/graphql-aspnet-subscriptions.csproj index 9e4407520..ab7590b5f 100644 --- a/src/graphql-aspnet-subscriptions/graphql-aspnet-subscriptions.csproj +++ b/src/graphql-aspnet-subscriptions/graphql-aspnet-subscriptions.csproj @@ -1,4 +1,4 @@ - + @@ -8,6 +8,8 @@ A package to support subscriptions for GraphQL ASP.NET. Provides the required functionality to setup a websocket connection and perform graphql subscriptions over web sockets. + + @@ -19,12 +21,4 @@ - - - - - - - - \ No newline at end of file diff --git a/src/graphql-aspnet/Attributes/FromGraphQLAttribute.cs b/src/graphql-aspnet/Attributes/FromGraphQLAttribute.cs index 6299ce590..f2333608a 100644 --- a/src/graphql-aspnet/Attributes/FromGraphQLAttribute.cs +++ b/src/graphql-aspnet/Attributes/FromGraphQLAttribute.cs @@ -59,5 +59,15 @@ public FromGraphQLAttribute(string argumentName) /// /// The type expression to assign to this argument. public string TypeExpression { get; set; } + + /// + /// Gets or sets a customized name to refer to this parameter in all log entries and error messages. + /// + /// + /// When not supplied the name defaults to a combination of the class + method + parameter naem. (e.g. 'MyObject.Method1.Param1'). This + /// can be especially helpful when working with runtime defined fields (e.g. minimal api). + /// + /// The name to refer to this parameter on internal messaging. + public string InternalName { get; set; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Attributes/GraphEnumValueAttribute.cs b/src/graphql-aspnet/Attributes/GraphEnumValueAttribute.cs index 15aa30d22..47dd61edd 100644 --- a/src/graphql-aspnet/Attributes/GraphEnumValueAttribute.cs +++ b/src/graphql-aspnet/Attributes/GraphEnumValueAttribute.cs @@ -40,5 +40,14 @@ public GraphEnumValueAttribute(string name) /// /// The name given to this enum value. public string Name { get; } + + /// + /// Gets or sets a customized name to refer to this .NET enum value in all log entries and error messages. + /// + /// + /// When not supplied the name defaults to the qualified name of the enum value. (e.g. 'MyEnum.Value1'). + /// + /// The name to refer to this enum value on internal messaging. + public string InternalName { get; set; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Attributes/GraphFieldAttribute.cs b/src/graphql-aspnet/Attributes/GraphFieldAttribute.cs index 8cbdf7224..44490e736 100644 --- a/src/graphql-aspnet/Attributes/GraphFieldAttribute.cs +++ b/src/graphql-aspnet/Attributes/GraphFieldAttribute.cs @@ -33,9 +33,10 @@ public GraphFieldAttribute() /// /// Initializes a new instance of the class. /// - /// Name of the field. - public GraphFieldAttribute(string name) - : this(false, SchemaItemCollections.Types, name) + /// The template naming scheme to use to generate a graph field from this method or property. The exact name may be altered on a per schema + /// basis depending on field name formatting rules etc. + public GraphFieldAttribute(string template) + : this(false, SchemaItemCollections.Types, template) { } @@ -97,14 +98,14 @@ protected GraphFieldAttribute( /// /// Gets the name of the union this field defines, if any. /// - /// The name of the union. + /// The name of the union type to create when multiple return types are possible. public string UnionTypeName { get; } /// /// Gets a value indicating whether this instance represents a template /// pathed from its root operation or if it is intended to be nested with another fragment. /// - /// true if this instance is root fragment; otherwise, false. + /// true if this instance is rooted to a top level operation; otherwise, false. public bool IsRootFragment { get; } /// @@ -145,7 +146,17 @@ protected GraphFieldAttribute( /// Gets the mode indicating how the runtime should process /// the objects resolving this field. /// - /// The mode. + /// The execution mode of this field when it is resolved by the runtime. public virtual FieldResolutionMode ExecutionMode => FieldResolutionMode.PerSourceItem; + + /// + /// Gets or sets a customized name to refer to this field in all log entries and error messages. + /// + /// + /// When not supplied the name defaults to the fully qualified method or property name. (e.g. 'MyObject.MyMethod'). This + /// can be especially helpful when working with runtime defined fields (e.g. minimal api). + /// + /// The name to refer to this field on internal messaging. + public string InternalName { get; set; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Attributes/GraphSkipAttribute.cs b/src/graphql-aspnet/Attributes/GraphSkipAttribute.cs index e7101fc70..17c3bdd98 100644 --- a/src/graphql-aspnet/Attributes/GraphSkipAttribute.cs +++ b/src/graphql-aspnet/Attributes/GraphSkipAttribute.cs @@ -21,7 +21,8 @@ namespace GraphQL.AspNet.Attributes | AttributeTargets.Field | AttributeTargets.Enum | AttributeTargets.Class - | AttributeTargets.Struct)] + | AttributeTargets.Struct + | AttributeTargets.Parameter)] public class GraphSkipAttribute : Attribute { // Implementation note: This attribute purposefully does diff --git a/src/graphql-aspnet/Attributes/GraphTypeAttribute.cs b/src/graphql-aspnet/Attributes/GraphTypeAttribute.cs index 05b4dab6f..814d801e9 100644 --- a/src/graphql-aspnet/Attributes/GraphTypeAttribute.cs +++ b/src/graphql-aspnet/Attributes/GraphTypeAttribute.cs @@ -111,5 +111,15 @@ public TemplateDeclarationRequirements FieldDeclarationRequirements /// /// true if publish; otherwise, false. public bool Publish { get; set; } + + /// + /// Gets or sets a customized name to refer to this .NET type in all log entries and error messages. + /// + /// + /// When not supplied the name defaults to the fully qualified class, struct or primative name. (e.g. 'MyObject', 'Person'). This + /// can be especially helpful when working with runtime defined fields (e.g. minimal api). + /// + /// The name to refer to this field on internal messaging. + public string InternalName { get; set; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Attributes/MutationAttribute.cs b/src/graphql-aspnet/Attributes/MutationAttribute.cs index 3e199d712..a36183d9c 100644 --- a/src/graphql-aspnet/Attributes/MutationAttribute.cs +++ b/src/graphql-aspnet/Attributes/MutationAttribute.cs @@ -36,7 +36,17 @@ public MutationAttribute() /// /// The template naming scheme to use to generate a graph field from this method. public MutationAttribute(string template) - : this(template, null) + : this(template, null as Type) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The template naming scheme to use to generate a graph field from this method. + /// Name of the union type. + public MutationAttribute(string template, string unionTypeName) + : this(template, unionTypeName, null) { } @@ -82,7 +92,7 @@ public MutationAttribute(string template, Type returnType) /// be sure to supply any additional concrete types so that they may be included in the object graph. /// Any additional types to include in the object graph on behalf of this method. public MutationAttribute(string template, Type returnType, params Type[] additionalTypes) - : base(false, SchemaItemCollections.Mutation, template, returnType.AsEnumerable().Concat(additionalTypes).ToArray()) + : base(false, SchemaItemCollections.Mutation, template, (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray()) { } @@ -92,15 +102,14 @@ public MutationAttribute(string template, Type returnType, params Type[] additio /// The template naming scheme to use to generate a graph field from this method. /// Name of the union type. /// The first of two required types to include in the union. - /// The second of two required types to include in the union. /// Any additional union types. - public MutationAttribute(string template, string unionTypeName, Type unionTypeA, Type unionTypeB, params Type[] additionalUnionTypes) + public MutationAttribute(string template, string unionTypeName, Type unionTypeA, params Type[] additionalUnionTypes) : base( false, SchemaItemCollections.Mutation, template, unionTypeName, - unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).ToArray()) + (new Type[] { unionTypeA }).Concat(additionalUnionTypes ?? Enumerable.Empty()).ToArray()) { } } diff --git a/src/graphql-aspnet/Attributes/MutationRootAttribute.cs b/src/graphql-aspnet/Attributes/MutationRootAttribute.cs index 9cebba929..a1b8bb873 100644 --- a/src/graphql-aspnet/Attributes/MutationRootAttribute.cs +++ b/src/graphql-aspnet/Attributes/MutationRootAttribute.cs @@ -35,7 +35,17 @@ public MutationRootAttribute() /// /// The template naming scheme to use to generate a graph field from this method. public MutationRootAttribute(string template) - : this(template, null) + : this(template, null as Type) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The template naming scheme to use to generate a graph field from this method. + /// Name of the union type. + public MutationRootAttribute(string template, string unionTypeName) + : this(template, unionTypeName, null, null) { } @@ -81,7 +91,7 @@ public MutationRootAttribute(string template, Type returnType) /// be sure to supply any additional concrete types so that they may be included in the object graph. /// Any additional types to include in the object graph on behalf of this method. public MutationRootAttribute(string template, Type returnType, params Type[] additionalTypes) - : base(true, SchemaItemCollections.Mutation, template, returnType.AsEnumerable().Concat(additionalTypes).ToArray()) + : base(true, SchemaItemCollections.Mutation, template, (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray()) { } @@ -91,10 +101,14 @@ public MutationRootAttribute(string template, Type returnType, params Type[] add /// The template naming scheme to use to generate a graph field from this method. /// Name of the union type. /// The first of two required types to include in the union. - /// The second of two required types to include in the union. /// Any additional union types. - public MutationRootAttribute(string template, string unionTypeName, Type unionTypeA, Type unionTypeB, params Type[] additionalUnionTypes) - : base(true, SchemaItemCollections.Mutation, template, unionTypeName, unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).ToArray()) + public MutationRootAttribute(string template, string unionTypeName, Type unionTypeA, params Type[] additionalUnionTypes) + : base( + true, + SchemaItemCollections.Mutation, + template, + unionTypeName, + (new Type[] { unionTypeA }).Concat(additionalUnionTypes ?? Enumerable.Empty()).ToArray()) { } } diff --git a/src/graphql-aspnet/Attributes/PossibleTypesAttribute.cs b/src/graphql-aspnet/Attributes/PossibleTypesAttribute.cs index edce03ab0..4543c7c09 100644 --- a/src/graphql-aspnet/Attributes/PossibleTypesAttribute.cs +++ b/src/graphql-aspnet/Attributes/PossibleTypesAttribute.cs @@ -12,7 +12,6 @@ namespace GraphQL.AspNet.Attributes using System; using System.Collections.Generic; using System.Linq; - using GraphQL.AspNet.Common.Extensions; /// /// When an graph field returns an interface, use this attribute @@ -32,13 +31,16 @@ public class PossibleTypesAttribute : Attribute /// Any additional possible types. public PossibleTypesAttribute(Type firstPossibleType, params Type[] additionalPossibleTypes) { - this.PossibleTypes = firstPossibleType.AsEnumerable().Concat(additionalPossibleTypes).Where(x => x != null).ToArray(); + this.PossibleTypes = new Type[] { firstPossibleType } + .Concat(additionalPossibleTypes ?? Enumerable.Empty()) + .Where(x => x != null) + .ToList(); } /// /// Gets the possible types this field may return under its declared interface. /// /// The possible types. - public IReadOnlyList PossibleTypes { get; } + public IList PossibleTypes { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Attributes/QueryAttribute.cs b/src/graphql-aspnet/Attributes/QueryAttribute.cs index 0e2ff7aa8..fe9083886 100644 --- a/src/graphql-aspnet/Attributes/QueryAttribute.cs +++ b/src/graphql-aspnet/Attributes/QueryAttribute.cs @@ -36,7 +36,17 @@ public QueryAttribute() /// /// The template naming scheme to use to generate a graph field from this method. public QueryAttribute(string template) - : this(template, null) + : this(template, null as Type) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The template naming scheme to use to generate a graph field from this method. + /// Name of the union type. + public QueryAttribute(string template, string unionTypeName) + : this(template, unionTypeName, null) { } @@ -82,7 +92,7 @@ public QueryAttribute(string template, Type returnType) /// be sure to supply any additional concrete types so that they may be included in the object graph. /// Any additional types to include in the object graph on behalf of this method. public QueryAttribute(string template, Type returnType, params Type[] additionalTypes) - : base(false, SchemaItemCollections.Query, template, returnType.AsEnumerable().Concat(additionalTypes).ToArray()) + : base(false, SchemaItemCollections.Query, template, (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray()) { } @@ -91,16 +101,15 @@ public QueryAttribute(string template, Type returnType, params Type[] additional /// /// The template naming scheme to use to generate a graph field from this method. /// Name of the union type. - /// The first of two required types to include in the union. - /// The second of two required types to include in the union. + /// The first type to include in the union. /// Any additional union types to include. - public QueryAttribute(string template, string unionTypeName, Type unionTypeA, Type unionTypeB, params Type[] additionalUnionTypes) + public QueryAttribute(string template, string unionTypeName, Type unionTypeA, params Type[] additionalUnionTypes) : base( false, SchemaItemCollections.Query, template, unionTypeName, - unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).ToArray()) + (new Type[] { unionTypeA }).Concat(additionalUnionTypes ?? Enumerable.Empty()).ToArray()) { } } diff --git a/src/graphql-aspnet/Attributes/QueryRootAttribute.cs b/src/graphql-aspnet/Attributes/QueryRootAttribute.cs index fe6d73102..1614ef2cb 100644 --- a/src/graphql-aspnet/Attributes/QueryRootAttribute.cs +++ b/src/graphql-aspnet/Attributes/QueryRootAttribute.cs @@ -35,7 +35,17 @@ public QueryRootAttribute() /// /// The template naming scheme to use to generate a graph field from this method. public QueryRootAttribute(string template) - : this(template, null) + : this(template, null as Type) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The template naming scheme to use to generate a graph field from this method. + /// Name of the union type. + public QueryRootAttribute(string template, string unionTypeName) + : this(template, unionTypeName, null, null) { } @@ -81,7 +91,11 @@ public QueryRootAttribute(string template, Type returnType) /// be sure to supply any additional concrete types so that they may be included in the object graph. /// Any additional types to include in the object graph on behalf of this method. public QueryRootAttribute(string template, Type returnType, params Type[] additionalTypes) - : base(true, SchemaItemCollections.Query, template, returnType.AsEnumerable().Concat(additionalTypes).ToArray()) + : base( + true, + SchemaItemCollections.Query, + template, + (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray()) { } @@ -91,15 +105,14 @@ public QueryRootAttribute(string template, Type returnType, params Type[] additi /// The template naming scheme to use to generate a graph field from this method. /// Name of the union type. /// The first of two required types to include in the union. - /// The second of two required types to include in the union. /// Any additional union types. - public QueryRootAttribute(string template, string unionTypeName, Type unionTypeA, Type unionTypeB, params Type[] additionalUnionTypes) + public QueryRootAttribute(string template, string unionTypeName, Type unionTypeA, params Type[] additionalUnionTypes) : base( true, SchemaItemCollections.Query, template, unionTypeName, - unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).ToArray()) + (new Type[] { unionTypeA }).Concat(additionalUnionTypes ?? Enumerable.Empty()).ToArray()) { } } diff --git a/src/graphql-aspnet/Attributes/UnionAttribute.cs b/src/graphql-aspnet/Attributes/UnionAttribute.cs new file mode 100644 index 000000000..61a4ec3b3 --- /dev/null +++ b/src/graphql-aspnet/Attributes/UnionAttribute.cs @@ -0,0 +1,90 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Attributes +{ + using System; + using System.Collections.Generic; + using System.Linq; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// An attribute that, when applied to an action method, field or property + /// declares that the field is to return one of multiple possible types. + /// + /// + /// Fields applying this attribute should return a or + /// for maximum compatiability. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] + public class UnionAttribute : GraphAttributeBase + { + /// + /// Initializes a new instance of the class. + /// + /// The name of the union as it should appear in the schema. + /// The first member type to include in the union. + /// Additional member types to include in the union. + /// All Unions must declare at least two member types that will be included in the union. + public UnionAttribute(string unionName, Type firstUnionMemberType, params Type[] otherUnionMembers) + { + this.UnionName = unionName?.Trim(); + + var list = new List(2 + otherUnionMembers.Length); + list.Add(firstUnionMemberType); + list.AddRange(otherUnionMembers); + this.UnionMemberTypes = list; + } + + /// + /// Initializes a new instance of the class. + /// + /// A type that inherits from or implements which + /// declares all required information about the referenced union. + public UnionAttribute(Type unionProxyType) + : this() + { + this.UnionName = null; + if (unionProxyType != null) + this.UnionMemberTypes.Add(unionProxyType); + } + + /// + /// Initializes a new instance of the class. + /// + /// The name to assign to this union. + public UnionAttribute(string unionName) + : this() + { + this.UnionName = unionName; + } + + /// + /// Prevents a default instance of the class from being created. + /// + private UnionAttribute() + { + this.UnionMemberTypes = new List(); + } + + /// + /// Gets or sets the name of the new union to create within the schema. This union name must be a valid graph name. + /// + /// The name of the union as it will appear in the schema. + public string UnionName { get; set; } + + /// + /// Gets or sets the concrete types of the objects that may be returned by this field. + /// + /// All union member types to be included in this union. + public IList UnionMemberTypes { get; set; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Common/Extensions/AttributeExtensions.cs b/src/graphql-aspnet/Common/Extensions/AttributeExtensions.cs new file mode 100644 index 000000000..89dd03e14 --- /dev/null +++ b/src/graphql-aspnet/Common/Extensions/AttributeExtensions.cs @@ -0,0 +1,44 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Common.Extensions +{ + using System; + using System.Linq; + using Microsoft.AspNetCore.Authorization; + + /// + /// Extension methods for working with the attributes supplied by the library. + /// + public static class AttributeExtensions + { + /// + /// Inspects the given attribute's attributes to determine if its allowed to be applied more than once + /// to a given entity. + /// + /// + /// Used primarily for runtime field configuration to ensure correct usage within the templating system. + /// + /// The attribute to check. + /// true if this instance can be applied to an entity multiple times the specified attribute; otherwise, false. + public static bool CanBeAppliedMultipleTimes(this Attribute attribute) + { + var usage = attribute.GetType().GetCustomAttributes(typeof(AttributeUsageAttribute), true) + .Cast() + .ToList(); + + // the default is always false + if (usage.Count == 0) + return false; + + // take the first found instance in the inheritance stack + return usage[0].AllowMultiple; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Common/Extensions/TypeExtensions.cs b/src/graphql-aspnet/Common/Extensions/TypeExtensions.cs index 165d0be32..9c3b4e426 100644 --- a/src/graphql-aspnet/Common/Extensions/TypeExtensions.cs +++ b/src/graphql-aspnet/Common/Extensions/TypeExtensions.cs @@ -106,16 +106,16 @@ public static TAttribute SingleAttributeOrDefault(this Enum enumValu /// is also returned. /// /// The type of the attribute to check for. - /// The type to inspect. + /// The type to inspect. /// When true, look up the hierarchy chain for the inherited custom attribute.. /// TAttribute. - public static TAttribute SingleAttributeOrDefault(this ICustomAttributeProvider type, bool inherit = false) + public static TAttribute SingleAttributeOrDefault(this ICustomAttributeProvider attribProvider, bool inherit = false) where TAttribute : Attribute { - if (type == null) + if (attribProvider == null) return null; - var attribs = type.GetCustomAttributes(typeof(TAttribute), inherit) + var attribs = attribProvider.GetCustomAttributes(typeof(TAttribute), inherit) .Where(x => x.GetType() == typeof(TAttribute)) .Take(2); @@ -130,16 +130,16 @@ public static TAttribute SingleAttributeOrDefault(this ICustomAttrib /// that is castable to the given type, the first instance encountered is returned. /// /// The type of the attribute to check for. - /// The type to inspect. + /// The type to inspect. /// When true, look up the hierarchy chain for the inherited custom attribute.. /// TAttribute. - public static TAttribute FirstAttributeOfTypeOrDefault(this ICustomAttributeProvider type, bool inherit = false) + public static TAttribute FirstAttributeOfTypeOrDefault(this ICustomAttributeProvider attribProvider, bool inherit = false) where TAttribute : Attribute { - if (type == null) + if (attribProvider == null) return null; - var attribs = type.GetCustomAttributes(typeof(TAttribute), inherit).Where(x => x.GetType() == typeof(TAttribute)).Take(2); + var attribs = attribProvider.GetCustomAttributes(typeof(TAttribute), inherit).Where(x => x.GetType() == typeof(TAttribute)).Take(2); return attribs.FirstOrDefault() as TAttribute; } @@ -148,16 +148,16 @@ public static TAttribute FirstAttributeOfTypeOrDefault(this ICustomA /// that matches the type condition, null is returned. /// /// The type of the attribute to check for. - /// The type to inspect. + /// The type to inspect. /// When true, look up the hierarchy chain for the inherited custom attribute.. /// TAttribute. - public static TAttribute SingleAttributeOfTypeOrDefault(this ICustomAttributeProvider type, bool inherit = false) + public static TAttribute SingleAttributeOfTypeOrDefault(this ICustomAttributeProvider attribProvider, bool inherit = false) where TAttribute : Attribute { - if (type == null) + if (attribProvider == null) return null; - var attribs = type.GetCustomAttributes(typeof(TAttribute), inherit).Cast().Take(2); + var attribs = attribProvider.GetCustomAttributes(typeof(TAttribute), inherit).Cast().Take(2); if (attribs.Count() == 1) return attribs.Single(); @@ -169,16 +169,16 @@ public static TAttribute SingleAttributeOfTypeOrDefault(this ICustom /// the given an empty set is returned. /// /// The type of the attribute to check for. - /// The type from which to extract attributes. + /// The type from which to extract attributes. /// When true, look up the hierarchy chain for the inherited custom attribute.. /// TAttribute. - public static IEnumerable AttributesOfType(this ICustomAttributeProvider type, bool inherit = false) + public static IEnumerable AttributesOfType(this ICustomAttributeProvider attribProvider, bool inherit = false) where TAttribute : Attribute { - if (type == null) + if (attribProvider == null) return null; - var attribs = type.GetCustomAttributes(typeof(TAttribute), inherit) + var attribs = attribProvider.GetCustomAttributes(typeof(TAttribute), inherit) .Where(x => Validation.IsCastable(x.GetType(), typeof(TAttribute))) .Cast(); @@ -189,28 +189,28 @@ public static IEnumerable AttributesOfType(this ICustomA /// Determines if the given type had the attribute defined at least once. /// /// The type of the attribute. - /// The type from which to check. + /// The type from which to check. /// When true, look up the hierarchy chain for the inherited custom attribute.. /// TAttribute. - public static bool HasAttribute(this ICustomAttributeProvider type, bool inherit = false) + public static bool HasAttribute(this ICustomAttributeProvider attribProvider, bool inherit = false) where TAttribute : Attribute { - return type.HasAttribute(typeof(TAttribute), inherit); + return attribProvider.HasAttribute(typeof(TAttribute), inherit); } /// /// Determines if the given type had the attribute defined at least once. /// - /// The type to check. + /// The type to check. /// Type of the attribute. /// When true, look up the hierarchy chain for the inherited custom attribute.. /// TAttribute. - public static bool HasAttribute(this ICustomAttributeProvider type, Type attributeType, bool inherit = false) + public static bool HasAttribute(this ICustomAttributeProvider attribProvider, Type attributeType, bool inherit = false) { - if (type == null || attributeType == null || !Validation.IsCastable(attributeType)) + if (attribProvider == null || attributeType == null || !Validation.IsCastable(attributeType)) return false; - return type.IsDefined(attributeType, inherit); + return attribProvider.IsDefined(attributeType, inherit); } /// diff --git a/src/graphql-aspnet/Common/Generics/InstanceFactory.cs b/src/graphql-aspnet/Common/Generics/InstanceFactory.cs index b6891c270..a4ca2d443 100644 --- a/src/graphql-aspnet/Common/Generics/InstanceFactory.cs +++ b/src/graphql-aspnet/Common/Generics/InstanceFactory.cs @@ -24,7 +24,8 @@ namespace GraphQL.AspNet.Common.Generics internal static class InstanceFactory { private static readonly ConcurrentDictionary, ObjectActivator> CACHED_OBJECT_CREATORS; - private static readonly ConcurrentDictionary CACHED_METHOD_INVOKERS; + private static readonly ConcurrentDictionary CACHED_INSTANCE_METHOD_INVOKERS; + private static readonly ConcurrentDictionary CACHED_STATIC_METHOD_INVOKERS; private static readonly ConcurrentDictionary CACHED_PROPERTY_SETTER_INVOKERS; private static readonly ConcurrentDictionary CACHED_PROPERTY_GETTER_INVOKERS; @@ -34,7 +35,8 @@ internal static class InstanceFactory static InstanceFactory() { CACHED_OBJECT_CREATORS = new ConcurrentDictionary, ObjectActivator>(); - CACHED_METHOD_INVOKERS = new ConcurrentDictionary(); + CACHED_INSTANCE_METHOD_INVOKERS = new ConcurrentDictionary(); + CACHED_STATIC_METHOD_INVOKERS = new ConcurrentDictionary(); CACHED_PROPERTY_SETTER_INVOKERS = new ConcurrentDictionary(); CACHED_PROPERTY_GETTER_INVOKERS = new ConcurrentDictionary(); } @@ -45,7 +47,8 @@ static InstanceFactory() public static void Clear() { CACHED_OBJECT_CREATORS.Clear(); - CACHED_METHOD_INVOKERS.Clear(); + CACHED_INSTANCE_METHOD_INVOKERS.Clear(); + CACHED_STATIC_METHOD_INVOKERS.Clear(); CACHED_PROPERTY_SETTER_INVOKERS.Clear(); CACHED_PROPERTY_GETTER_INVOKERS.Clear(); } @@ -169,17 +172,17 @@ public static PropertyGetterCollection CreatePropertyGetterInvokerCollection(Typ } /// - /// Creates a compiled lamda expression to invoke an arbitrary method via a common set of parameters. This lamda is cached for any future use greatly speeding up - /// the invocation. + /// Creates a compiled lamda expression to invoke an arbitrary method via a common set of parameters on an object instance. + /// This lamda is cached for any future use greatly speeding up the invocation. /// /// The method information. /// Func<System.Object, System.Object[], System.Object>. - public static MethodInvoker CreateInstanceMethodInvoker(MethodInfo methodInfo) + public static InstanceMethodInvoker CreateInstanceMethodInvoker(MethodInfo methodInfo) { if (methodInfo == null) return null; - if (CACHED_METHOD_INVOKERS.TryGetValue(methodInfo, out var invoker)) + if (CACHED_INSTANCE_METHOD_INVOKERS.TryGetValue(methodInfo, out var invoker)) return invoker; if (methodInfo.IsStatic) @@ -194,17 +197,114 @@ public static MethodInvoker CreateInstanceMethodInvoker(MethodInfo methodInfo) "This instance creator only supports methods with a return value."); } - invoker = CreateMethodInvoker(methodInfo); - CACHED_METHOD_INVOKERS.TryAdd(methodInfo, invoker); + invoker = CompileInstanceMethodInvoker(methodInfo); + CACHED_INSTANCE_METHOD_INVOKERS.TryAdd(methodInfo, invoker); return invoker; } /// - /// Creates the method invoker expression and compiles the resultant lambda. + /// Creates a compiled lamda expression to invoke an arbitrary static method via a common set of parameters. + /// This lamda is cached for any future use greatly speeding up the invocation. + /// + /// The method to create an expression for. + /// StaticMethodInvoker. + public static StaticMethodInvoker CreateStaticMethodInvoker(MethodInfo methodInfo) + { + if (methodInfo == null) + return null; + + if (CACHED_STATIC_METHOD_INVOKERS.TryGetValue(methodInfo, out var invoker)) + return invoker; + + if (!methodInfo.IsStatic) + { + throw new ArgumentException($"The method '{methodInfo.Name}' on type '{methodInfo.DeclaringType.FriendlyName()}' is NOT static " + + "and cannot be used to create a static method reference."); + } + + if (methodInfo.ReturnType == typeof(void)) + { + throw new ArgumentException($"The method '{methodInfo.Name}' on type '{methodInfo.DeclaringType.FriendlyName()}' does not return a value. " + + "This instance creator only supports methods with a return value."); + } + + invoker = CompileStaticMethodInvoker(methodInfo); + CACHED_STATIC_METHOD_INVOKERS.TryAdd(methodInfo, invoker); + return invoker; + } + + /// + /// Compiles the method invoker expression for a static method and compiles the resultant lambda. /// /// The method to create an expression for. /// MethodInvoker. - private static MethodInvoker CreateMethodInvoker(MethodInfo methodInfo) + private static StaticMethodInvoker CompileStaticMethodInvoker(MethodInfo methodInfo) + { + Validation.ThrowIfNull(methodInfo, nameof(methodInfo)); + + // ------------------------------------------- + // Function call parameters + // ------------------------------------------- + // e.g. + // object[] paramSet = new object[]; + // "var returnValue = invoke(paramSet); + // + // create a single param of type object[] tha will hold the variable length of method arguments being passed in + ParameterExpression inputArguments = Expression.Parameter(typeof(object[]), "args"); + + // ------------------------------------------- + // method body + // ------------------------------------------- + + // invocation parameters to pass to the method + ParameterInfo[] paramsInfo = methodInfo.GetParameters(); + + // a set of expressions that represents + // a casting of each supplied object to its specific type for invocation + Expression[] argsAssignments = new Expression[paramsInfo.Length]; + + // pick each arg from the supplied method parameters and create a expression + // that casts them into the required type for the parameter position on the method + for (var i = 0; i < paramsInfo.Length; i++) + { + Expression index = Expression.Constant(i); + Type paramType = paramsInfo[i].ParameterType; + Expression paramAccessorExp = Expression.ArrayIndex(inputArguments, index); + if (paramType.IsValueType) + argsAssignments[i] = Expression.Unbox(paramAccessorExp, paramType); + else + argsAssignments[i] = Expression.Convert(paramAccessorExp, paramType); + } + + // a direct call to the method on the invokable object with the supplied parameters + var methodCall = Expression.Call(methodInfo, argsAssignments); + + // Execute the method call and assign its output to returnedVariable + var returnedVariable = Expression.Variable(methodInfo.ReturnType); + var methodCallResultAssigned = Expression.Assign(returnedVariable, methodCall); + + // box the method result into an object + var boxedResult = Expression.Variable(typeof(object)); + var boxedResultAssignment = Expression.Assign(boxedResult, Expression.Convert(returnedVariable, typeof(object))); + + // assembly the method body + var methodBody = Expression.Block( + new ParameterExpression[] { returnedVariable, boxedResult }, + methodCallResultAssigned, + boxedResultAssignment, + boxedResult); + + // Create lambda expression that accepts the "this" parameter and the args from the user. + var lambda = Expression.Lambda(methodBody, new ParameterExpression[] { inputArguments }); + return lambda.Compile(); + } + + /// + /// Compiles the method invoker expression for an instance based method and compiles the resultant lambda. + /// + /// The method to create an expression for. + /// InstanceMethodInvoker. + private static InstanceMethodInvoker CompileInstanceMethodInvoker(MethodInfo methodInfo) { Validation.ThrowIfNull(methodInfo, nameof(methodInfo)); @@ -224,7 +324,7 @@ private static MethodInvoker CreateMethodInvoker(MethodInfo methodInfo) // ------------------------------------------- // cast the input object to its required Type var castedObjectToInvokeOn = Expression.Variable(declaredType); - var castOperation = Expression.Assign(castedObjectToInvokeOn, Expression.Convert(objectToInvokeOn, declaredType)); + var castingOperation = Expression.Assign(castedObjectToInvokeOn, Expression.Convert(objectToInvokeOn, declaredType)); // invocation parameters to pass to the method ParameterInfo[] paramsInfo = methodInfo.GetParameters(); @@ -265,14 +365,14 @@ private static MethodInvoker CreateMethodInvoker(MethodInfo methodInfo) var methodBody = Expression.Block( new ParameterExpression[] { castedObjectToInvokeOn, returnedVariable, boxedResult }, - castOperation, + castingOperation, methodCallResultAssigned, boxedResultAssignment, reassignReffedValue, boxedResult); // Create lambda expression that accepts the "this" parameter and the args from the user. - var lambda = Expression.Lambda(methodBody, new ParameterExpression[] { objectToInvokeOn, inputArguments }); + var lambda = Expression.Lambda(methodBody, new ParameterExpression[] { objectToInvokeOn, inputArguments }); return lambda.Compile(); } @@ -304,7 +404,7 @@ public static object CreateInstance(Type type, params object[] args) // in combination with Roger Johanson: https://rogerjohansson.blog/2008/02/28/linq-expressions-creating-objects/ . // ----------------------------- if (args == null) - return CreateInstance(type); + return CreateInstance(type, new object[0]); if ((args.Length > 3) || (args.Length > 0 && args[0] == null) @@ -458,19 +558,26 @@ private static IReadOnlyList CreateConstructorTypeList(params Type[] types /// Gets the creators cached to this application instance. /// /// The cached creators. - public static IReadOnlyDictionary, ObjectActivator> ObjectCreators => CACHED_OBJECT_CREATORS; + public static IReadOnlyDictionary, ObjectActivator> ObjectCreators => CACHED_OBJECT_CREATORS; + + /// + /// Gets the collection of cached method invokers for fast invocation of methods on target + /// objects in this app instance. + /// + /// The method invokers. + public static IReadOnlyDictionary InstanceMethodInvokers => CACHED_INSTANCE_METHOD_INVOKERS; /// - /// Gets the collection of cached method invokers for fast invocation in this app instance. + /// Gets the collection of cached static method invokers for fast invocation in this app instance. /// /// The method invokers. - public static IReadOnlyDictionary MethodInvokers => CACHED_METHOD_INVOKERS; + public static IReadOnlyDictionary StaticMethodInvokers => CACHED_STATIC_METHOD_INVOKERS; /// /// Gets the collection of cached property setter invokers for fast invocation in this app instance. /// /// The cached collection of property "setter" invokers. - public static IReadOnlyDictionary PropertySetterInvokers => CACHED_PROPERTY_SETTER_INVOKERS; + public static IReadOnlyDictionary PropertySetterInvokers => CACHED_PROPERTY_SETTER_INVOKERS; /// /// Gets the collection of cached property getter invokers for fast invocation in this app instance. diff --git a/src/graphql-aspnet/Common/Generics/MethodInvoker.cs b/src/graphql-aspnet/Common/Generics/MethodInvoker.cs index 6a2ae172c..61a0c1cdc 100644 --- a/src/graphql-aspnet/Common/Generics/MethodInvoker.cs +++ b/src/graphql-aspnet/Common/Generics/MethodInvoker.cs @@ -6,13 +6,22 @@ // -- // License: MIT // ************************************************************* + namespace GraphQL.AspNet.Common.Generics { /// /// A representation of a compiled lamda to invoke a method on an instance of an object. /// - /// The object instance to invoke on. + /// The object instance to invoke the delegated method on. /// The parameters to pass the method call. /// The result of the call. - internal delegate object MethodInvoker(ref object instanceToInvokeOn, params object[] methodParameters); + internal delegate object InstanceMethodInvoker(ref object instanceToInvokeOn, params object[] methodParameters); + + /// + /// A representation of a compiled lamda to invoke a static method not attached to a specific + /// object instance. + /// + /// The parameters to pass the method call. + /// The result of the call. + internal delegate object StaticMethodInvoker(params object[] methodParameters); } \ No newline at end of file diff --git a/src/graphql-aspnet/Common/JsonNodes/JsonNodeException.cs b/src/graphql-aspnet/Common/JsonNodes/JsonNodeException.cs index 94edc1fe3..25046d727 100644 --- a/src/graphql-aspnet/Common/JsonNodes/JsonNodeException.cs +++ b/src/graphql-aspnet/Common/JsonNodes/JsonNodeException.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Common.Extensions +namespace GraphQL.AspNet.Common.JsonNodes { using System; using System.Text.Json.Nodes; diff --git a/src/graphql-aspnet/Common/JsonNodes/JsonNodeExtensions.cs b/src/graphql-aspnet/Common/JsonNodes/JsonNodeExtensions.cs index bc0d74b20..be7adf724 100644 --- a/src/graphql-aspnet/Common/JsonNodes/JsonNodeExtensions.cs +++ b/src/graphql-aspnet/Common/JsonNodes/JsonNodeExtensions.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Common.Extensions +namespace GraphQL.AspNet.Common.JsonNodes { using System; using System.Collections.Generic; diff --git a/src/graphql-aspnet/Common/TypeCollection.cs b/src/graphql-aspnet/Common/TypeCollection.cs deleted file mode 100644 index e2cf34b5d..000000000 --- a/src/graphql-aspnet/Common/TypeCollection.cs +++ /dev/null @@ -1,96 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Common -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Collections.Immutable; - - /// - /// A collection of unique types. This collection is guaranteed to contain unique items - /// and is read-only once created. - /// - public class TypeCollection : IEnumerable - { - /// - /// Gets a collection representing no additional types. - /// - /// The none. - public static TypeCollection Empty { get; } - - /// - /// Initializes static members of the class. - /// - static TypeCollection() - { - Empty = new TypeCollection(); - } - - /// - /// Prevents a default instance of the class from being created. - /// - private TypeCollection() - { - this.TypeSet = ImmutableHashSet.Create(); - } - - /// - /// Initializes a new instance of the class. - /// - /// The types. - public TypeCollection(params Type[] types) - : this() - { - if (types != null) - this.TypeSet = ImmutableHashSet.Create(types); - } - - /// - /// Determines whether this instance contains the specified type. - /// - /// The type. - /// true if the type is found; otherwise, false. - public bool Contains(Type type) - { - return this.TypeSet.Contains(type); - } - - /// - /// Gets the count of types in this set. - /// - /// The count of types. - public int Count => this.TypeSet.Count; - - /// - /// Gets the set of s that represent the scalar. - /// - /// The type set. - public IImmutableSet TypeSet { get; } - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// An enumerator that can be used to iterate through the collection. - public IEnumerator GetEnumerator() - { - return this.TypeSet.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// An object that can be used to iterate through the collection. - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/DirectiveBindingConfiguration.cs b/src/graphql-aspnet/Configuration/DirectiveBindingSchemaExtension.cs similarity index 88% rename from src/graphql-aspnet/Configuration/DirectiveBindingConfiguration.cs rename to src/graphql-aspnet/Configuration/DirectiveBindingSchemaExtension.cs index a52d1724b..dcd2786ac 100644 --- a/src/graphql-aspnet/Configuration/DirectiveBindingConfiguration.cs +++ b/src/graphql-aspnet/Configuration/DirectiveBindingSchemaExtension.cs @@ -21,7 +21,7 @@ namespace GraphQL.AspNet.Configuration /// A configuration class used to apply a late-bound directive to a set of schema items /// matching a filter. /// - public sealed class DirectiveBindingConfiguration : ISchemaConfigurationExtension + public sealed class DirectiveBindingSchemaExtension : IGraphQLServerExtension { // a set of default filters applied to any directive applicator unless explicitly removed // by the developer. Used to auto filter items down to those reasonably assumed @@ -30,9 +30,9 @@ public sealed class DirectiveBindingConfiguration : ISchemaConfigurationExtensio private static IReadOnlyList> _defaultFilters; /// - /// Initializes static members of the class. + /// Initializes static members of the class. /// - static DirectiveBindingConfiguration() + static DirectiveBindingSchemaExtension() { var list = new List>(4); @@ -53,10 +53,10 @@ static DirectiveBindingConfiguration() private List> _customFilters; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Type of the directive being applied in this instnace. - public DirectiveBindingConfiguration(Type directiveType) + public DirectiveBindingSchemaExtension(Type directiveType) : this() { _directiveType = Validation.ThrowIfNullOrReturn(directiveType, nameof(directiveType)); @@ -64,24 +64,24 @@ public DirectiveBindingConfiguration(Type directiveType) } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Name of the directive as it is declared in the schema /// where it is being applied. - public DirectiveBindingConfiguration(string directiveName) + public DirectiveBindingSchemaExtension(string directiveName) : this() { _directiveName = Validation.ThrowIfNullWhiteSpaceOrReturn(directiveName, nameof(directiveName)); } - private DirectiveBindingConfiguration() + private DirectiveBindingSchemaExtension() { _customFilters = new List>(); this.WithArguments(); } /// - void ISchemaConfigurationExtension.Configure(ISchema schema) + void IGraphQLServerExtension.EnsureSchema(ISchema schema) { var allFilters = _defaultFilters.Concat(_customFilters).ToList(); foreach (var schemaItem in schema.AllSchemaItems(includeDirectives: false)) @@ -125,7 +125,7 @@ void ISchemaConfigurationExtension.Configure(ISchema schema) /// The arguments to supply to the directive when its /// executed. /// IDirectiveInjector. - public DirectiveBindingConfiguration WithArguments(params object[] arguments) + public DirectiveBindingSchemaExtension WithArguments(params object[] arguments) { arguments = arguments ?? new object[0]; _argumentFunction = x => arguments; @@ -141,7 +141,7 @@ public DirectiveBindingConfiguration WithArguments(params object[] arguments) /// A function that will be used to /// create a new unique set of arguments per schema item the directive is applied to. /// IDirectiveInjector. - public DirectiveBindingConfiguration WithArguments(Func argsCreator) + public DirectiveBindingSchemaExtension WithArguments(Func argsCreator) { Validation.ThrowIfNull(argsCreator, nameof(argsCreator)); _argumentFunction = argsCreator; @@ -158,7 +158,7 @@ public DirectiveBindingConfiguration WithArguments(Func a /// /// The item filter. /// DirectiveApplicator. - public DirectiveBindingConfiguration ToItems(Func itemFilter) + public DirectiveBindingSchemaExtension ToItems(Func itemFilter) { Validation.ThrowIfNull(itemFilter, nameof(itemFilter)); _customFilters.Add(itemFilter); @@ -169,7 +169,7 @@ public DirectiveBindingConfiguration ToItems(Func itemFilter) /// Clears this instance of any directive arguments and filter criteria. /// /// DirectiveApplicator. - public DirectiveBindingConfiguration Clear() + public DirectiveBindingSchemaExtension Clear() { _customFilters.Clear(); this.WithArguments(); diff --git a/src/graphql-aspnet/Configuration/Formatting/GraphNameFormatter.cs b/src/graphql-aspnet/Configuration/Formatting/GraphNameFormatter.cs index 6311ea0b9..50eb9537c 100644 --- a/src/graphql-aspnet/Configuration/Formatting/GraphNameFormatter.cs +++ b/src/graphql-aspnet/Configuration/Formatting/GraphNameFormatter.cs @@ -11,6 +11,7 @@ namespace GraphQL.AspNet.Configuration.Formatting { using System.Diagnostics; using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; /// /// A formatter class capable of altering a graph item name before its added to a schema. @@ -87,10 +88,6 @@ public virtual string FormatEnumValueName(string name) /// System.String. public virtual string FormatGraphTypeName(string name) { - // scalar names are considered fixed constants and can't be changed - if (name == null || GraphQLProviders.ScalarProvider.IsScalar(name)) - return name; - return this.FormatName(name, _typeNameStrategy); } diff --git a/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Directives.cs b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Directives.cs new file mode 100644 index 000000000..7ab773e64 --- /dev/null +++ b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Directives.cs @@ -0,0 +1,209 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Execution.Parsing.Lexing.Tokens; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions; + using GraphQL.AspNet.Schemas.TypeSystem; + using Microsoft.AspNetCore.Authorization; + + /// + /// Extension methods for configuring minimal API methods as fields on the graph. + /// + public static partial class GraphQLRuntimeSchemaItemDefinitionExtensions + { + /// + /// Adds policy-based authorization requirements to the directive. + /// + /// + /// This is similar to adding the to a controller method + /// + /// The directive being built. + /// The name of the policy to assign via this requirement. + /// A comma-seperated list of roles to assign via this requirement. + /// IGraphQLRuntimeDirectiveDefinition. + public static IGraphQLRuntimeDirectiveDefinition RequireAuthorization( + this IGraphQLRuntimeDirectiveDefinition directiveTemplate, + string policyName = null, + string roles = null) + { + Validation.ThrowIfNull(directiveTemplate, nameof(directiveTemplate)); + + return RequireAuthorizationInternal(directiveTemplate, policyName, roles); + } + + /// + /// Indicates that the directive should allow anonymous access. + /// + /// + /// + /// This is similar to adding the to a controller method + /// + /// + /// Any inherited authorization permissions from field groups are automatically + /// dropped from this field instance. + /// + /// + /// The directive being built. + /// IGraphQLRuntimeDirectiveDefinition. + public static IGraphQLRuntimeDirectiveDefinition AllowAnonymous(this IGraphQLRuntimeDirectiveDefinition directiveTemplate) + { + Validation.ThrowIfNull(directiveTemplate, nameof(directiveTemplate)); + return AllowAnonymousInternal(directiveTemplate); + } + + /// + /// Marks this directive as being repeatable such that it can be applied to a single + /// schema item more than once. + /// + /// The directive template. + /// IGraphQLRuntimeDirectiveDefinition. + public static IGraphQLRuntimeDirectiveDefinition IsRepeatable(this IGraphQLRuntimeDirectiveDefinition directiveTemplate) + { + Validation.ThrowIfNull(directiveTemplate, nameof(directiveTemplate)); + if (directiveTemplate.Attributes.Count(x => x is RepeatableAttribute) == 0) + { + var repeatable = new RepeatableAttribute(); + directiveTemplate.AddAttribute(repeatable); + } + + return directiveTemplate; + } + + /// + /// Restricts the locations that this directive can be applied. + /// + /// + /// If called more than once this method acts as an additive restrictor. Each additional + /// call will add more location restrictions. Duplicate restrictions are ignored. + /// + /// The directive template to alter. + /// The bitwise set of locations where this + /// directive can be applied. + /// IGraphQLRuntimeDirectiveDefinition. + public static IGraphQLRuntimeDirectiveDefinition RestrictLocations( + this IGraphQLRuntimeDirectiveDefinition directiveTemplate, + DirectiveLocation locations) + { + Validation.ThrowIfNull(directiveTemplate, nameof(directiveTemplate)); + var restrictions = new DirectiveLocationsAttribute(locations); + directiveTemplate.AddAttribute(restrictions); + + return directiveTemplate; + } + + /// + /// Sets the resolver to be used when this directive is requested at runtime. + /// + /// + /// + /// If this method is called more than once, the previously set resolver will be replaced. + /// + /// + /// Directive resolver methods must return a . + /// + /// + /// The directive being built. + /// The delegate to assign as the resolver. This method will be + /// parsed to determine input arguments for the field on the target schema. + /// IGraphQLRuntimeDirectiveDefinition. + public static IGraphQLRuntimeDirectiveDefinition AddResolver(this IGraphQLRuntimeDirectiveDefinition directiveTemplate, Delegate resolverMethod) + { + return AddResolverInternal(directiveTemplate, null, null, resolverMethod); + } + + /// + /// Assigns a custom value to the internal name of this directive. This value will be used in error + /// messages and log entries instead of an anonymous method name. This can significantly increase readability + /// while trying to debug an issue. + /// + /// + /// This value does NOT affect the directive name as it would appear in a schema. It only effects the internal + /// name used in log messages and exception text. + /// + /// The directive being built. + /// The value to use as the internal name for this field definition when its + /// added to the schema. + /// IGraphQLRuntimeDirectiveDefinition. + public static IGraphQLRuntimeDirectiveDefinition WithInternalName(this IGraphQLRuntimeDirectiveDefinition directiveTemplate, string internalName) + { + Validation.ThrowIfNull(directiveTemplate, nameof(directiveTemplate)); + directiveTemplate.InternalName = internalName; + return directiveTemplate; + } + + /// + /// Maps a new directive into the target schema. + /// + /// The builder representing the schema being constructed. + /// Name of the directive (e.g. '@myDirective'). + /// IGraphQLRuntimeDirectiveDefinition. + public static IGraphQLRuntimeDirectiveDefinition MapDirective(this ISchemaBuilder schemaBuilder, string directiveName) + { + Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder)); + return MapDirective(schemaBuilder.Options, directiveName, null as Delegate); + } + + /// + /// Maps a new directive into the target schema. + /// + /// The builder representing the schema being constructed. + /// Name of the directive (e.g. '@myDirective'). + /// The resolver that will be executed when the directive is invoked. + /// IGraphQLDirectiveTemplate. + public static IGraphQLRuntimeDirectiveDefinition MapDirective(this ISchemaBuilder schemaBuilder, string directiveName, Delegate resolverMethod) + { + Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder)); + return MapDirective(schemaBuilder.Options, directiveName, resolverMethod); + } + + /// + /// Maps a new directive into the target schema. + /// + /// The schema options where the directive will be created. + /// Name of the directive (e.g. '@myDirective'). + /// IGraphQLRuntimeDirectiveDefinition. + public static IGraphQLRuntimeDirectiveDefinition MapDirective(this SchemaOptions schemaOptions, string directiveName) + { + return MapDirective(schemaOptions, directiveName, null as Delegate); + } + + /// + /// Maps a new directive into the target schema. + /// + /// The schema options where the directive will be created. + /// Name of the directive (e.g. '@myDirective'). + /// The resolver that will be executed when the directive is invoked. + /// IGraphQLRuntimeDirectiveDefinition. + public static IGraphQLRuntimeDirectiveDefinition MapDirective(this SchemaOptions schemaOptions, string directiveName, Delegate resolverMethod) + { + Validation.ThrowIfNull(schemaOptions, nameof(schemaOptions)); + + while (directiveName != null && directiveName.StartsWith(TokenTypeNames.STRING_AT_SYMBOL)) + directiveName = directiveName.Substring(1); + + var directive = new RuntimeDirectiveActionDefinition(schemaOptions, directiveName); + schemaOptions.AddRuntimeSchemaItem(directive); + + if (resolverMethod != null) + directive.AddResolver(resolverMethod); + + return directive; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_FieldGroups.cs b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_FieldGroups.cs new file mode 100644 index 000000000..78a0b16b3 --- /dev/null +++ b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_FieldGroups.cs @@ -0,0 +1,154 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration +{ + using System; + using System.Linq; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using Microsoft.AspNetCore.Authorization; + + /// + /// Extension methods for configuring minimal API methods as fields on the graph. + /// + public static partial class GraphQLRuntimeSchemaItemDefinitionExtensions + { + /// + /// Adds policy-based authorization requirements to the field. + /// + /// + /// This is similar to adding the to a controller method. Subsequent calls to this + /// method will cause more authorization restrictions to be added to the field. + /// + /// The field being built. + /// The name of the policy to assign via this requirement. + /// A comma-seperated list of roles to assign via this requirement. + /// IGraphQLFieldBuilder. + public static IGraphQLRuntimeFieldGroupDefinition RequireAuthorization( + this IGraphQLRuntimeFieldGroupDefinition fieldBuilder, + string policyName = null, + string roles = null) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + + var attrib = new AuthorizeAttribute(); + attrib.Policy = policyName?.Trim(); + attrib.Roles = roles?.Trim(); + fieldBuilder.AddAttribute(attrib); + return fieldBuilder; + } + + /// + /// Indicates that the field should allow anonymous access. + /// + /// + /// + /// This is similar to adding the to a controller method + /// + /// + /// Any inherited authorization permissions from field groups are automatically + /// dropped from this field instance. + /// + /// + /// The field being built. + /// IGraphQLFieldBuilder. + public static IGraphQLRuntimeFieldGroupDefinition AllowAnonymous(this IGraphQLRuntimeFieldGroupDefinition fieldBuilder) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + if (!fieldBuilder.Attributes.OfType().Any()) + fieldBuilder.AddAttribute(new AllowAnonymousAttribute()); + + return fieldBuilder; + } + + /// + /// Maps a terminal child field into the schema and assigns the resolver method to it. + /// + /// The field under which this new field will be nested. + /// The template pattern to be appended to the supplied . + /// IGraphQLResolvedFieldBuilder. + public static IGraphQLRuntimeResolvedFieldDefinition MapField(this IGraphQLRuntimeFieldGroupDefinition field, string subTemplate) + { + return MapField( + field, + subTemplate, + null as Type, // expectedReturnType + null, // unionName + null as Delegate); + } + + /// + /// Maps a terminal child field into the schema and assigns the resolver method to it. + /// + /// The field under which this new field will be nested. + /// The template pattern to be appended to the supplied . + /// The resolver method to be called when this field is requested. + /// IGraphQLResolvedFieldBuilder. + public static IGraphQLRuntimeResolvedFieldDefinition MapField(this IGraphQLRuntimeFieldGroupDefinition field, string subTemplate, Delegate resolverMethod) + { + return MapField( + field, + subTemplate, + null as Type, // expectedReturnType + null, // unionName + resolverMethod); + } + + /// + /// Maps a terminal child field into the schema and assigns the resolver method to it. + /// + /// The expected, primary return type of the field. Must be provided + /// if the supplied delegate returns an . + /// The field under which this new field will be nested. + /// The template pattern to be appended to the supplied . + /// The resolver method to be called when this field is requested. + /// IGraphQLResolvedFieldBuilder. + public static IGraphQLRuntimeResolvedFieldDefinition MapField(this IGraphQLRuntimeFieldGroupDefinition field, string subTemplate, Delegate resolverMethod) + { + return MapField( + field, + subTemplate, + typeof(TReturnType), // expectedReturnType + null, // unionName + resolverMethod); + } + + /// + /// Maps a terminal child field into the schema and assigns the resolver method to it. + /// + /// The field under which this new field will be nested. + /// The template pattern to be appended to the supplied . + /// Provide a name and this field will be declared to return a union. Use to declare union members. + /// The resolver method to be called when this field is requested. + /// IGraphQLResolvedFieldBuilder. + public static IGraphQLRuntimeResolvedFieldDefinition MapField(this IGraphQLRuntimeFieldGroupDefinition field, string subTemplate, string unionName, Delegate resolverMethod) + { + return MapField( + field, + subTemplate, + null as Type, // expectedReturnType + unionName, // unionName + resolverMethod); + } + + private static IGraphQLRuntimeResolvedFieldDefinition MapField( + IGraphQLRuntimeFieldGroupDefinition field, + string subTemplate, + Type expectedReturnType, + string unionName, + Delegate resolverMethod) + { + var subField = field.MapField(subTemplate); + AddResolverInternal(subField, expectedReturnType, unionName, resolverMethod); + return subField; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Internals.cs b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Internals.cs new file mode 100644 index 000000000..43264c1e1 --- /dev/null +++ b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Internals.cs @@ -0,0 +1,129 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration +{ + using System; + using System.Linq; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions; + using GraphQL.AspNet.Schemas.TypeSystem; + using Microsoft.AspNetCore.Authorization; + + /// + /// Extension methods for configuring minimal API methods as fields on the graph. + /// + public static partial class GraphQLRuntimeSchemaItemDefinitionExtensions + { + private static TItemType RequireAuthorizationInternal( + this TItemType schemaItem, + string policyName = null, + string roles = null) + where TItemType : IGraphQLRuntimeSchemaItemDefinition + { + Validation.ThrowIfNull(schemaItem, nameof(schemaItem)); + + var attrib = new AuthorizeAttribute(); + attrib.Policy = policyName?.Trim(); + attrib.Roles = roles?.Trim(); + schemaItem.AddAttribute(attrib); + + // remove any allow anon attriubtes + var allowAnonAttribs = schemaItem.Attributes.OfType().ToList(); + foreach (var anonAttrib in allowAnonAttribs) + schemaItem.RemoveAttribute(anonAttrib); + + return schemaItem; + } + + private static TItemType AllowAnonymousInternal(TItemType schemaItem) + where TItemType : IGraphQLRuntimeSchemaItemDefinition + { + Validation.ThrowIfNull(schemaItem, nameof(schemaItem)); + if (schemaItem.Attributes.Count(x => x is AllowAnonymousAttribute) == 0) + { + schemaItem.AddAttribute(new AllowAnonymousAttribute()); + } + + // remove any authorize attributes + var authAttribs = schemaItem.Attributes.OfType().ToList(); + foreach (var attib in authAttribs) + schemaItem.RemoveAttribute(attib); + + return schemaItem; + } + + private static TItemType ClearPossibleTypesInternal(TItemType fieldBuilder) + where TItemType : IGraphQLRuntimeSchemaItemDefinition + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + var attributes = fieldBuilder.Attributes.OfType().ToList(); + foreach (var att in attributes) + fieldBuilder.RemoveAttribute(att); + + return fieldBuilder; + } + + private static TItemType AddPossibleTypesInternal(TItemType fieldBuilder, Type firstPossibleType, params Type[] additionalPossibleTypes) + where TItemType : IGraphQLRuntimeSchemaItemDefinition + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + var possibleTypes = new PossibleTypesAttribute(firstPossibleType, additionalPossibleTypes); + fieldBuilder.AddAttribute(possibleTypes); + return fieldBuilder; + } + + private static IGraphQLRuntimeFieldGroupDefinition MapGraphQLFieldInternal( + SchemaOptions schemaOptions, + GraphOperationType operationType, + string pathTemplate) + { + schemaOptions = Validation.ThrowIfNullOrReturn(schemaOptions, nameof(schemaOptions)); + pathTemplate = Validation.ThrowIfNullWhiteSpaceOrReturn(pathTemplate, nameof(pathTemplate)); + + var fieldTemplate = new RuntimeFieldGroupTemplate( + schemaOptions, + (SchemaItemCollections)operationType, + pathTemplate); + + return fieldTemplate; + } + + private static TItemType AddResolverInternal(this TItemType fieldBuilder, Type expectedReturnType, string unionName, Delegate resolverMethod) + where TItemType : IGraphQLResolvableSchemaItemDefinition + { + fieldBuilder.Resolver = resolverMethod; + fieldBuilder.ReturnType = expectedReturnType; + + // since the resolver was declared as non-union, remove any potential union setup that might have + // existed via a previous call. if TReturnType is a union proxy it will be + // picked up automatically during templating + var unionAttrib = fieldBuilder.Attributes.OfType().SingleOrDefault(); + if (string.IsNullOrEmpty(unionName)) + { + if (unionAttrib != null) + fieldBuilder.RemoveAttribute(unionAttrib); + } + else if (unionAttrib != null) + { + unionAttrib.UnionName = unionName?.Trim(); + unionAttrib.UnionMemberTypes.Clear(); + } + else + { + fieldBuilder.AddAttribute(new UnionAttribute(unionName.Trim())); + } + + return fieldBuilder; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Mutations.cs b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Mutations.cs new file mode 100644 index 000000000..4e24c694b --- /dev/null +++ b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Mutations.cs @@ -0,0 +1,174 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration +{ + using System; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// Extension methods for configuring minimal API methods as fields on the graph. + /// + public static partial class GraphQLRuntimeSchemaItemDefinitionExtensions + { + /// + /// Begins a new field group for the mutation schema object. All fields created using this group will be nested underneath it and inherit any set parameters such as authorization requirements. + /// + /// The builder to append the mutation group to. + /// The template path for this group. + /// IGraphQLRuntimeFieldDefinition. + public static IGraphQLRuntimeFieldGroupDefinition MapMutationGroup(this ISchemaBuilder schemaBuilder, string template) + { + return MapMutationGroup(schemaBuilder?.Options, template); + } + + /// + /// Begins a new field group for the mutation schema object. All fields created using this group will be nested underneath it and inherit any set parameters such as authorization requirements. + /// + /// The schema options to append the mutation group to. + /// The template path for this group. + /// IGraphQLRuntimeFieldDefinition. + public static IGraphQLRuntimeFieldGroupDefinition MapMutationGroup(this SchemaOptions schemaOptions, string template) + { + return new RuntimeFieldGroupTemplate(schemaOptions, SchemaItemCollections.Mutation, template); + } + + /// + /// Creates a new, explicitly resolvable field in the mutation root object with the given path. This field cannot be + /// further extended or nested with other fields via the Mapping API. + /// + /// The builder representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// IGraphQLFieldTemplate. + public static IGraphQLRuntimeResolvedFieldDefinition MapMutation(this ISchemaBuilder schemaBuilder, string template) + { + Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder)); + + return MapMutation( + schemaBuilder.Options, + template, + null, // unionName + null as Delegate); + } + + /// + /// Creates a new field in the mutation object with the given path. This field can act as a + /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself. + /// + /// The builder representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// The resolver method to execute when this + /// field is requested. + /// IGraphQLFieldTemplate. + public static IGraphQLRuntimeResolvedFieldDefinition MapMutation(this ISchemaBuilder schemaBuilder, string template, Delegate resolverMethod) + { + Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder)); + return MapMutation( + schemaBuilder.Options, + template, + null, // unionName + resolverMethod); + } + + /// + /// Creates a new field in the mutation object with the given path. This field can act as a + /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself. + /// + /// The builder representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// Provide a name and this field will be declared to return a union. Use to declare union members. + /// The resolver method to execute when this + /// field is requested. + /// IGraphQLFieldTemplate. + public static IGraphQLRuntimeResolvedFieldDefinition MapMutation(this ISchemaBuilder schemaBuilder, string template, string unionName, Delegate resolverMethod) + { + Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder)); + + return MapMutation( + schemaBuilder.Options, + template, + unionName, + resolverMethod); + } + + /// + /// Creates a new field in the mutation root object with the given path. This field can act as a + /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself. + /// + /// The options representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// IGraphQLFieldTemplate. + public static IGraphQLRuntimeResolvedFieldDefinition MapMutation(this SchemaOptions schemaOptions, string template) + { + return MapMutation( + schemaOptions, + template, + null, // unionName + null as Delegate); + } + + /// + /// Creates a new, explicitly resolvable field in the Mutation root object with the given path. This field cannot be + /// further extended or nested with other fields via the Mapping API. + /// + /// The options representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// The resolver method to execute when + /// this field is requested by a caller. + /// IGraphQLResolvedFieldTemplate. + public static IGraphQLRuntimeResolvedFieldDefinition MapMutation(this SchemaOptions schemaOptions, string template, Delegate resolverMethod) + { + return MapMutation( + schemaOptions, + template, + null, // unionName + resolverMethod); + } + + /// + /// Creates a new, explicitly resolvable field in the Mutation root object with the given path. This field cannot be + /// further extended or nested with other fields via the Mapping API. + /// + /// The options representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// Provide a name and this field will be declared to return a union. Use to declare union members. + /// The resolver method to execute when + /// this field is requested by a caller. + /// IGraphQLResolvedFieldTemplate. + public static IGraphQLRuntimeResolvedFieldDefinition MapMutation(this SchemaOptions schemaOptions, string template, string unionName, Delegate resolverMethod) + { + Validation.ThrowIfNull(schemaOptions, nameof(schemaOptions)); + + var field = MapGraphQLFieldInternal( + schemaOptions, + GraphOperationType.Mutation, + template); + + var resolvedField = RuntimeResolvedFieldDefinition.FromFieldTemplate(field); + schemaOptions.AddRuntimeSchemaItem(resolvedField); + + if (!string.IsNullOrWhiteSpace(unionName)) + resolvedField.AddAttribute(new UnionAttribute(unionName.Trim())); + + return resolvedField.AddResolver(unionName, resolverMethod); + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Queries.cs b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Queries.cs new file mode 100644 index 000000000..af02bcd82 --- /dev/null +++ b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_Queries.cs @@ -0,0 +1,176 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration +{ + using System; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// Extension methods for configuring minimal API methods as fields on the graph. + /// + public static partial class GraphQLRuntimeSchemaItemDefinitionExtensions + { + /// + /// Begins a new field group for the query schema object. All fields created using + /// this group will be nested underneath it and inherit any set parameters such as authorization requirements. + /// + /// The builder to append the query group to. + /// The template path for this group. + /// IGraphQLRuntimeFieldDefinition. + public static IGraphQLRuntimeFieldGroupDefinition MapQueryGroup(this ISchemaBuilder schemaBuilder, string template) + { + return MapQueryGroup(schemaBuilder?.Options, template); + } + + /// + /// Begins a new field group for the query schema object. All fields created using + /// this group will be nested underneath it and inherit any set parameters such as authorization requirements. + /// + /// The schema options to append the query group to. + /// The template path for this group. + /// IGraphQLRuntimeFieldDefinition. + public static IGraphQLRuntimeFieldGroupDefinition MapQueryGroup(this SchemaOptions schemaOptions, string template) + { + return new RuntimeFieldGroupTemplate(schemaOptions, SchemaItemCollections.Query, template); + } + + /// + /// Creates a new, explicitly resolvable field in the query root object with the given path. This field cannot be + /// further extended or nested with other fields via the Mapping API. + /// + /// The builder representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// IGraphQLFieldTemplate. + public static IGraphQLRuntimeResolvedFieldDefinition MapQuery(this ISchemaBuilder schemaBuilder, string template) + { + Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder)); + + return MapQuery( + schemaBuilder.Options, + template, + null, // unionName + null as Delegate); + } + + /// + /// Creates a new field in the query object with the given path. This field can act as a + /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself. + /// + /// The builder representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// The resolver method to execute when this + /// field is requested. + /// IGraphQLFieldTemplate. + public static IGraphQLRuntimeResolvedFieldDefinition MapQuery(this ISchemaBuilder schemaBuilder, string template, Delegate resolverMethod) + { + Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder)); + return MapQuery( + schemaBuilder.Options, + template, + null, // unionName + resolverMethod); + } + + /// + /// Creates a new field in the query object with the given path. This field can act as a + /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself. + /// + /// The builder representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// Provide a name and this field will be declared to return a union. Use to declare union members. + /// The resolver method to execute when this + /// field is requested. + /// IGraphQLFieldTemplate. + public static IGraphQLRuntimeResolvedFieldDefinition MapQuery(this ISchemaBuilder schemaBuilder, string template, string unionName, Delegate resolverMethod) + { + Validation.ThrowIfNull(schemaBuilder, nameof(schemaBuilder)); + + return MapQuery( + schemaBuilder.Options, + template, + unionName, + resolverMethod); + } + + /// + /// Creates a new field in the query root object with the given path. This field can act as a + /// grouping field of other resolvable fields or be converted to an explicitly resolvable field itself. + /// + /// The options representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// IGraphQLFieldTemplate. + public static IGraphQLRuntimeResolvedFieldDefinition MapQuery(this SchemaOptions schemaOptions, string template) + { + return MapQuery( + schemaOptions, + template, + null, // unionMethod + null as Delegate); + } + + /// + /// Creates a new, explicitly resolvable field in the query root object with the given path. This field cannot be + /// further extended or nested with other fields via the Mapping API. + /// + /// The options representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// The resolver method to execute when + /// this field is requested by a caller. + /// IGraphQLResolvedFieldTemplate. + public static IGraphQLRuntimeResolvedFieldDefinition MapQuery(this SchemaOptions schemaOptions, string template, Delegate resolverMethod) + { + return MapQuery( + schemaOptions, + template, + null, // unionMethod + resolverMethod); + } + + /// + /// Creates a new, explicitly resolvable field in the query root object with the given path. This field cannot be + /// further extended or nested with other fields via the Mapping API. + /// + /// The options representing the schema where this field + /// will be created. + /// The template path string for his field. (e.g. /path1/path2/path3) + /// Provide a name and this field will be declared to return a union. Use to declare union members. + /// The resolver method to execute when + /// this field is requested by a caller. + /// IGraphQLResolvedFieldTemplate. + public static IGraphQLRuntimeResolvedFieldDefinition MapQuery(this SchemaOptions schemaOptions, string template, string unionName, Delegate resolverMethod) + { + Validation.ThrowIfNull(schemaOptions, nameof(schemaOptions)); + + var field = MapGraphQLFieldInternal( + schemaOptions, + GraphOperationType.Query, + template); + + var resolvedField = RuntimeResolvedFieldDefinition.FromFieldTemplate(field); + schemaOptions.AddRuntimeSchemaItem(resolvedField); + + if (!string.IsNullOrWhiteSpace(unionName)) + resolvedField.AddAttribute(new UnionAttribute(unionName.Trim())); + + return resolvedField.AddResolver(unionName, resolverMethod); + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_ResolvedFields.cs b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_ResolvedFields.cs new file mode 100644 index 000000000..c7eb91ed2 --- /dev/null +++ b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_ResolvedFields.cs @@ -0,0 +1,177 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration +{ + using System; + using System.Linq; + using System.Reflection; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using Microsoft.AspNetCore.Authorization; + + /// + /// Extension methods for configuring minimal API methods as fields on the graph. + /// + public static partial class GraphQLRuntimeSchemaItemDefinitionExtensions + { + /// + /// Adds policy-based authorization requirements to the field. + /// + /// + /// This is similar to adding the to a controller method + /// + /// The field being built. + /// The name of the policy to assign via this requirement. + /// A comma-seperated list of roles to assign via this requirement. + /// IGraphQLFieldBuilder. + public static IGraphQLRuntimeResolvedFieldDefinition RequireAuthorization( + this IGraphQLRuntimeResolvedFieldDefinition fieldBuilder, + string policyName = null, + string roles = null) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + return RequireAuthorizationInternal(fieldBuilder, policyName, roles); + } + + /// + /// Indicates that the field should allow anonymous access. This will override any potential authorization requirements setup via + /// the "MapGroup" methods if this field was created within a group. + /// + /// + /// This is similar to adding the to a controller method + /// + /// The field being built. + /// IGraphQLFieldBuilder. + public static IGraphQLRuntimeResolvedFieldDefinition AllowAnonymous(this IGraphQLRuntimeResolvedFieldDefinition fieldBuilder) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + return AllowAnonymousInternal(fieldBuilder); + } + + /// + /// Adds a set of possible return types for this field. This is synonymous to using the + /// on a controller's action method. + /// + /// + /// This method can be called multiple times. Any new types will be appended to the field. All types added + /// must be coercable to the declared return type of the assigned resolver for this field unless this field returns a union; in + /// which case the types will be added as union members. + /// + /// The field being built. + /// The first possible type that might be returned by this + /// field. + /// Any number of additional possible types that + /// might be returned by this field. + /// IGraphQLFieldBuilder. + public static IGraphQLRuntimeResolvedFieldDefinition AddPossibleTypes(this IGraphQLRuntimeResolvedFieldDefinition fieldBuilder, Type firstPossibleType, params Type[] additionalPossibleTypes) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + return AddPossibleTypesInternal(fieldBuilder, firstPossibleType, additionalPossibleTypes); + } + + /// + /// Clears all extra defined possible types this field may declare. This will not affect the core type defined by the resolver, if + /// a resolver has been defined for this field. + /// + /// The field builder. + /// IGraphQLRuntimeResolvedFieldDefinition. + public static IGraphQLRuntimeResolvedFieldDefinition ClearPossibleTypes(this IGraphQLRuntimeResolvedFieldDefinition fieldBuilder) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + return ClearPossibleTypesInternal(fieldBuilder); + } + + /// + /// Assigns a custom internal name to this field. This value will be used in error + /// messages and log entries instead of an anonymous method name. This can significantly increase readability + /// while trying to debug an issue. This value has no bearing on the runtime use of this field. It is cosmetic only. + /// + /// + /// This value does NOT affect the field name as it would appear in a schema. It only effects the internal + /// name used in log messages and exception text. + /// + /// The field being built. + /// The value to use as the internal name for this field definition when its + /// added to the schema. + /// IGraphQLFieldBuilder. + public static IGraphQLRuntimeResolvedFieldDefinition WithInternalName(this IGraphQLRuntimeResolvedFieldDefinition fieldBuilder, string internalName) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + fieldBuilder.InternalName = internalName?.Trim(); + return fieldBuilder; + } + + /// + /// Indicates this field will return a union and sets the resolver to be used when this field is requested at runtime. The provided + /// resolver should return a . + /// + /// The field being built. + /// Provide a name and this field will be declared to return a union. Use to declare union members. + /// The delegate to assign as the resolver. This method will be + /// parsed to determine input arguments for the field on the target schema. + /// IGraphQLFieldBuilder. + public static IGraphQLRuntimeResolvedFieldDefinition AddResolver( + this IGraphQLRuntimeResolvedFieldDefinition fieldBuilder, + string unionName, + Delegate resolverMethod) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + return AddResolverInternal( + fieldBuilder, + null as Type, // return Type + unionName, + resolverMethod); + } + + /// + /// Sets the resolver to be used when this field is requested at runtime. + /// + /// + /// If this method is called more than once the previously set resolver will be replaced. + /// + /// The field being built. + /// The delegate to assign as the resolver. This method will be + /// parsed to determine input arguments for the field on the target schema. + /// IGraphQLFieldBuilder. + public static IGraphQLRuntimeResolvedFieldDefinition AddResolver(this IGraphQLRuntimeResolvedFieldDefinition fieldBuilder, Delegate resolverMethod) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + return AddResolverInternal( + fieldBuilder, + null as Type, // return Type + null, // unionName + resolverMethod); + } + + /// + /// Sets the resolver to be used when this field is requested at runtime. + /// + /// + /// If this method is called more than once the previously set resolver will be replaced. + /// + /// The expected, primary return type of the field. Must be provided + /// if the supplied delegate returns an . + /// The field being built. + /// The delegate to assign as the resolver. This method will be + /// parsed to determine input arguments for the field on the target schema. + /// IGraphQLFieldBuilder. + public static IGraphQLRuntimeResolvedFieldDefinition AddResolver(this IGraphQLRuntimeResolvedFieldDefinition fieldBuilder, Delegate resolverMethod) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + return AddResolverInternal( + fieldBuilder, + typeof(TReturnType), + null, // unionName + resolverMethod); + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_TypeExtensions.cs b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_TypeExtensions.cs new file mode 100644 index 000000000..accaf668a --- /dev/null +++ b/src/graphql-aspnet/Configuration/GraphQLRuntimeSchemaItemDefinitionExtensions_TypeExtensions.cs @@ -0,0 +1,437 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration +{ + using System; + using System.Collections.Generic; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions; + using Microsoft.AspNetCore.Authorization; + + /// + /// Extension methods for configuring minimal API methods as fields on the graph. + /// + public static partial class GraphQLRuntimeSchemaItemDefinitionExtensions + { + /// + /// Registers a new type extension to a given OBJECT or INTERFACE type for the target schema. + /// + /// + /// + /// This method is synonymous with using the on + /// a controller action. + /// + /// + /// The supplied resolver must declare a parameter that is of the same type as . + /// + /// + /// The concrete interface, class or struct to extend with a new field. + /// The schema builder to append the field to. + /// Name of the field to add to the . + /// IGraphQLResolvedFieldTemplate. + public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this ISchemaBuilder builder, string fieldName) + { + Validation.ThrowIfNull(builder, nameof(builder)); + + return MapTypeExtension( + builder.Options, + typeof(TOwnerType), + fieldName, + null, // unionName + null as Delegate); + } + + /// + /// Registers a new type extension to a given OBJECT or INTERFACE type for the target schema. + /// + /// + /// + /// This method is synonymous with using the on + /// a controller action. + /// + /// + /// The supplied resolver must declare a parameter that is of the same type as . + /// + /// + /// The concrete interface, class or struct to extend with a new field. + /// The schema builder to append the field to. + /// Name of the field to add to the . + /// The resolver method to be called when the field is requested. + /// IGraphQLResolvedFieldTemplate. + public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this ISchemaBuilder builder, string fieldName, Delegate resolverMethod) + { + Validation.ThrowIfNull(builder, nameof(builder)); + + return MapTypeExtension( + builder.Options, + typeof(TOwnerType), + fieldName, + null, // unionName + resolverMethod); + } + + /// + /// Registers a new type extension to a given OBJECT or INTERFACE type for the target schema. + /// + /// + /// + /// This method is synonymous with using the on + /// a controller action. + /// + /// + /// The supplied resolver must declare a parameter that is of the same type as . + /// + /// + /// The concrete interface, class or struct to extend with a new field. + /// The schema builder to append the field to. + /// Name of the field to add to the . + /// Provide a name and this field will be declared to return a union. Use to declare union members. + /// The resolver method to be called when the field is requested. + /// IGraphQLResolvedFieldTemplate. + public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this ISchemaBuilder builder, string fieldName, string unionName, Delegate resolverMethod) + { + Validation.ThrowIfNull(builder, nameof(builder)); + + return MapTypeExtension( + builder.Options, + typeof(TOwnerType), + fieldName, + unionName, + resolverMethod); + } + + /// + /// Registers a new type extension to a given OBJECT or INTERFACE type for the target schema. + /// + /// + /// + /// This method is synonymous with using the on + /// a controller action. + /// + /// + /// The supplied resolver must declare a parameter that is of the same type as . + /// + /// + /// The concrete interface, class or struct to extend with a new field. + /// The configuration options for the target schema. + /// Name of the field to add to the . + /// IGraphQLResolvedFieldTemplate. + public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this SchemaOptions schemaOptions, string fieldName) + { + return MapTypeExtension( + schemaOptions, + typeof(TOwnerType), + fieldName, + null, // unionName + null as Delegate); + } + + /// + /// Registers a new type extension to a given OBJECT or INTERFACE type for the target schema. + /// + /// + /// + /// This method is synonymous with using the on + /// a controller action. + /// + /// + /// The supplied resolver must declare a parameter that is of the same type as . + /// + /// + /// The concrete interface, class or struct to extend with a new field. + /// The configuration options for the target schema. + /// Name of the field to add to the . + /// The resolver method to be called when the field is requested. + /// IGraphQLResolvedFieldTemplate. + public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this SchemaOptions schemaOptions, string fieldName, Delegate resolverMethod) + { + return MapTypeExtension( + schemaOptions, + typeof(TOwnerType), + fieldName, + null, // unionName + resolverMethod); + } + + /// + /// Registers a new field to a given type on the target schema. + /// + /// + /// + /// The supplied resolver must declare a parameter that is of the same type as . + /// + /// + /// The configuration options for the target schema. + /// The concrete interface, class or struct to extend with a new field. + /// Name of the field to add to the . + /// IGraphQLResolvedFieldTemplate. + public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this SchemaOptions schemaOptions, Type fieldOwnerType, string fieldName) + { + return MapTypeExtension( + schemaOptions, + fieldOwnerType, + fieldName, + null, // unionName + null as Delegate); + } + + /// + /// Registers a new field to a given type on the target schema. + /// + /// + /// + /// The supplied resolver must declare a parameter that is of the same type as . + /// + /// + /// The configuration options for the target schema. + /// The concrete interface, class or struct to extend with a new field. + /// Name of the field to add to the . + /// The resolver method to be called when the field is requested. + /// IGraphQLResolvedFieldTemplate. + public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this SchemaOptions schemaOptions, Type fieldOwnerType, string fieldName, Delegate resolverMethod) + { + return MapTypeExtension( + schemaOptions, + fieldOwnerType, + fieldName, + null, // unionName + resolverMethod); + } + + /// + /// Registers a new type extension to a given OBJECT or INTERFACE type for the target schema. + /// + /// + /// + /// This method is synonymous with using the on + /// a controller action. + /// + /// + /// The supplied resolver must declare a parameter that is of the same type as . + /// + /// + /// The concrete interface, class or struct to extend with a new field. + /// The configuration options for the target schema. + /// Name of the field to add to the . + /// Provide a name and this field will be declared to return a union. Use to declare union members. + /// The resolver method to be called when the field is requested. + /// IGraphQLResolvedFieldTemplate. + public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this SchemaOptions schemaOptions, string fieldName, string unionName, Delegate resolverMethod) + { + return MapTypeExtension( + schemaOptions, + typeof(TOwnerType), + fieldName, + unionName, + resolverMethod); + } + + /// + /// Registers a new field to a given type on the target schema. + /// + /// + /// + /// The supplied resolver must declare a parameter that is of the same type as . + /// + /// + /// The configuration options for the target schema. + /// The concrete interface, class or struct to extend with a new field. + /// Name of the field to add to the . + /// Provide a name and this field will be declared to return a union. Use to declare union members. + /// The resolver method to be called when the field is requested. + /// IGraphQLResolvedFieldTemplate. + public static IGraphQLRuntimeTypeExtensionDefinition MapTypeExtension(this SchemaOptions schemaOptions, Type fieldOwnerType, string fieldName, string unionName, Delegate resolverMethod) + { + schemaOptions = Validation.ThrowIfNullOrReturn(schemaOptions, nameof(schemaOptions)); + fieldOwnerType = Validation.ThrowIfNullOrReturn(fieldOwnerType, nameof(fieldOwnerType)); + fieldName = Validation.ThrowIfNullWhiteSpaceOrReturn(fieldName, nameof(fieldName)); + + IGraphQLRuntimeTypeExtensionDefinition field = new RuntimeTypeExtensionDefinition( + schemaOptions, + fieldOwnerType, + fieldName, + FieldResolutionMode.PerSourceItem); + + schemaOptions.AddRuntimeSchemaItem(field); + + if (resolverMethod != null) + field = field.AddResolver(unionName, resolverMethod); + + return field; + } + + /// + /// Instructs the new type extension field that it should process data in batched mode rather than + /// in a "per source item" mode. + /// + /// + /// + /// The supplied resolver must declare a parameter that is an of the same as + /// class, interface or struct that was originally extended as indicated by . + /// + /// + /// The type extension to make into a batch field. + /// IGraphQLTypeExtensionTemplate. + public static IGraphQLRuntimeTypeExtensionDefinition WithBatchProcessing(this IGraphQLRuntimeTypeExtensionDefinition typeExtension) + { + Validation.ThrowIfNull(typeExtension, nameof(typeExtension)); + typeExtension.ExecutionMode = FieldResolutionMode.Batch; + return typeExtension; + } + + /// + /// Adds a set of possible return types for this field. This is synonymous to using the + /// on a controller's action method. + /// + /// + /// This method can be called multiple times. Any new types will be appended to the field. + /// + /// The field being built. + /// The first possible type that might be returned by this + /// field. + /// Any number of additional possible types that + /// might be returned by this field. + /// IGraphQLFieldBuilder. + public static IGraphQLRuntimeTypeExtensionDefinition AddPossibleTypes(this IGraphQLRuntimeTypeExtensionDefinition typeExtension, Type firstPossibleType, params Type[] additionalPossibleTypes) + { + Validation.ThrowIfNull(typeExtension, nameof(typeExtension)); + return AddPossibleTypesInternal(typeExtension, firstPossibleType, additionalPossibleTypes); + } + + /// + /// Clears all extra defined possible types this field may declare. This will not affect the core type defined by the resolver, if + /// a resolver has been defined for this field. + /// + /// The field builder. + /// IGraphQLRuntimeResolvedFieldDefinition. + public static IGraphQLRuntimeTypeExtensionDefinition ClearPossibleTypes(this IGraphQLRuntimeTypeExtensionDefinition typeExtension) + { + Validation.ThrowIfNull(typeExtension, nameof(typeExtension)); + return ClearPossibleTypesInternal(typeExtension); + } + + /// + /// Assigns a custom value to the internal name of this type exension. This value will be used in error + /// messages and log entries instead of an anonymous method name. This can significantly increase readability + /// while trying to debug an issue. + /// + /// + /// This value does NOT affect the field name as it would appear in a schema. It only effects the internal + /// name used in log messages and exception text. + /// + /// The type exension field being built. + /// The value to use as the internal name for this field definition when its + /// added to the schema. + /// IGraphQLFieldBuilder. + public static IGraphQLRuntimeTypeExtensionDefinition WithInternalName(this IGraphQLRuntimeTypeExtensionDefinition typeExtension, string internalName) + { + typeExtension.InternalName = internalName; + return typeExtension; + } + + /// + /// Adds policy-based authorization requirements to the field. + /// + /// + /// This is similar to adding the to a controller method + /// + /// The field being built. + /// The name of the policy to assign via this requirement. + /// A comma-seperated list of roles to assign via this requirement. + /// IGraphQLFieldBuilder. + public static IGraphQLRuntimeTypeExtensionDefinition RequireAuthorization( + this IGraphQLRuntimeTypeExtensionDefinition fieldBuilder, + string policyName = null, + string roles = null) + { + Validation.ThrowIfNull(fieldBuilder, nameof(fieldBuilder)); + return RequireAuthorizationInternal(fieldBuilder, policyName, roles); + } + + /// + /// Indicates that the field should allow anonymous access. + /// + /// + /// This is similar to adding the to a controller method + /// + /// The field being built. + /// IGraphQLFieldBuilder. + public static IGraphQLRuntimeTypeExtensionDefinition AllowAnonymous(this IGraphQLRuntimeTypeExtensionDefinition typeExtension) + { + Validation.ThrowIfNull(typeExtension, nameof(typeExtension)); + return AllowAnonymousInternal(typeExtension); + } + + /// + /// Sets the resolver to be used when this field is requested at runtime. + /// + /// + /// If this method is called more than once the previously set resolver will be replaced. + /// + /// The field being built. + /// The delegate to assign as the resolver. This method will be + /// parsed to determine input arguments for the field on the target schema. + /// IGraphQLFieldBuilder. + public static IGraphQLRuntimeTypeExtensionDefinition AddResolver(this IGraphQLRuntimeTypeExtensionDefinition typeExtension, Delegate resolverMethod) + { + return AddResolverInternal( + typeExtension, + null as Type, // returnType + null, // unionName + resolverMethod); + } + + /// + /// Sets the resolver to be used when this field is requested at runtime. + /// + /// + /// If this method is called more than once the previously set resolver will be replaced. + /// + /// The expected, primary return type of the field. Must be provided + /// if the supplied delegate returns an . + /// The field being built. + /// The delegate to assign as the resolver. This method will be + /// parsed to determine input arguments for the field on the target schema. + /// IGraphQLFieldBuilder. + public static IGraphQLRuntimeTypeExtensionDefinition AddResolver(this IGraphQLRuntimeTypeExtensionDefinition fieldBuilder, Delegate resolverMethod) + { + return AddResolverInternal( + fieldBuilder, + typeof(TReturnType), + null, // unionName + resolverMethod); + } + + /// + /// Sets the resolver to be used when this field is requested at runtime. + /// + /// + /// If this method is called more than once the previously set resolver will be replaced. + /// + /// The field being built. + /// Provide a name and this field will be declared to return a union. Use to declare union members. + /// The delegate to assign as the resolver. This method will be + /// parsed to determine input arguments for the field on the target schema. + /// IGraphQLFieldBuilder. + public static IGraphQLRuntimeTypeExtensionDefinition AddResolver(this IGraphQLRuntimeTypeExtensionDefinition fieldBuilder, string unionName, Delegate resolverMethod) + { + return AddResolverInternal( + fieldBuilder, + null as Type, // returnType + unionName, + resolverMethod); + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/GraphQLSchemaBuilderExtensions.cs b/src/graphql-aspnet/Configuration/GraphQLSchemaBuilderExtensions.cs index b4064ec0f..e338d1f39 100644 --- a/src/graphql-aspnet/Configuration/GraphQLSchemaBuilderExtensions.cs +++ b/src/graphql-aspnet/Configuration/GraphQLSchemaBuilderExtensions.cs @@ -10,12 +10,10 @@ namespace GraphQL.AspNet.Configuration { using System; - using System.Collections.Generic; using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Configuration.Startup; using GraphQL.AspNet.Engine; - using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Configuration; using GraphQL.AspNet.Interfaces.Engine; using GraphQL.AspNet.Interfaces.Schema; @@ -28,25 +26,6 @@ namespace GraphQL.AspNet.Configuration /// public static class GraphQLSchemaBuilderExtensions { - private static readonly Dictionary SCHEMA_REGISTRATIONS; - - /// - /// Initializes static members of the class. - /// - static GraphQLSchemaBuilderExtensions() - { - SCHEMA_REGISTRATIONS = new Dictionary(); - } - - /// - /// Helper method to null out the schema registration references. Useful in testing and after setup is complete there is no - /// need to keep the reference chain in tact. - /// - public static void Clear() - { - SCHEMA_REGISTRATIONS.Clear(); - } - /// /// Enables the query cache locally, in memory, to retain parsed query plans. When enabled, use the configuration /// settings for each added schema to determine how each will interact with the cache. Implement your own cache provider @@ -75,18 +54,21 @@ public static ISchemaBuilder AddGraphQL( where TSchema : class, ISchema { Validation.ThrowIfNull(serviceCollection, nameof(serviceCollection)); - if (SCHEMA_REGISTRATIONS.ContainsKey(typeof(TSchema))) + + var wasFound = GraphQLSchemaInjectorFactory.TryGetOrCreate( + out var injector, + serviceCollection, + options); + + if (wasFound) { - throw new GraphTypeDeclarationException( + throw new InvalidOperationException( $"The schema type {typeof(TSchema).FriendlyName()} has already been registered. " + "Each schema type may only be registered once with GraphQL."); } - var schemaOptions = new SchemaOptions(serviceCollection); - var injector = new GraphQLSchemaInjector(schemaOptions, options); - SCHEMA_REGISTRATIONS.Add(typeof(TSchema), injector); - injector.ConfigureServices(); + return injector.SchemaBuilder; } @@ -111,12 +93,15 @@ public static ISchemaBuilder AddGraphQL( /// The application being constructed. public static void UseGraphQL(this IApplicationBuilder app) { - foreach (var injector in SCHEMA_REGISTRATIONS.Values) + Validation.ThrowIfNull(app, nameof(app)); + var allInjectors = app.ApplicationServices.GetServices(); + if (allInjectors != null) { - injector.UseSchema(app); + foreach (var injector in allInjectors) + { + injector.UseSchema(app); + } } - - Clear(); } /// @@ -133,12 +118,15 @@ public static void UseGraphQL(this IApplicationBuilder app) /// graphql runtime. public static void UseGraphQL(this IServiceProvider serviceProvider) { - foreach (var injector in SCHEMA_REGISTRATIONS.Values) + Validation.ThrowIfNull(serviceProvider, nameof(serviceProvider)); + var allInjectors = serviceProvider.GetServices(); + if (allInjectors != null) { - injector.UseSchema(serviceProvider); + foreach (var injector in allInjectors) + { + injector.UseSchema(serviceProvider); + } } - - Clear(); } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/ResolverIsolationOptions.cs b/src/graphql-aspnet/Configuration/ResolverIsolationOptions.cs index 56194be3a..d562ca47d 100644 --- a/src/graphql-aspnet/Configuration/ResolverIsolationOptions.cs +++ b/src/graphql-aspnet/Configuration/ResolverIsolationOptions.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Configuration { using System; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; /// /// A set of options indicating the various resolver types that may be diff --git a/src/graphql-aspnet/Configuration/ResolverIsolationOptionsExtensions.cs b/src/graphql-aspnet/Configuration/ResolverIsolationOptionsExtensions.cs index 51dc59cf2..032071a06 100644 --- a/src/graphql-aspnet/Configuration/ResolverIsolationOptionsExtensions.cs +++ b/src/graphql-aspnet/Configuration/ResolverIsolationOptionsExtensions.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Configuration { using System; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; /// /// Helper methods for working with . diff --git a/src/graphql-aspnet/Configuration/ResolverParameterResolutionRules.cs b/src/graphql-aspnet/Configuration/ResolverParameterResolutionRules.cs new file mode 100644 index 000000000..1ae3c2bbd --- /dev/null +++ b/src/graphql-aspnet/Configuration/ResolverParameterResolutionRules.cs @@ -0,0 +1,21 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration +{ + /// + /// The available rules that dictate how the runtime will handle a missing or unresolved parameter + /// during the execution of a field or directive resolver. + /// + public enum ResolverParameterResolutionRules + { + ThrowException = 0, + UseNullorDefault = 1, + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/SchemaArgumentBindingRules.cs b/src/graphql-aspnet/Configuration/SchemaArgumentBindingRules.cs new file mode 100644 index 000000000..fe8e09ce9 --- /dev/null +++ b/src/graphql-aspnet/Configuration/SchemaArgumentBindingRules.cs @@ -0,0 +1,60 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration +{ + using System; + + /// + /// A declaration of the possible rules used by a schema to determine which + /// arguments should be injected by a DI container and which should be part of the + /// a query. + /// + public enum SchemaArgumentBindingRules + { + /// + /// + /// Undecorated parameters will be treated as being part of the schema if the declared .NET type of the + /// argument is part of the schema as an appropriate graph type (e.g. SCALAR, ENUM, INPUT_OBJECT). + /// + /// Method parameters not included in the schema will attempt to be resolved from a scoped + /// when a field or directive resolver is invoked. + /// + /// + /// + /// This is the default option for all schemas unless changed by the developer. + /// + ParametersPreferQueryResolution = 0, + + /// + /// + /// All method parameters intending to be included as arguments on the schema must be explicitly decorated using + /// [FromGraphQL]. Undecorated parameters will be treated as needing to be resolved + /// from a scoped when a field or directive resolver is invoked. + /// + /// + /// Undecorated parameters WILL NOT be included as part of the schema. + /// + /// + ParametersRequireFromGraphQLDeclaration = 1, + + /// + /// + /// All method parameters intending to be resolved from a scoped + /// must be explicitly declared using [FromServices]. Undecorated arguments will be treated as + /// being part of the schema. + /// + /// + /// Undecorated parameters WILL be included as part of the schema. This may lead to the schema being unable to be + /// generated and the server failing to start. + /// + /// + ParametersRequireFromServicesDeclaration = 2, + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/SchemaDeclarationConfiguration.cs b/src/graphql-aspnet/Configuration/SchemaDeclarationConfiguration.cs index fd0d5da52..73dd760e1 100644 --- a/src/graphql-aspnet/Configuration/SchemaDeclarationConfiguration.cs +++ b/src/graphql-aspnet/Configuration/SchemaDeclarationConfiguration.cs @@ -31,12 +31,13 @@ public SchemaDeclarationConfiguration() this.AllowedOperations = new HashSet(); this.AllowedOperations.Add(GraphOperationType.Query); this.AllowedOperations.Add(GraphOperationType.Mutation); + this.ArgumentBindingRule = SchemaArgumentBindingRules.ParametersPreferQueryResolution; } /// /// Merges the specified configuration setttings into this instance. /// - /// The configuration. + /// The configuration values to merge into this instance. public void Merge(ISchemaDeclarationConfiguration config) { if (config == null) @@ -45,6 +46,7 @@ public void Merge(ISchemaDeclarationConfiguration config) this.DisableIntrospection = config.DisableIntrospection; this.FieldDeclarationRequirements = config.FieldDeclarationRequirements; this.GraphNamingFormatter = config.GraphNamingFormatter; + this.ArgumentBindingRule = config.ArgumentBindingRule; if (config.AllowedOperations != null) { @@ -56,6 +58,9 @@ public void Merge(ISchemaDeclarationConfiguration config) /// public bool DisableIntrospection { get; set; } + /// + public SchemaArgumentBindingRules ArgumentBindingRule { get; set; } + /// public TemplateDeclarationRequirements FieldDeclarationRequirements { get; set; } = TemplateDeclarationRequirements.Default; diff --git a/src/graphql-aspnet/Configuration/SchemaExecutionConfiguration.cs b/src/graphql-aspnet/Configuration/SchemaExecutionConfiguration.cs index 3320b0294..f00fd9ad9 100644 --- a/src/graphql-aspnet/Configuration/SchemaExecutionConfiguration.cs +++ b/src/graphql-aspnet/Configuration/SchemaExecutionConfiguration.cs @@ -23,6 +23,14 @@ public class SchemaExecutionConfiguration : ISchemaExecutionConfiguration { private ResolverIsolationOptions _isolationOptions = ResolverIsolationOptions.None; + /// + /// Initializes a new instance of the class. + /// + public SchemaExecutionConfiguration() + { + this.ResolverParameterResolutionRule = ResolverParameterResolutionRules.ThrowException; + } + /// /// Merges the specified configuration setttings into this instance. /// @@ -38,6 +46,7 @@ public void Merge(ISchemaExecutionConfiguration config) this.MaxQueryDepth = config.MaxQueryDepth; this.MaxQueryComplexity = config.MaxQueryComplexity; this.DebugMode = config.DebugMode; + this.ResolverParameterResolutionRule = config.ResolverParameterResolutionRule; } /// @@ -52,6 +61,12 @@ public void Merge(ISchemaExecutionConfiguration config) /// public float? MaxQueryComplexity { get; set; } + /// + public bool DebugMode { get; set; } + + /// + public ResolverParameterResolutionRules ResolverParameterResolutionRule { get; set; } + /// public ResolverIsolationOptions ResolverIsolation { @@ -68,8 +83,5 @@ public ResolverIsolationOptions ResolverIsolation _isolationOptions = value; } } - - /// - public bool DebugMode { get; set; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/SchemaItemFilterExtensions.cs b/src/graphql-aspnet/Configuration/SchemaItemFilterExtensions.cs index 6f9dca8d8..4cccdf188 100644 --- a/src/graphql-aspnet/Configuration/SchemaItemFilterExtensions.cs +++ b/src/graphql-aspnet/Configuration/SchemaItemFilterExtensions.cs @@ -209,7 +209,7 @@ public static bool IsEnumValue(this ISchemaItem schemaItem, TEnum enumVal return schemaItem != null && schemaItem is IEnumValue ev && ev.Parent.ObjectType == typeof(TEnum) - && Enum.Equals(ev.InternalValue, enumValue); + && Enum.Equals(ev.DeclaredValue, enumValue); } /// diff --git a/src/graphql-aspnet/Configuration/SchemaOptions.cs b/src/graphql-aspnet/Configuration/SchemaOptions.cs index 5e09a1ed4..41ae55cef 100644 --- a/src/graphql-aspnet/Configuration/SchemaOptions.cs +++ b/src/graphql-aspnet/Configuration/SchemaOptions.cs @@ -20,6 +20,7 @@ namespace GraphQL.AspNet.Configuration using GraphQL.AspNet.Directives; using GraphQL.AspNet.Interfaces.Configuration; using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.TypeSystem; using Microsoft.Extensions.DependencyInjection; @@ -30,11 +31,11 @@ namespace GraphQL.AspNet.Configuration /// public abstract class SchemaOptions { - private readonly Dictionary _serverExtensions; + private readonly List _serverExtensions; private readonly HashSet _possibleTypes; private readonly List _registeredServices; - private List _configExtensions; + private readonly List _runtimeTemplates; /// /// Initializes a new instance of the class. @@ -50,9 +51,9 @@ public SchemaOptions(Type schemaType, IServiceCollection serviceCollection) Validation.ThrowIfNotCastable(schemaType, nameof(schemaType)); _possibleTypes = new HashSet(SchemaTypeToRegister.DefaultEqualityComparer); - _serverExtensions = new Dictionary(); + _runtimeTemplates = new List(); + _serverExtensions = new List(); _registeredServices = new List(); - _configExtensions = new List(); this.DeclarationOptions = new SchemaDeclarationConfiguration(); this.CacheOptions = new SchemaQueryExecutionPlanCacheConfiguration(); @@ -269,20 +270,7 @@ public void RegisterExtension(TExtensionType extension) Validation.ThrowIfNull(extension, nameof(extension)); extension.Configure(this); - _serverExtensions.Add(extension.GetType(), extension); - } - - /// - /// Adds an extension to allow processing of schema instance by an extenal object - /// before the schema is complete. The state of the schema is not garunteed - /// when then extension is executed. It is highly likely that the schema will undergo - /// further processing after the extension executes. - /// - /// The extension to apply. - public void AddConfigurationExtension(ISchemaConfigurationExtension extension) - { - Validation.ThrowIfNull(extension, nameof(extension)); - _configExtensions.Add(extension); + _serverExtensions.Add(extension); } /// @@ -291,7 +279,7 @@ public void AddConfigurationExtension(ISchemaConfigurationExtension extension) /// /// The type of the directive to apply. /// IDirectiveInjector. - public DirectiveBindingConfiguration ApplyDirective() + public DirectiveBindingSchemaExtension ApplyDirective() where TDirectiveType : GraphDirective { return this.ApplyDirective(typeof(TDirectiveType)); @@ -303,14 +291,14 @@ public DirectiveBindingConfiguration ApplyDirective() /// /// The type of the directive to apply to schema items. /// IDirectiveInjector. - public DirectiveBindingConfiguration ApplyDirective(Type directiveType) + public DirectiveBindingSchemaExtension ApplyDirective(Type directiveType) { Validation.ThrowIfNull(directiveType, nameof(directiveType)); Validation.ThrowIfNotCastable(directiveType, nameof(directiveType)); this.AddType(directiveType, null, null); - var applicator = new DirectiveBindingConfiguration(directiveType); - this.AddConfigurationExtension(applicator); + var applicator = new DirectiveBindingSchemaExtension(directiveType); + this.RegisterExtension(applicator); return applicator; } @@ -322,11 +310,11 @@ public DirectiveBindingConfiguration ApplyDirective(Type directiveType) /// /// Name of the directive. /// IDirectiveInjector. - public DirectiveBindingConfiguration ApplyDirective(string directiveName) + public DirectiveBindingSchemaExtension ApplyDirective(string directiveName) { directiveName = Validation.ThrowIfNullWhiteSpaceOrReturn(directiveName, nameof(directiveName)); - var applicator = new DirectiveBindingConfiguration(directiveName); - this.AddConfigurationExtension(applicator); + var applicator = new DirectiveBindingSchemaExtension(directiveName); + this.RegisterExtension(applicator); return applicator; } @@ -347,6 +335,30 @@ internal void FinalizeServiceRegistration() } } + /// + /// Adds a "runtime declared" schema item to this instance so that it can be + /// registered when the schema is set up. + /// + /// + /// "Runtime declared" schema items are synonymous with minimal api defined fields and directives. + /// + /// The schema item to include. + public void AddRuntimeSchemaItem(IGraphQLRuntimeSchemaItemDefinition template) + { + this.ServiceCollection.AddRuntimeFieldExecutionSupport(); + _runtimeTemplates.Add(template); + } + + /// + /// Gets the runtime configured schema item templates that need to be setup + /// when the schema is generated. + /// + /// + /// These are the templates created via the Minimal API methods. + /// + /// The runtime templates. + public IEnumerable RuntimeTemplates => _runtimeTemplates; + /// /// Gets the classes, enums, structs and other types that need to be /// registered to the schema when its created. @@ -354,13 +366,6 @@ internal void FinalizeServiceRegistration() /// The registered schema types. public IEnumerable SchemaTypesToRegister => _possibleTypes; - /// - /// Gets the configuration extensions that will be applied to the schema instance when its - /// created. - /// - /// The configuration extensions. - public IEnumerable ConfigurationExtensions => _configExtensions; - /// /// Gets or sets a value indicating whether any , or /// any classes that implement at least one graph attribute, that are part of the entry assembly, are automatically @@ -411,7 +416,7 @@ internal void FinalizeServiceRegistration() /// Gets the set of options extensions added to this schema configuration. /// /// The extensions. - public IReadOnlyDictionary ServerExtensions => _serverExtensions; + public IEnumerable ServerExtensions => _serverExtensions; /// /// Gets the service collection which contains all the required entries for diff --git a/src/graphql-aspnet/Configuration/SchemaTypeToRegister.cs b/src/graphql-aspnet/Configuration/SchemaTypeToRegister.cs index 1f2e7cc01..69c0f8789 100644 --- a/src/graphql-aspnet/Configuration/SchemaTypeToRegister.cs +++ b/src/graphql-aspnet/Configuration/SchemaTypeToRegister.cs @@ -32,7 +32,9 @@ public class SchemaTypeToRegister /// Initializes a new instance of the class. /// /// The type to be registered to the schema. - /// The graph type kind to register the type as. + /// (optional) A specific graph type kind to force the type to be + /// coerced into. + [DebuggerStepperBoundary] public SchemaTypeToRegister(Type type, TypeKind? typeKind = null) { this.Type = Validation.ThrowIfNullOrReturn(type, nameof(type)); diff --git a/src/graphql-aspnet/Configuration/ServiceCollectionExtensions.cs b/src/graphql-aspnet/Configuration/ServiceCollectionExtensions.cs new file mode 100644 index 000000000..123a4cf8f --- /dev/null +++ b/src/graphql-aspnet/Configuration/ServiceCollectionExtensions.cs @@ -0,0 +1,40 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration +{ + using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.DependencyInjection.Extensions; + + /// + /// Helper methods for configuring an + /// during startup. + /// + public static class ServiceCollectionExtensions + { + /// + /// Adds support for the execution of runtime field declarations (e.g. minimal api + /// defined fields). + /// + /// The service collection. + /// IServiceCollection. + public static IServiceCollection AddRuntimeFieldExecutionSupport(this IServiceCollection serviceCollection) + { + if (serviceCollection != null) + { + serviceCollection.TryAddTransient(); + serviceCollection.TryAddTransient(); + } + + return serviceCollection; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/Startup/GraphQLSchemaInjectorFactory.cs b/src/graphql-aspnet/Configuration/Startup/GraphQLSchemaInjectorFactory.cs index f230994c8..e4fc23977 100644 --- a/src/graphql-aspnet/Configuration/Startup/GraphQLSchemaInjectorFactory.cs +++ b/src/graphql-aspnet/Configuration/Startup/GraphQLSchemaInjectorFactory.cs @@ -10,6 +10,7 @@ namespace GraphQL.AspNet.Configuration.Startup { using System; + using System.Linq; using GraphQL.AspNet.Common; using GraphQL.AspNet.Interfaces.Configuration; using GraphQL.AspNet.Interfaces.Schema; @@ -36,11 +37,46 @@ public static ISchemaInjector Create( where TSchema : class, ISchema { Validation.ThrowIfNull(schemaOptions, nameof(schemaOptions)); - var injector = new GraphQLSchemaInjector(schemaOptions, configurationDelegate); return injector; } + /// + /// Creates a new schema injector capable of configuring the provided + /// to properly serve schema dependencies. If an injector is already registered for the target + /// schema it is returned directly. + /// + /// The type of the schema being created. + /// Will be set to the instance that was located or created. + /// A set of schema options to use when generating entity + /// templates to be injected. + /// A configuration delegate to configure + /// schema specific settings. + /// true if this instance was successfully retrieved, false is the instance + /// was newly created. + public static bool TryGetOrCreate( + out ISchemaInjector instance, + SchemaOptions schemaOptions, + Action> configurationDelegate = null) + where TSchema : class, ISchema + { + instance = null; + Validation.ThrowIfNull(schemaOptions, nameof(schemaOptions)); + + var registeredInjector = schemaOptions.ServiceCollection? + .SingleOrDefault(x => x.ImplementationInstance is ISchemaInjector)? + .ImplementationInstance as ISchemaInjector; + + if (registeredInjector != null) + { + instance = registeredInjector; + return true; + } + + instance = Create(schemaOptions, configurationDelegate); + return false; + } + /// /// Creates a new schema injector capable of configuring the provided /// to properly serve schema dependencies. @@ -61,5 +97,40 @@ public static ISchemaInjector Create( var injector = new GraphQLSchemaInjector(schemaOptions, configurationDelegate); return injector; } + + /// + /// Creates a new schema injector capable of configuring the provided + /// to properly serve schema dependencies. If the provided service collection already contains + /// a registered instance of the target injector it is returned directly. + /// + /// The type of the schema being created. + /// Will be set to the instance that was located or created. + /// The service collection to populate. + /// A configuration delegate to configure + /// schema specific settings. + /// true if this instance was successfully retrieved, false is the instance + /// was newly created. + public static bool TryGetOrCreate( + out ISchemaInjector instance, + IServiceCollection serviceCollection, + Action> configurationDelegate = null) + where TSchema : class, ISchema + { + instance = null; + Validation.ThrowIfNull(serviceCollection, nameof(serviceCollection)); + + var registeredInjector = serviceCollection + .SingleOrDefault(x => x.ImplementationInstance is ISchemaInjector)? + .ImplementationInstance as ISchemaInjector; + + if (registeredInjector != null) + { + instance = registeredInjector; + return true; + } + + instance = Create(serviceCollection, configurationDelegate); + return false; + } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/Startup/GraphQLSchemaInjector{TSchema}.cs b/src/graphql-aspnet/Configuration/Startup/GraphQLSchemaInjector{TSchema}.cs index 2814aae97..2ffc35b2a 100644 --- a/src/graphql-aspnet/Configuration/Startup/GraphQLSchemaInjector{TSchema}.cs +++ b/src/graphql-aspnet/Configuration/Startup/GraphQLSchemaInjector{TSchema}.cs @@ -18,6 +18,7 @@ namespace GraphQL.AspNet.Configuration.Startup using GraphQL.AspNet.Configuration; using GraphQL.AspNet.Engine; using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Configuration; using GraphQL.AspNet.Interfaces.Engine; using GraphQL.AspNet.Interfaces.Execution; @@ -85,6 +86,13 @@ public GraphQLSchemaInjector(SchemaOptions options, Action public void ConfigureServices() { + if (_options.ServiceCollection.Any(x => x.ServiceType == typeof(TSchema))) + { + throw new InvalidOperationException( + $"The schema type {typeof(TSchema).FriendlyName()} has already been registered. " + + "Each schema type may only be registered once with GraphQL."); + } + // create the builder to guide the rest of the setup operations _configureOptions?.Invoke(_options); @@ -139,6 +147,9 @@ public void ConfigureServices() _options.ServiceCollection.TryAddSingleton(CreatePipelineFactory(_schemaBuilder.QueryExecutionPipeline)); _options.ServiceCollection.TryAddSingleton(CreatePipelineFactory(_schemaBuilder.DirectiveExecutionPipeline)); + // register self for final "using" extraction + _options.ServiceCollection.AddSingleton(this); + this.RegisterEngineComponents(); _options.FinalizeServiceRegistration(); @@ -160,6 +171,7 @@ private void RegisterEngineComponents() // "per request per schema" components _options.ServiceCollection.TryAddTransient(typeof(IGraphQLHttpProcessor), _options.QueryHandler.HttpProcessorType); + _options.ServiceCollection.TryAddTransient, DefaultGraphQLSchemaFactory>(); // "per application server" instance _options.ServiceCollection.TryAddScoped(); @@ -181,9 +193,17 @@ private void RegisterEngineComponents() /// TSchema. private TSchema BuildNewSchemaInstance(IServiceProvider serviceProvider) { - var schemaInstance = GraphSchemaBuilder.BuildSchema(serviceProvider); - var initializer = new GraphSchemaInitializer(_options, serviceProvider); - initializer.Initialize(schemaInstance); + var scope = serviceProvider.CreateScope(); + + var schemaConfig = _options.CreateConfiguration(); + + var factory = scope.ServiceProvider.GetRequiredService>(); + var schemaInstance = factory.CreateInstance( + scope, + schemaConfig, + _options.SchemaTypesToRegister, + _options.RuntimeTemplates, + _options.ServerExtensions); serviceProvider.WriteLogEntry( (l) => l.SchemaInstanceCreated(schemaInstance)); @@ -204,7 +224,7 @@ public void UseSchema(IApplicationBuilder appBuilder) if (_options.ServerExtensions != null) { foreach (var additionalOptions in _options.ServerExtensions) - additionalOptions.Value.UseExtension(appBuilder, appBuilder.ApplicationServices); + additionalOptions.UseExtension(appBuilder, appBuilder.ApplicationServices); } if (!_options.QueryHandler.DisableDefaultRoute) @@ -240,17 +260,13 @@ public void UseSchema(IServiceProvider serviceProvider) /// server options on this instance are invoked with just the service provider. private void UseSchema(IServiceProvider serviceProvider, bool invokeServerExtensions) { - // pre-parse any types known to this schema - var preCacher = new SchemaPreCacher(); - preCacher.PreCacheTemplates(_options.SchemaTypesToRegister.Select(x => x.Type)); - // only when the service provider is used for final configuration do we // invoke extensions with just the service provider // (mostly just for test harnessing, but may be used by developers as well) if (invokeServerExtensions) { foreach (var additionalOptions in _options.ServerExtensions) - additionalOptions.Value.UseExtension(serviceProvider: serviceProvider); + additionalOptions.UseExtension(serviceProvider: serviceProvider); } // try and build the schema diff --git a/src/graphql-aspnet/Configuration/Startup/GraphSchemaBuilder.cs b/src/graphql-aspnet/Configuration/Startup/GraphSchemaBuilder.cs index 4c60b8386..3e1251cea 100644 --- a/src/graphql-aspnet/Configuration/Startup/GraphSchemaBuilder.cs +++ b/src/graphql-aspnet/Configuration/Startup/GraphSchemaBuilder.cs @@ -42,7 +42,7 @@ public static TSchema BuildSchema(IServiceProvider sp) where TSchema : class, ISchema { // the whole point of this method is to try every possible combination - // of parameters and give out a friendly error message if nothing works. + // of parameters and give out a friendly error message instead of using the service provider cryptic error message. Validation.ThrowIfNull(sp, nameof(sp)); List paramSet = null; if (!SCHEMA_CONSTRUCTORS.TryGetValue(typeof(TSchema), out var schemaConstructor)) diff --git a/src/graphql-aspnet/Configuration/Startup/SchemaPreCacher.cs b/src/graphql-aspnet/Configuration/Startup/SchemaPreCacher.cs deleted file mode 100644 index 521b7b5d9..000000000 --- a/src/graphql-aspnet/Configuration/Startup/SchemaPreCacher.cs +++ /dev/null @@ -1,76 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Configuration.Startup -{ - using System; - using System.Collections.Generic; - using System.Linq; - using GraphQL.AspNet; - using GraphQL.AspNet.Common; - using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal; - - /// - /// Preloads data related to a schema into memory. - /// - internal class SchemaPreCacher - { - private readonly HashSet _parsedTypes; - - /// - /// Initializes a new instance of the class. - /// - public SchemaPreCacher() - { - _parsedTypes = new HashSet(); - } - - /// - /// Iterates over the schema types and ensures the are pre-loaded into the global template cache. - /// - /// The schema types. - public void PreCacheTemplates(IEnumerable schemaTypes) - { - if (schemaTypes == null) - return; - - foreach (var type in schemaTypes) - { - this.PreParseTypeAndChildren(type); - } - } - - private void PreParseTypeAndChildren(Type type) - { - if (_parsedTypes.Contains(type)) - return; - - _parsedTypes.Add(type); - - // can't preparse scalars (no parsing nessceary) - if (GraphQLProviders.ScalarProvider.IsScalar(type)) - return; - - // can't preparse union proxies (they aren't parsable graph types) - if (!GraphValidation.IsParseableType(type)) - return; - - var template = GraphQLProviders.TemplateProvider.ParseType(type); - - if (template is IGraphTypeFieldTemplateContainer fieldContainer) - { - foreach (var dependent in fieldContainer.FieldTemplates.SelectMany(x => x.RetrieveRequiredTypes())) - { - this.PreParseTypeAndChildren(dependent.Type); - } - } - } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Constants.cs b/src/graphql-aspnet/Constants.cs index 6c63e5e0f..fd0f3da91 100644 --- a/src/graphql-aspnet/Constants.cs +++ b/src/graphql-aspnet/Constants.cs @@ -35,6 +35,19 @@ static Constants() { } + /// + /// Other constants that don't fit into a category. + /// + public static class Other + { + /// + /// When a type expression for a field or argument is first parsed, the graph type name is + /// set to this value as a placeholder until such a time as the actual, case-sensitive graph type name + /// is determined. + /// + public const string DEFAULT_TYPE_EXPRESSION_TYPE_NAME = "Type"; + } + /// /// Constants related to the graphql logging framework. /// @@ -222,11 +235,8 @@ public static class ScalarNames public const string ID = "ID"; public const string SHORT = "Short"; public const string USHORT = "UShort"; - -#if NET6_0_OR_GREATER public const string DATEONLY = "DateOnly"; public const string TIMEONLY = "TimeOnly"; -#endif } /// diff --git a/src/graphql-aspnet/Controllers/ActionResults/Batching/BatchBuilder.cs b/src/graphql-aspnet/Controllers/ActionResults/Batching/BatchBuilder.cs index 0f342794d..2eb32e105 100644 --- a/src/graphql-aspnet/Controllers/ActionResults/Batching/BatchBuilder.cs +++ b/src/graphql-aspnet/Controllers/ActionResults/Batching/BatchBuilder.cs @@ -21,9 +21,8 @@ public class BatchBuilder : BatchBuilder /// /// Initializes a new instance of the class. /// - /// The field for witch this batch is being produced. - public BatchBuilder(IGraphField field) - : base(field, null, null, null, null) + public BatchBuilder() + : base(null, null, null, null) { } @@ -39,7 +38,7 @@ public BatchBuilder(IGraphField field) /// A batch builder with a given set of source data. public BatchBuilder FromSource(IEnumerable sourceData, Func sourceKeySelector) { - return new BatchBuilder(this.Field, sourceData, sourceKeySelector); + return new BatchBuilder(sourceData, sourceKeySelector); } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Controllers/ActionResults/Batching/BatchBuilder{TSource,TKey}.cs b/src/graphql-aspnet/Controllers/ActionResults/Batching/BatchBuilder{TSource,TKey}.cs index 838ebbae8..2847c397a 100644 --- a/src/graphql-aspnet/Controllers/ActionResults/Batching/BatchBuilder{TSource,TKey}.cs +++ b/src/graphql-aspnet/Controllers/ActionResults/Batching/BatchBuilder{TSource,TKey}.cs @@ -24,11 +24,10 @@ public class BatchBuilder : BatchBuilder /// /// Initializes a new instance of the class. /// - /// The field for witch this batch is being produced. /// The source data. /// A function to extract a single key value from each source item. - public BatchBuilder(IGraphField field, IEnumerable sourceData, Func sourceKeySelector) - : base(field, sourceData, null, sourceKeySelector, null) + public BatchBuilder(IEnumerable sourceData, Func sourceKeySelector) + : base(sourceData, null, sourceKeySelector, null) { } @@ -45,7 +44,6 @@ public BatchBuilder WithResults(IEnumerable( - this.Field, this.SourceData, resultData, this.SourceKeySelector, @@ -64,7 +62,7 @@ public BatchBuilder WithResults(IEnumerable WithResults(IEnumerable resultData, Func> resultKeySelector) where TResult : class { - return new BatchBuilder(this.Field, this.SourceData, resultData, this.SourceKeySelector, resultKeySelector); + return new BatchBuilder(this.SourceData, resultData, this.SourceKeySelector, resultKeySelector); } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Controllers/ActionResults/Batching/BatchBuilder{TSource,TResult,TKey}.cs b/src/graphql-aspnet/Controllers/ActionResults/Batching/BatchBuilder{TSource,TResult,TKey}.cs index 75092b472..6eef553ea 100644 --- a/src/graphql-aspnet/Controllers/ActionResults/Batching/BatchBuilder{TSource,TResult,TKey}.cs +++ b/src/graphql-aspnet/Controllers/ActionResults/Batching/BatchBuilder{TSource,TResult,TKey}.cs @@ -11,9 +11,7 @@ namespace GraphQL.AspNet.Controllers.ActionResults.Batching { using System; using System.Collections.Generic; - using System.Linq; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Interfaces.Schema; /// /// A builder to help construct a batch result for a field that can be properly dispursed to the source items in the batch. @@ -26,19 +24,16 @@ public class BatchBuilder : IBatchBuilder /// /// Initializes a new instance of the class. /// - /// The field for witch this batch is being produced. /// The source data. /// The result data. /// The source key selector. /// The result key selector. public BatchBuilder( - IGraphField field, IEnumerable sourceData, IEnumerable resultData, Func sourceKeySelector, Func> resultKeySelector) { - this.Field = field; this.SourceData = sourceData; this.ResultData = resultData; this.SourceKeySelector = sourceKeySelector; @@ -54,92 +49,24 @@ public IGraphActionResult Complete() if (this.SourceData == null) { return new InternalServerErrorGraphActionResult("The source data list, when attempting to finalize a " + - $"batch for field '{this.Field.Name}', was null."); + $"batch was null."); } if (this.SourceKeySelector == null) { return new InternalServerErrorGraphActionResult("The source key locator, when attempting to finalize a " + - $"batch for field '{this.Field.Name}', was null."); + $"batch was null."); } if (this.ResultData != null && this.ResultKeySelector == null) { return new InternalServerErrorGraphActionResult("The result key locator, when attempting to finalize a " + - $"batch for field '{this.Field.Name}', was null."); + $"batch was null."); } - var dictionary = new Dictionary(); - - // key out the results - var resultsByKey = new Dictionary>(); - if (this.ResultData != null) - { - // N:N relationships should complete in O(M) - // where M is the total number of keys defined across all instances - // - // 1:N relationships it should process in O(N) - // where N is the number of result items - foreach (var item in this.ResultData) - { - if (item == null) - continue; - - var keys = this.ResultKeySelector(item) ?? Enumerable.Empty(); - foreach (var key in keys) - { - if (key == null) - continue; - - if (!resultsByKey.ContainsKey(key)) - resultsByKey.Add(key, new HashSet()); - - resultsByKey[key].Add(item); - } - } - } - - var fieldReturnsAList = this.Field.TypeExpression.IsListOfItems; - - // add each source item to the reslt dictionary pulling in hte matching items from - // the results set and ensuring list vs no list depending on the executed field. - foreach (var sourceItem in this.SourceData) - { - object sourceResults = null; - var key = this.SourceKeySelector(sourceItem); - var lookupResults = resultsByKey.ContainsKey(key) ? resultsByKey[key] : null; - if (lookupResults != null) - { - if (fieldReturnsAList) - { - sourceResults = lookupResults.ToList(); - } - else - { - if (lookupResults.Count > 1) - { - return new InternalServerErrorGraphActionResult( - $"Invalid field resolution. When attempting to finalize a batch for field '{this.Field.Name}', " + - $"a source item with key '{key}' had {lookupResults.Count} result(s) in the batch was expected to have " + - "a single item."); - } - - sourceResults = lookupResults.FirstOrDefault(); - } - } - - dictionary.Add(sourceItem, sourceResults); - } - - return new ObjectReturnedGraphActionResult(dictionary); + return new CompleteBatchOperationGraphActionResult(this); } - /// - /// Gets the field for witch this batch is being produced. - /// - /// The field. - public IGraphField Field { get; } - /// /// Gets or sets the source data. /// diff --git a/src/graphql-aspnet/Controllers/ActionResults/CompleteBatchOperationGraphActionResult.cs b/src/graphql-aspnet/Controllers/ActionResults/CompleteBatchOperationGraphActionResult.cs new file mode 100644 index 000000000..695b1fee0 --- /dev/null +++ b/src/graphql-aspnet/Controllers/ActionResults/CompleteBatchOperationGraphActionResult.cs @@ -0,0 +1,133 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Controllers.ActionResults +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Controllers.ActionResults.Batching; + using GraphQL.AspNet.Execution.Contexts; + using GraphQL.AspNet.Execution.RulesEngine.RuleSets.FieldResolution.FieldValidation; + using GraphQL.AspNet.Interfaces.Controllers; + + /// + /// A graph action result that is built from a batch builder and can turn said builder into an + /// appropriate dictionary of items to resolve a type extension. + /// + /// The type of the source item supplied to the batch extension. + /// The type of the result item produced by the batch extension. + /// The type of the key that links to . + public class CompleteBatchOperationGraphActionResult : IGraphActionResult + { + private readonly BatchBuilder _batchBuilder; + + /// + /// Initializes a new instance of the class. + /// + /// The batch builder. + public CompleteBatchOperationGraphActionResult(BatchBuilder batchBuilder) + { + _batchBuilder = batchBuilder; + } + + /// + public Task CompleteAsync(SchemaItemResolutionContext context) + { + if (!(context is FieldResolutionContext frc)) + { + context.Messages.Critical( + $"Batch Operations cannot be completed against a resolution context of type {context?.GetType().FriendlyName()}", + Constants.ErrorCodes.INVALID_BATCH_RESULT, + context.Request.Origin); + return Task.CompletedTask; + } + + if (_batchBuilder == null) + { + context.Messages.Critical( + $"No batch builder was provided. Unable to complete the requested batch operation.", + Constants.ErrorCodes.INVALID_BATCH_RESULT, + frc.Request.Origin); + return Task.CompletedTask; + } + + var field = frc.Request.Field; + var dictionary = new Dictionary(); + + // key out the results + var resultsByKey = new Dictionary>(); + if (_batchBuilder.ResultData != null) + { + // N:N relationships should complete in O(M) + // where M is the total number of keys defined across all instances + // + // 1:N relationships it should process in O(N) + // where N is the number of result items + foreach (var item in _batchBuilder.ResultData) + { + if (item == null) + continue; + + var keys = _batchBuilder.ResultKeySelector(item) ?? Enumerable.Empty(); + foreach (var key in keys) + { + if (key == null) + continue; + + if (!resultsByKey.ContainsKey(key)) + resultsByKey.Add(key, new HashSet()); + + resultsByKey[key].Add(item); + } + } + } + + var fieldReturnsAList = field.TypeExpression.IsListOfItems; + + // add each source item to the reslt dictionary pulling in hte matching items from + // the results set and ensuring list vs no list depending on the executed field. + foreach (var sourceItem in _batchBuilder.SourceData) + { + object sourceResults = null; + var key = _batchBuilder.SourceKeySelector(sourceItem); + var lookupResults = resultsByKey.ContainsKey(key) ? resultsByKey[key] : null; + if (lookupResults != null) + { + if (fieldReturnsAList) + { + sourceResults = lookupResults.ToList(); + } + else + { + if (lookupResults.Count > 1) + { + frc.Messages.Critical( + $"Invalid field resolution. When attempting to finalize a batch for field '{field.Name}', " + + $"a source item with key '{key}' had {lookupResults.Count} result(s) in the batch was expected to have " + + "a single item.", + Constants.ErrorCodes.INVALID_BATCH_RESULT, + frc.Request.Origin); + + return Task.CompletedTask; + } + + sourceResults = lookupResults.FirstOrDefault(); + } + } + + dictionary.Add(sourceItem, sourceResults); + } + + frc.Result = dictionary; + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Controllers/ActionResults/GraphActionResult.cs b/src/graphql-aspnet/Controllers/ActionResults/GraphActionResult.cs new file mode 100644 index 000000000..6caa217fc --- /dev/null +++ b/src/graphql-aspnet/Controllers/ActionResults/GraphActionResult.cs @@ -0,0 +1,153 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Controllers.ActionResults +{ + using System; + using GraphQL.AspNet.Controllers.ActionResults.Batching; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Interfaces.Execution; + + /// + /// A helper class to allow the use of common methods + /// with non-controller based resolvers. + /// + public static class GraphActionResult + { + /// + /// Returns an result with the given item as the resolved value for the field. + /// + /// The object to resolve the field with. + /// IGraphActionResult<TResult>. + public static IGraphActionResult Ok(object item) + { + return new OperationCompleteGraphActionResult(item); + } + + /// + /// Returns a result indicating that null is the resolved value + /// for the field. + /// + /// IGraphActionResult<TResult>. + public static IGraphActionResult Ok() + { + return Ok(null); + } + + /// + /// Returns an error indicating that the issue indicated by occured. + /// + /// The human-friendly error message to assign ot the reported error in the graph result. + /// The code to assign to the error entry in the graph result. + /// An optional exception that generated this error. + /// IGraphActionResult. + public static IGraphActionResult Error( + string message, + string code = null, + Exception exception = null) + { + return new GraphFieldErrorActionResult(message, code, exception); + } + + /// + /// Returns an error indicating that the issue indicated by occured. + /// + /// The severity of the message. + /// The human-friendly error message to assign ot the reported error in the graph result. + /// The error code to assign to the reported error in the graph result. + /// An optional exception to be published if the query is configured to allow exposing exceptions. + /// IGraphActionResult. + public static IGraphActionResult Error( + GraphMessageSeverity severity, + string message, + string code = null, + Exception exception = null) + { + var errorMessage = new GraphExecutionMessage(severity, message, code, exception: exception); + return Error(errorMessage); + } + + /// + /// Returns an error indicating that the given issue occured. + /// + /// A custom generated error message. + /// IGraphActionResult. + public static IGraphActionResult Error(IGraphMessage message) + { + return new GraphFieldErrorActionResult(message); + } + + /// + /// Returns an error result indicating that processing failed due to some internal process. An exception + /// will be injected into the graph result and processing will be terminated. + /// + /// The error message. + /// IGraphActionResult. + public static IGraphActionResult InternalServerError(string errorMessage) + { + return new InternalServerErrorGraphActionResult(errorMessage); + } + + /// + /// Returns an error indicating that something could not be resolved correctly + /// with the information provided. + /// + /// The message indicating what was not found. + /// IGraphActionResult. + public static IGraphActionResult NotFound(string message) + { + return new RouteNotFoundGraphActionResult(message); + } + + /// + /// Returns an negative result, indicating the data supplied on the request was bad or + /// otherwise not usable by the controller method. + /// + /// The message. + /// IGraphActionResult. + public static IGraphActionResult BadRequest(string message) + { + return new BadRequestGraphActionResult(message); + } + + /// + /// Returns a negative result, indicating that the action requested was unauthorized for the current context. + /// + /// The message to return to the client. + /// The error code to apply to the error returned to the client. + /// IGraphActionResult. + public static IGraphActionResult Unauthorized(string message = null, string errorCode = null) + { + return new UnauthorizedGraphActionResult(message, errorCode); + } + + /// + /// Begings building a result capable of mapping a batch result to the original source items to correctly resolve + /// the field. (e.g. it will create a single item reference or a collection of children as the field + /// requires). An will be + /// generated indicating an issue if the batch produced cannot fulfill the requirements of the field. + /// This method will not throw an exception. + /// + /// IGraphActionResult. + public static BatchBuilder StartBatch() + { + return new BatchBuilder(); + } + + /// + /// Silently cancels the current execution context without supplying an error or reason code. + /// + /// IGraphActionResult. + public static IGraphActionResult Cancel() + { + return new UnspecifiedCancellationResult(); + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Controllers/ActionResults/GraphFieldErrorActionResult.cs b/src/graphql-aspnet/Controllers/ActionResults/GraphFieldErrorActionResult.cs index 22f4986bf..1a878fd48 100644 --- a/src/graphql-aspnet/Controllers/ActionResults/GraphFieldErrorActionResult.cs +++ b/src/graphql-aspnet/Controllers/ActionResults/GraphFieldErrorActionResult.cs @@ -16,7 +16,7 @@ namespace GraphQL.AspNet.Controllers.ActionResults using GraphQL.AspNet.Interfaces.Execution; /// - /// An action result indicating that the errored or in some way did + /// An action result indicating that the errored or in some way did /// not fulfill its request for data in an acceptable manner. This result is always interpreted as a /// critical error and stops execution of the current query. /// diff --git a/src/graphql-aspnet/Controllers/ActionResults/InternalServerErrorGraphActionResult.cs b/src/graphql-aspnet/Controllers/ActionResults/InternalServerErrorGraphActionResult.cs index 30af6ad4d..8352fcb30 100644 --- a/src/graphql-aspnet/Controllers/ActionResults/InternalServerErrorGraphActionResult.cs +++ b/src/graphql-aspnet/Controllers/ActionResults/InternalServerErrorGraphActionResult.cs @@ -23,7 +23,7 @@ namespace GraphQL.AspNet.Controllers.ActionResults public class InternalServerErrorGraphActionResult : IGraphActionResult { private readonly string _errorMessage; - private readonly IGraphFieldResolverMethod _action; + private readonly IGraphFieldResolverMetaData _action; private readonly Exception _exception; /// @@ -51,12 +51,15 @@ public InternalServerErrorGraphActionResult(string errorMessage, Exception excep /// /// The action that was invoked to cause this internal error, if any. /// The exception, if any, that was thrown. Useful for logging or other intermediate actions. - public InternalServerErrorGraphActionResult(IGraphFieldResolverMethod action, Exception exception) + public InternalServerErrorGraphActionResult(IGraphFieldResolverMetaData action, Exception exception) { _action = action; - _errorMessage = $"An unhandled exception was thrown during the execution of field '{_action?.Name ?? "-unknown-"}'."; + _errorMessage = $"An unhandled exception was thrown during the execution of an action. See inner exception for details."; _exception = exception; + + if (_exception == null) + _exception = new Exception($"The action method '{action?.InternalName}' failed to complete successfully but did not record an exception."); } /// diff --git a/src/graphql-aspnet/Controllers/ActionResults/ObjectReturnedGraphActionResult.cs b/src/graphql-aspnet/Controllers/ActionResults/OperationCompleteGraphActionResult.cs similarity index 82% rename from src/graphql-aspnet/Controllers/ActionResults/ObjectReturnedGraphActionResult.cs rename to src/graphql-aspnet/Controllers/ActionResults/OperationCompleteGraphActionResult.cs index 9945faf49..5dee49dfc 100644 --- a/src/graphql-aspnet/Controllers/ActionResults/ObjectReturnedGraphActionResult.cs +++ b/src/graphql-aspnet/Controllers/ActionResults/OperationCompleteGraphActionResult.cs @@ -18,15 +18,15 @@ namespace GraphQL.AspNet.Controllers.ActionResults /// A result indicating an ok return status and an object to be rendered to the graph. /// [DebuggerDisplay("Has Object: {_result?.GetType().FriendlyName()}")] - public class ObjectReturnedGraphActionResult : IGraphActionResult + public class OperationCompleteGraphActionResult : IGraphActionResult { private readonly object _result; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The object to return. - public ObjectReturnedGraphActionResult(object objectToReturn) + public OperationCompleteGraphActionResult(object objectToReturn) { _result = objectToReturn; } diff --git a/src/graphql-aspnet/Controllers/ActionResults/RouteNotFoundGraphActionResult.cs b/src/graphql-aspnet/Controllers/ActionResults/RouteNotFoundGraphActionResult.cs index cc417f989..b5c404b5c 100644 --- a/src/graphql-aspnet/Controllers/ActionResults/RouteNotFoundGraphActionResult.cs +++ b/src/graphql-aspnet/Controllers/ActionResults/RouteNotFoundGraphActionResult.cs @@ -12,6 +12,7 @@ namespace GraphQL.AspNet.Controllers.ActionResults using System; using System.Threading.Tasks; using GraphQL.AspNet.Execution.Contexts; + using GraphQL.AspNet.Execution.RulesEngine.RuleSets.DocumentValidation.QueryFragmentSteps; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Interfaces.Execution; @@ -22,7 +23,7 @@ namespace GraphQL.AspNet.Controllers.ActionResults /// public class RouteNotFoundGraphActionResult : IGraphActionResult { - private readonly IGraphFieldResolverMethod _invokeDef; + private readonly IGraphFieldResolverMetaData _invokeDef; private readonly Exception _thrownException; private readonly string _message; @@ -31,7 +32,7 @@ public class RouteNotFoundGraphActionResult : IGraphActionResult /// /// The invoked action at the route location. /// The thrown exception that occured when invoking the action, if any. - public RouteNotFoundGraphActionResult(IGraphFieldResolverMethod invokedAction, Exception thrownException = null) + public RouteNotFoundGraphActionResult(IGraphFieldResolverMetaData invokedAction, Exception thrownException = null) { _invokeDef = invokedAction; _thrownException = thrownException; @@ -56,30 +57,44 @@ public RouteNotFoundGraphActionResult() /// public Task CompleteAsync(SchemaItemResolutionContext context) { - if (_invokeDef != null) + context.Cancel(); + + if (!string.IsNullOrWhiteSpace(_message)) { context.Messages.Critical( - $"The field '{_invokeDef.Name}' was not found or could not be invoked.", + _message, Constants.ErrorCodes.INVALID_ROUTE, - context.Request.Origin, - _thrownException); + context.Request.Origin); + + return Task.CompletedTask; } - else if (!string.IsNullOrWhiteSpace(_message)) + + string fieldName = context.Route?.Path; + if (string.IsNullOrWhiteSpace(fieldName)) + fieldName = "~Unknown~"; + + if (_invokeDef != null) { + var exception = new Exception( + $"The resolver '{_invokeDef.InternalName}' with {_invokeDef.Parameters.Count} was not invocable with the provided data on " + + $"the request.", + _thrownException); + context.Messages.Critical( - _message, + $"The field '{fieldName}' was not found or the resolver could not be invoked.", Constants.ErrorCodes.INVALID_ROUTE, - context.Request.Origin); + context.Request.Origin, + exception); } else { context.Messages.Critical( - "The item was not routable or otherwise not available.", + $"The field '{fieldName}' was not routable or otherwise not available.", Constants.ErrorCodes.INVALID_ROUTE, - context.Request.Origin); + context.Request.Origin, + _thrownException); } - context.Cancel(); return Task.CompletedTask; } diff --git a/src/graphql-aspnet/Directives/ActionResults/DirectiveCancelPipelineResult.cs b/src/graphql-aspnet/Controllers/ActionResults/UnspecifiedCancellationResult.cs similarity index 65% rename from src/graphql-aspnet/Directives/ActionResults/DirectiveCancelPipelineResult.cs rename to src/graphql-aspnet/Controllers/ActionResults/UnspecifiedCancellationResult.cs index f7f9678d9..cba3fefe3 100644 --- a/src/graphql-aspnet/Directives/ActionResults/DirectiveCancelPipelineResult.cs +++ b/src/graphql-aspnet/Controllers/ActionResults/UnspecifiedCancellationResult.cs @@ -7,22 +7,21 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Directives.ActionResults +namespace GraphQL.AspNet.Controllers.ActionResults { using System.Threading.Tasks; using GraphQL.AspNet.Execution.Contexts; using GraphQL.AspNet.Interfaces.Controllers; /// - /// A directive action result that generates a response indicating the in-progress pipeline - /// should be abandoned. + /// A action result that generates a a canceled context without indicating any specific error. /// - public class DirectiveCancelPipelineResult : IGraphActionResult + public class UnspecifiedCancellationResult : IGraphActionResult { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public DirectiveCancelPipelineResult() + public UnspecifiedCancellationResult() { } diff --git a/src/graphql-aspnet/Controllers/GraphControllerBase.cs b/src/graphql-aspnet/Controllers/GraphControllerBase.cs index e01942f96..c988d6297 100644 --- a/src/graphql-aspnet/Controllers/GraphControllerBase.cs +++ b/src/graphql-aspnet/Controllers/GraphControllerBase.cs @@ -10,6 +10,7 @@ namespace GraphQL.AspNet.Controllers { using System; + using System.Data.SqlTypes; using System.Reflection; using System.Security.Claims; using System.Threading.Tasks; @@ -23,6 +24,7 @@ namespace GraphQL.AspNet.Controllers using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Interfaces.Web; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using Microsoft.AspNetCore.Http; /// @@ -34,60 +36,49 @@ public abstract class GraphControllerBase where TRequest : class, IDataRequest { private SchemaItemResolutionContext _schemaItemContext; - private IGraphFieldResolverMethod _action; + private IGraphFieldResolverMetaData _resolverMetaData; /// /// Invoke the specified action method as an asynchronous operation. /// - /// The action to invoke. + /// The metadata describing the method on this controller to invoke. /// The invocation context to process. /// Task<System.Object>. [GraphSkip] internal virtual async Task InvokeActionAsync( - IGraphFieldResolverMethod actionToInvoke, + IGraphFieldResolverMetaData resolverMetadata, SchemaItemResolutionContext schemaItemContext) { // deconstruct the context for processing Validation.ThrowIfNull(schemaItemContext, nameof(schemaItemContext)); _schemaItemContext = schemaItemContext; - _action = actionToInvoke; + _resolverMetaData = resolverMetadata; // ensure a field request is available var fieldRequest = schemaItemContext?.Request; Validation.ThrowIfNull(fieldRequest, nameof(fieldRequest)); - _schemaItemContext.Logger?.ActionMethodInvocationRequestStarted(_action, this.Request); + _schemaItemContext.Logger?.ActionMethodInvocationRequestStarted(_resolverMetaData, this.Request); if (_schemaItemContext.QueryRequest is IQueryExecutionWebRequest webRequest) this.HttpContext = webRequest.HttpContext; - if (_action?.Method == null) + if (_resolverMetaData?.Method == null) { return new InternalServerErrorGraphActionResult( - $"The definition for field '{this.GetType().Name}' defined no graph action to execute. Operation failed."); + $"The definition for field '{this.GetType().Name}' defined no resolver to execute. Operation failed."); } try { var modelGenerator = new ModelStateGenerator(schemaItemContext.ServiceProvider); - this.ModelState = modelGenerator.CreateStateDictionary(schemaItemContext.Arguments); + this.ModelState = modelGenerator.CreateStateDictionary(schemaItemContext.ExecutionSuppliedArguments); - _schemaItemContext.Logger?.ActionMethodModelStateValidated(_action, this.Request, this.ModelState); + _schemaItemContext.Logger?.ActionMethodModelStateValidated(_resolverMetaData, this.Request, this.ModelState); - // ensure this controller type is or inherits from the controller type where the method is actually - // declared as the method will be invoked aganst this instance. - if (!Validation.IsCastable(this.GetType(), _action.Method.DeclaringType)) - { - throw new TargetException($"Unable to invoke action '{_action.Route.Path}' on controller '{this.GetType().FriendlyName()}'. The controller " + - "does not own the method."); - } - - var invoker = InstanceFactory.CreateInstanceMethodInvoker(_action.Method); - var invocationParameters = schemaItemContext.Arguments.PrepareArguments(_action); - - var controllerRef = this as object; - var invokeReturn = invoker(ref controllerRef, invocationParameters); - if (_action.IsAsyncField) + var invocationParameters = _schemaItemContext.ExecutionSuppliedArguments.PrepareArguments(_resolverMetaData); + var invokeReturn = this.CreateAndInvokeAction(_resolverMetaData, invocationParameters); + if (_resolverMetaData.IsAsyncField) { if (invokeReturn is Task task) { @@ -95,26 +86,26 @@ internal virtual async Task InvokeActionAsync( if (task.IsFaulted) throw task.UnwrapException(); - invokeReturn = task.ResultOfTypeOrNull(_action.ExpectedReturnType); + invokeReturn = task.ResultOfTypeOrNull(_resolverMetaData.ExpectedReturnType); } else { // given all the checking and parsing this should be impossible, but just in case invokeReturn = new InternalServerErrorGraphActionResult( - $"The action '{_action.Route.Path}' is defined " + + $"The action '{_resolverMetaData.InternalName}' on controller '{_resolverMetaData.ParentInternalName}' is defined " + $"as asyncronous but it did not return a {typeof(Task)}."); } } - _schemaItemContext.Logger?.ActionMethodInvocationCompleted(_action, this.Request, invokeReturn); + _schemaItemContext.Logger?.ActionMethodInvocationCompleted(_resolverMetaData, this.Request, invokeReturn); return invokeReturn; } catch (TargetInvocationException ti) { var innerException = ti.InnerException ?? ti; - _schemaItemContext.Logger?.ActionMethodInvocationException(_action, this.Request, innerException); + _schemaItemContext.Logger?.ActionMethodInvocationException(_resolverMetaData, this.Request, innerException); - return new InternalServerErrorGraphActionResult(_action, innerException); + return new InternalServerErrorGraphActionResult(_resolverMetaData, innerException); } catch (Exception ex) { @@ -124,19 +115,70 @@ internal virtual async Task InvokeActionAsync( // might happen if a method was declared differently than the actual call signature case TargetException _: case TargetParameterCountException _: - _schemaItemContext.Logger?.ActionMethodInvocationException(_action, this.Request, ex); - return new RouteNotFoundGraphActionResult(_action, ex); + _schemaItemContext.Logger?.ActionMethodInvocationException(_resolverMetaData, this.Request, ex); + return new RouteNotFoundGraphActionResult(_resolverMetaData, ex); default: // total failure by the user's action code. // record and bubble - _schemaItemContext.Logger?.ActionMethodUnhandledException(_action, this.Request, ex); + _schemaItemContext.Logger?.ActionMethodUnhandledException(_resolverMetaData, this.Request, ex); throw; } } } + /// + /// Invoke the actual C# method declared by the + /// using this controller instance as the target object of the invocation. + /// + /// The resolver declaration that needs to be executed. + /// The realized set of arguments that need + /// to be passed to the invocable method instance. + /// The exact return value from the invoked resolver. + protected virtual object CreateAndInvokeAction(IGraphFieldResolverMetaData resolverMetaData, object[] invocationArguments) + { + switch (resolverMetaData.DefinitionSource) + { + case ItemSource.DesignTime: + if (!Validation.IsCastable(this.GetType(), resolverMetaData.Method.DeclaringType)) + { + throw new TargetException($"Unable to invoke action '{_resolverMetaData.InternalName}' on controller '{this.GetType().FriendlyName()}'. The controller " + + "does not own the method."); + } + + if (resolverMetaData.Method.IsStatic) + { + throw new TargetException($"Unable to invoke action '{_resolverMetaData.InternalName}' on controller '{this.GetType().FriendlyName()}'. The method " + + "is static and cannot be directly invoked on this controller instance."); + } + + var ctrlInvoker = InstanceFactory.CreateInstanceMethodInvoker(resolverMetaData.Method); + var controllerRef = this as object; + return ctrlInvoker(ref controllerRef, invocationArguments); + + case ItemSource.Runtime: + // minimal api resolvers are allowed to be static since there is no + // extra context to setup or make available such as 'this.User' etc. + if (resolverMetaData.Method.IsStatic) + { + var staticInvoker = InstanceFactory.CreateStaticMethodInvoker(resolverMetaData.Method); + return staticInvoker(invocationArguments); + } + else + { + var instanceInvoker = InstanceFactory.CreateInstanceMethodInvoker(resolverMetaData.Method); + var instance = InstanceFactory.CreateInstance(resolverMetaData.Method.DeclaringType); + return instanceInvoker(ref instance, invocationArguments); + } + + default: + throw new TargetException( + $"Unable to execute the target resolver {resolverMetaData.InternalName}. " + + $"Invalid or unsupported source location '{_resolverMetaData.DefinitionSource}'."); + } + } + /// /// Gets the state of the model being represented on this controller action invocation. /// diff --git a/src/graphql-aspnet/Controllers/GraphController_ActionResults.cs b/src/graphql-aspnet/Controllers/GraphController_ActionResults.cs index 4a31e0539..eb10b627a 100644 --- a/src/graphql-aspnet/Controllers/GraphController_ActionResults.cs +++ b/src/graphql-aspnet/Controllers/GraphController_ActionResults.cs @@ -29,7 +29,7 @@ public abstract partial class GraphController /// IGraphActionResult<TResult>. protected virtual IGraphActionResult Ok(object item) { - return new ObjectReturnedGraphActionResult(item); + return GraphActionResult.Ok(item); } /// @@ -39,7 +39,7 @@ protected virtual IGraphActionResult Ok(object item) /// IGraphActionResult<TResult>. protected virtual IGraphActionResult Ok() { - return this.Ok(null); + return GraphActionResult.Ok(); } /// @@ -54,7 +54,7 @@ protected virtual IGraphActionResult Error( string code = null, Exception exception = null) { - return new GraphFieldErrorActionResult(message, code, exception); + return GraphActionResult.Error(message, code, exception); } /// @@ -71,8 +71,7 @@ protected virtual IGraphActionResult Error( string code = null, Exception exception = null) { - var errorMessage = new GraphExecutionMessage(severity, message, code, exception: exception); - return this.Error(errorMessage); + return GraphActionResult.Error(severity, message, code, exception: exception); } /// @@ -82,7 +81,7 @@ protected virtual IGraphActionResult Error( /// IGraphActionResult. protected virtual IGraphActionResult Error(IGraphMessage message) { - return new GraphFieldErrorActionResult(message); + return GraphActionResult.Error(message); } /// @@ -93,7 +92,7 @@ protected virtual IGraphActionResult Error(IGraphMessage message) /// IGraphActionResult. protected virtual IGraphActionResult InternalServerError(string errorMessage) { - return new InternalServerErrorGraphActionResult(errorMessage); + return GraphActionResult.InternalServerError(errorMessage); } /// @@ -104,7 +103,7 @@ protected virtual IGraphActionResult InternalServerError(string errorMessage) /// IGraphActionResult. protected virtual IGraphActionResult NotFound(string message = null) { - return new RouteNotFoundGraphActionResult(message); + return GraphActionResult.NotFound(message); } /// @@ -115,7 +114,7 @@ protected virtual IGraphActionResult NotFound(string message = null) /// IGraphActionResult. protected virtual IGraphActionResult BadRequest(string message) { - return new BadRequestGraphActionResult(message); + return GraphActionResult.BadRequest(message); } /// @@ -137,7 +136,7 @@ protected virtual IGraphActionResult BadRequest(InputModelStateDictionary modelS /// IGraphActionResult. protected virtual IGraphActionResult Unauthorized(string message = null, string errorCode = null) { - return new UnauthorizedGraphActionResult(message, errorCode); + return GraphActionResult.Unauthorized(message, errorCode); } /// @@ -150,7 +149,7 @@ protected virtual IGraphActionResult Unauthorized(string message = null, string /// IGraphActionResult. protected virtual BatchBuilder StartBatch() { - return new BatchBuilder(this.Request.InvocationContext.Field); + return GraphActionResult.StartBatch(); } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Controllers/InputModel/ModelStateGenerator.cs b/src/graphql-aspnet/Controllers/InputModel/ModelStateGenerator.cs index dd26064d8..b7e670653 100644 --- a/src/graphql-aspnet/Controllers/InputModel/ModelStateGenerator.cs +++ b/src/graphql-aspnet/Controllers/InputModel/ModelStateGenerator.cs @@ -77,15 +77,10 @@ public InputModelStateDictionary CreateStateDictionary(IEnumerable - /// Processes an input object's attribute validation items. - /// - /// The input. - /// InputModelStateEntry. private InputModelStateEntry ValidateSingleArgument(ExecutionArgument input) { var entry = new InputModelStateEntry(input); - if (input.Value == null || input.Argument.ArgumentModifiers.IsSourceParameter()) + if (input.Value == null) { entry.ValidationState = InputModelValidationState.Skipped; return entry; diff --git a/src/graphql-aspnet/Controllers/RuntimeFieldExecutionController.cs b/src/graphql-aspnet/Controllers/RuntimeFieldExecutionController.cs new file mode 100644 index 000000000..0140c97f7 --- /dev/null +++ b/src/graphql-aspnet/Controllers/RuntimeFieldExecutionController.cs @@ -0,0 +1,22 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Controllers +{ + using GraphQL.AspNet.Attributes; + + /// + /// A special controller instance for executing runtime configured controller + /// actions (e.g. minimal api defined fields and type exensions). + /// + [GraphRoot] + internal sealed class RuntimeFieldExecutionController : GraphController + { + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Directives/ActionResults/DirectiveOkActionResult.cs b/src/graphql-aspnet/Directives/ActionResults/DirectiveOkActionResult.cs deleted file mode 100644 index a575cd8ab..000000000 --- a/src/graphql-aspnet/Directives/ActionResults/DirectiveOkActionResult.cs +++ /dev/null @@ -1,34 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Directives.ActionResults -{ - using System.Threading.Tasks; - using GraphQL.AspNet.Execution.Contexts; - using GraphQL.AspNet.Interfaces.Controllers; - - /// - /// An action reslt indicating a successful completion of a directive with no returned value. - /// - public class DirectiveOkActionResult : IGraphActionResult - { - /// - /// Initializes a new instance of the class. - /// - public DirectiveOkActionResult() - { - } - - /// - public Task CompleteAsync(SchemaItemResolutionContext context) - { - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Directives/GraphDirective.cs b/src/graphql-aspnet/Directives/GraphDirective.cs index 3cfb23c47..6045b7e5f 100644 --- a/src/graphql-aspnet/Directives/GraphDirective.cs +++ b/src/graphql-aspnet/Directives/GraphDirective.cs @@ -28,7 +28,7 @@ public abstract partial class GraphDirective : GraphControllerBase internal override Task InvokeActionAsync( - IGraphFieldResolverMethod actionToInvoke, + IGraphFieldResolverMetaData actionToInvoke, SchemaItemResolutionContext context) { Validation.ThrowIfNull(context, nameof(context)); diff --git a/src/graphql-aspnet/Directives/GraphDirective_ActionResults.cs b/src/graphql-aspnet/Directives/GraphDirective_ActionResults.cs index eec00bc0e..9405c519e 100644 --- a/src/graphql-aspnet/Directives/GraphDirective_ActionResults.cs +++ b/src/graphql-aspnet/Directives/GraphDirective_ActionResults.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Directives { using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Directives.ActionResults; + using GraphQL.AspNet.Controllers.ActionResults; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Interfaces.Execution; @@ -25,7 +25,7 @@ public abstract partial class GraphDirective : GraphControllerBaseIGraphActionResult<TResult>. protected virtual IGraphActionResult Ok() { - return new DirectiveOkActionResult(); + return GraphActionResult.Ok(); } /// @@ -36,7 +36,7 @@ protected virtual IGraphActionResult Ok() /// IGraphActionResult. protected virtual IGraphActionResult Cancel() { - return new DirectiveCancelPipelineResult(); + return GraphActionResult.Cancel(); } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Directives/RuntimeExecutionDirective.cs b/src/graphql-aspnet/Directives/RuntimeExecutionDirective.cs new file mode 100644 index 000000000..a2119052a --- /dev/null +++ b/src/graphql-aspnet/Directives/RuntimeExecutionDirective.cs @@ -0,0 +1,40 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Controllers +{ + using GraphQL.AspNet.Common.Generics; + using GraphQL.AspNet.Directives; + using GraphQL.AspNet.Interfaces.Execution; + + /// + /// A special directive instance for executing runtime configured directive + /// actions (e.g. minimal api defined directives). + /// + internal sealed class RuntimeExecutionDirective : GraphDirective + { + /// + protected override object CreateAndInvokeAction(IGraphFieldResolverMetaData resolverMetaData, object[] invocationArguments) + { + // minimal api resolvers are allowed to be static since there is no + // extra context to setup or make available such as 'this.User' etc. + if (resolverMetaData.Method.IsStatic) + { + var invoker = InstanceFactory.CreateStaticMethodInvoker(resolverMetaData.Method); + return invoker(invocationArguments); + } + else + { + var invoker = InstanceFactory.CreateInstanceMethodInvoker(resolverMetaData.Method); + var instance = InstanceFactory.CreateInstance(resolverMetaData.Method.DeclaringType); + return invoker(ref instance, invocationArguments); + } + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/DefaultGraphEventLogger.cs b/src/graphql-aspnet/Engine/DefaultGraphEventLogger.cs index 2b9ea655e..b6d1e8ecd 100644 --- a/src/graphql-aspnet/Engine/DefaultGraphEventLogger.cs +++ b/src/graphql-aspnet/Engine/DefaultGraphEventLogger.cs @@ -224,7 +224,7 @@ public virtual void FieldResolutionCompleted(FieldResolutionContext context) } /// - public virtual void ActionMethodInvocationRequestStarted(IGraphFieldResolverMethod action, IDataRequest request) + public virtual void ActionMethodInvocationRequestStarted(IGraphFieldResolverMetaData action, IDataRequest request) { if (!this.IsEnabled(LogLevel.Trace)) return; @@ -234,7 +234,7 @@ public virtual void ActionMethodInvocationRequestStarted(IGraphFieldResolverMeth } /// - public virtual void ActionMethodModelStateValidated(IGraphFieldResolverMethod action, IDataRequest request, InputModelStateDictionary modelState) + public virtual void ActionMethodModelStateValidated(IGraphFieldResolverMetaData action, IDataRequest request, InputModelStateDictionary modelState) { if (!this.IsEnabled(LogLevel.Trace)) return; @@ -244,7 +244,7 @@ public virtual void ActionMethodModelStateValidated(IGraphFieldResolverMethod ac } /// - public virtual void ActionMethodInvocationException(IGraphFieldResolverMethod action, IDataRequest request, Exception exception) + public virtual void ActionMethodInvocationException(IGraphFieldResolverMetaData action, IDataRequest request, Exception exception) { if (!this.IsEnabled(LogLevel.Error)) return; @@ -254,7 +254,7 @@ public virtual void ActionMethodInvocationException(IGraphFieldResolverMethod ac } /// - public virtual void ActionMethodUnhandledException(IGraphFieldResolverMethod action, IDataRequest request, Exception exception) + public virtual void ActionMethodUnhandledException(IGraphFieldResolverMetaData action, IDataRequest request, Exception exception) { if (!this.IsEnabled(LogLevel.Error)) return; @@ -264,7 +264,7 @@ public virtual void ActionMethodUnhandledException(IGraphFieldResolverMethod act } /// - public virtual void ActionMethodInvocationCompleted(IGraphFieldResolverMethod action, IDataRequest request, object result) + public virtual void ActionMethodInvocationCompleted(IGraphFieldResolverMetaData action, IDataRequest request, object result) { if (!this.IsEnabled(LogLevel.Trace)) return; diff --git a/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory.cs b/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory.cs new file mode 100644 index 000000000..0be37837e --- /dev/null +++ b/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory.cs @@ -0,0 +1,233 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Engine +{ + using System; + using System.Collections.Generic; + using System.Linq; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Configuration.Startup; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Engine; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Schemas.TypeSystem; + using Microsoft.Extensions.DependencyInjection; + + /// + /// The default schema factory, capable of creating singleton instances of + /// schemas, fully populated and ready to serve requests. + /// + /// The type of the schema being built. + public partial class DefaultGraphQLSchemaFactory : IGraphQLSchemaFactory + where TSchema : class, ISchema + { + /// + /// Initializes a new instance of the class. + /// + public DefaultGraphQLSchemaFactory() + : this(true, true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// if set to true the specification defined + /// directives (e.g. @skip, @include etc.) will be automatically added to any + /// created schema instance. + /// if set to true any discovered + /// type system directives will be applied to their target schema items. + public DefaultGraphQLSchemaFactory( + bool includeBuiltInDirectives = true, + bool processTypeSystemDirectives = true) + { + this.IncludeBuiltInDirectives = includeBuiltInDirectives; + this.ProcessTypeSystemDirectives = processTypeSystemDirectives; + } + + /// + public virtual TSchema CreateInstance( + IServiceScope serviceScope, + ISchemaConfiguration configuration, + IEnumerable typesToRegister = null, + IEnumerable runtimeItemDefinitions = null, + IEnumerable schemaExtensions = null) + { + Validation.ThrowIfNull(serviceScope, nameof(serviceScope)); + Validation.ThrowIfNull(configuration, nameof(configuration)); + + this.ServiceProvider = serviceScope.ServiceProvider; + + runtimeItemDefinitions = runtimeItemDefinitions ?? Enumerable.Empty(); + typesToRegister = typesToRegister ?? Enumerable.Empty(); + schemaExtensions = schemaExtensions ?? Enumerable.Empty(); + + this.Schema = GraphSchemaBuilder.BuildSchema(this.ServiceProvider); + + if (this.Schema.IsInitialized) + return this.Schema; + + this.Schema.Configuration.Merge(configuration); + this.MakerFactory = this.CreateMakerFactory(); + + // Step 1: Ensure all the bare bones requirements are set + // -------------------------------------- + this.EnsureBaseLineDependencies(); + + // Step 2a: Figure out which types are scalars and register them first + // -------------------------------------- + // This speeds up the generation process since they don't have to be found + // and validated when trying to import objects or interfaces. + var scalarsToRegister = new List(); + var nonScalarsToRegister = new List(); + foreach (var regType in typesToRegister) + { + if (Validation.IsCastable(GlobalTypes.FindBuiltInScalarType(regType.Type) ?? regType.Type)) + scalarsToRegister.Add(regType); + else + nonScalarsToRegister.Add(regType); + } + + // Step 2b: Register all explicitly declared scalars first + // -------------------------------------- + foreach (var type in scalarsToRegister) + this.EnsureGraphType(type.Type); + + // Step 2c: Register other graph types + // -------------------------------------- + foreach (var type in nonScalarsToRegister) + this.EnsureGraphType(type.Type, type.TypeKind); + + // Step 3: Register any runtime defined items (e.g. minimal api registrations) + // -------------------------------------- + foreach (var itemDef in runtimeItemDefinitions) + this.AddRuntimeSchemaItemDefinition(itemDef); + + // Step 4: execute any assigned schema extensions + // -------------------------------------- + // this includes any late bound directives added to the type system via .ApplyDirective() + foreach (var extension in schemaExtensions) + extension.EnsureSchema(this.Schema); + + // VALIDATE: Run initial validation checks to ensure schema integrity BEFORE type + // system directives are applied + // -------------------------------------- + this.ValidateSchemaIntegrity(); + + // Step 5: apply all queued type system directives to the now filled schema + // -------------------------------------- + if (this.ProcessTypeSystemDirectives) + { + var totalApplied = this.ApplyTypeSystemDirectives(); + + // VALIDATE: Run final validations to ensure the schema is internally consistant + // -------------------------------------- + if (totalApplied > 0) + this.ValidateSchemaIntegrity(); + } + + // Step 6: Rebuild introspection data to match the now completed schema instance + // -------------------------------------- + if (!this.Schema.Configuration.DeclarationOptions.DisableIntrospection) + this.RebuildIntrospectionData(); + + this.Schema.IsInitialized = true; + return this.Schema; + } + + /// + /// Creates a new maker factory that will supply templates and graph type + /// makers during the schema generation process. + /// + /// DefaultGraphQLTypeMakerFactory. + protected virtual GraphTypeMakerFactory CreateMakerFactory() + { + return new GraphTypeMakerFactory(this.Schema); + } + + /// + /// Ensures the target schema has all the "specification required" pieces and dependencies + /// accounted for. + /// + protected virtual void EnsureBaseLineDependencies() + { + // all schemas depend on String because of the __typename field + this.EnsureGraphType(typeof(string)); + + // ensure top level schema directives are accounted for + foreach (var directive in this.Schema.GetType().ExtractAppliedDirectives()) + { + this.Schema.AppliedDirectives.Add(directive); + } + + foreach (var appliedDirective in this.Schema.AppliedDirectives.Where(x => x.DirectiveType != null)) + { + this.EnsureGraphType( + appliedDirective.DirectiveType, + TypeKind.DIRECTIVE); + } + + // ensure all globally required directives are added + if (this.IncludeBuiltInDirectives) + { + foreach (var type in Constants.GlobalDirectives) + this.EnsureGraphType(type); + } + + // all schemas must support query + this.EnsureGraphOperationType(GraphOperationType.Query); + } + + /// + /// Gets the service provider that will be used to create service instances + /// needed to generate the schema + /// + /// The service provider. + protected virtual IServiceProvider ServiceProvider { get; private set; } + + /// + /// Gets the configuration settings that will be used to generate + /// the schema. + /// + /// The schema configuration instance. + protected virtual ISchemaConfiguration Configuration => this.Schema.Configuration; + + /// + /// Gets the schema instance being built by this factory instance. + /// + /// The schema. + protected virtual TSchema Schema { get; private set; } + + /// + /// Gets or sets a factory instnace that can serve up instances of various + /// makers to generate graph types for the building . + /// + /// The maker factory to use in this instance. + protected virtual GraphTypeMakerFactory MakerFactory { get; set; } + + /// + /// Gets a value indicating whether the specification-defined, built-in directives (e.g. skip, include etc.) + /// are automatically added to the schema that is generated. + /// + /// true if [include built in directives]; otherwise, false. + protected virtual bool IncludeBuiltInDirectives { get; } + + /// + /// Gets a value indicating whether any assigned type system directives will be processed when a schema instance + /// is built. + /// + /// true if type system directives should be processed for any schema items; otherwise, false. + protected virtual bool ProcessTypeSystemDirectives { get; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_Controllers.cs b/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_Controllers.cs new file mode 100644 index 000000000..27fb4d02d --- /dev/null +++ b/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_Controllers.cs @@ -0,0 +1,242 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Engine +{ + using System; + using System.Collections.Generic; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// The default schema factory, capable of creating singleton instances of + /// schemas, fully populated and ready to serve requests. + /// + public partial class DefaultGraphQLSchemaFactory + { + /// + /// Incorpates the templated controller into the schema. + /// + /// The template of the controller to add. + protected virtual void AddController(IGraphControllerTemplate template) + { + Validation.ThrowIfNull(template, nameof(template)); + + template.Parse(); + template.ValidateOrThrow(); + + foreach (var action in template.Actions) + this.AddAction(action); + + foreach (var extension in template.Extensions) + this.AddTypeExtension(extension); + } + + /// + /// Adds the type extension to the schema for the configured concrete type. If the type + /// is not registered to the schema the field extension is queued for when it is added (if ever). + /// + /// The extension to add. + protected virtual void AddTypeExtension(IGraphFieldTemplate extension) + { + var fieldMaker = this.MakerFactory.CreateFieldMaker(); + var fieldResult = fieldMaker.CreateField(extension); + + if (fieldResult != null) + { + this.Schema.KnownTypes.EnsureGraphFieldExtension(extension.SourceObjectType, fieldResult.Field); + this.EnsureDependents(fieldResult); + } + } + + /// + /// Adds the to the schema. Any required parent fields + /// will be automatically created if necessary to ensure proper nesting. + /// + /// The action to add to the schema. + protected virtual void AddAction(IGraphFieldTemplate action) + { + var operation = action.Route.RootCollection.ToGraphOperationType(); + if (this.Schema.Configuration.DeclarationOptions.AllowedOperations.Contains(operation)) + { + this.EnsureGraphOperationType(operation); + var parentField = this.AddOrRetrieveControllerRoutePath(action); + this.AddActionAsField(parentField, action); + } + else + { + throw new ArgumentOutOfRangeException( + nameof(action), + $"The '{action.InternalName}' action's operation root ({action.Route.RootCollection}) is not " + + $"allowed by the target schema (Name: {this.Schema.Name})."); + } + } + + /// + /// Inspects the root and ensures that any intermediate, virtual fields + /// are accounted for and returns a reference to the immediate parent this action should be added to. + /// + /// The action. + /// IGraphField. + protected virtual IObjectGraphType AddOrRetrieveControllerRoutePath(IGraphFieldTemplate action) + { + var pathSegments = action.Route.GenerateParentPathSegments(); + + // loop through all parent path parts of this action + // creating virtual fields as necessary or using existing ones and adding on to them + IObjectGraphType parentType = this.Schema.Operations[action.Route.RootCollection.ToGraphOperationType()]; + + for (var i = 0; i < pathSegments.Count; i++) + { + var segment = pathSegments[i]; + var formattedName = this.Schema.Configuration.DeclarationOptions.GraphNamingFormatter.FormatFieldName(segment.Name); + if (parentType.Fields.ContainsKey(formattedName)) + { + var field = parentType[formattedName]; + var foundType = Schema.KnownTypes.FindGraphType(field.TypeExpression.TypeName); + + var ogt = foundType as IObjectGraphType; + if (ogt != null) + { + if (ogt.IsVirtual) + { + parentType = ogt; + continue; + } + + throw new GraphTypeDeclarationException( + $"The action '{action.Route}' attempted to nest itself under the {foundType.Kind} graph type '{foundType.Name}', which is returned by " + + $"the route '{field.Route}'. Actions can only be added to virtual graph types created by their parent controller."); + } + + if (foundType != null) + { + throw new GraphTypeDeclarationException( + $"The action '{action.Route.Path}' attempted to nest itself under the graph type '{foundType.Name}'. {foundType.Kind} graph types cannot " + + "accept fields."); + } + else + { + throw new GraphTypeDeclarationException( + $"The action '{action.Route.Path}' attempted to nest itself under the field '{field.Route}' but no graph type was found " + + "that matches its type."); + } + } + + parentType = this.CreateVirtualFieldOnParent( + parentType, + formattedName, + segment, + i == 0 ? action.Parent : null); + } + + return parentType; + } + + /// + /// Performs an out-of-band append of a new graph field to a parent. Accounts for type updates in this schema ONLY. + /// + /// the parent type to add the new field to. + /// Name of the field. + /// The path segment to represent the new field. + /// The definition item from which graph attributes should be used, if any. Attributes will be set to an empty string if not supplied. + /// The type associated with the field added to the parent type. + protected virtual IObjectGraphType CreateVirtualFieldOnParent( + IObjectGraphType parentType, + string fieldName, + SchemaItemPath path, + ISchemaItemTemplate definition = null) + { + var childField = new VirtualGraphField( + parentType, + fieldName, + path, + this.MakeSafeTypeNameFromRoutePath(path)) + { + IsDepreciated = false, + DepreciationReason = string.Empty, + Description = definition?.Description ?? string.Empty, + }; + + parentType.Extend(childField); + this.Schema.KnownTypes.EnsureGraphType(childField.AssociatedGraphType); + this.EnsureDependents(childField); + + return childField.AssociatedGraphType; + } + + /// + /// Makes the unique route being used for this virtual field type safe, removing special control characters + /// but retaining its uniqueness. + /// + /// The path. + /// System.String. + protected virtual string MakeSafeTypeNameFromRoutePath(SchemaItemPath path) + { + var segments = new List(); + foreach (var pathSegmentName in path) + { + switch (pathSegmentName) + { + case Constants.Routing.QUERY_ROOT: + segments.Add(Constants.ReservedNames.QUERY_TYPE_NAME); + break; + + case Constants.Routing.MUTATION_ROOT: + segments.Add(Constants.ReservedNames.MUTATION_TYPE_NAME); + break; + + case Constants.Routing.SUBSCRIPTION_ROOT: + segments.Add(Constants.ReservedNames.SUBSCRIPTION_TYPE_NAME); + break; + + default: + segments.Add(this.Schema.Configuration.DeclarationOptions.GraphNamingFormatter.FormatGraphTypeName(pathSegmentName)); + break; + } + } + + segments.Reverse(); + return string.Join("_", segments); + } + + /// + /// Iterates the given and adds + /// all found types to the type system for this . Generates + /// a field reference on the provided parent with a resolver pointing to the provided graph action. + /// + /// The parent which will own the generated action field. + /// The action. + protected virtual void AddActionAsField(IObjectGraphType parentType, IGraphFieldTemplate action) + { + // apend the action as a field on the parent + var maker = this.MakerFactory.CreateFieldMaker(); + var fieldResult = maker.CreateField(action); + + if (fieldResult != null) + { + if (parentType.Fields.ContainsKey(fieldResult.Field.Name)) + { + throw new GraphTypeDeclarationException( + $"The '{parentType.Kind}' graph type '{parentType.Name}' already contains a field named '{fieldResult.Field.Name}'. " + + $"The action method '{action.InternalName}' cannot be added to the graph type with the same name."); + } + + parentType.Extend(fieldResult.Field); + this.EnsureDependents(fieldResult); + } + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_Directives.cs b/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_Directives.cs new file mode 100644 index 000000000..aab3b2468 --- /dev/null +++ b/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_Directives.cs @@ -0,0 +1,37 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Engine +{ + using System.Linq; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// The default schema factory, capable of creating singleton instances of + /// schemas, fully populated and ready to serve requests. + /// + public partial class DefaultGraphQLSchemaFactory + { + /// + /// Inspects all graph types, fields, arguments and directives for any pending + /// type system directives. When found, applies each directive as approprate to the + /// target schema item. + /// + /// The total number of type system directives across the entire schema. + protected virtual int ApplyTypeSystemDirectives() + { + var processor = new DirectiveProcessorTypeSystem( + this.ServiceProvider, + new QuerySession()); + + return processor.ApplyDirectives(this.Schema); + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_GraphTypes.cs b/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_GraphTypes.cs new file mode 100644 index 000000000..8dbef2724 --- /dev/null +++ b/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_GraphTypes.cs @@ -0,0 +1,184 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Engine +{ + using System; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Engine; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using GraphQL.AspNet.ServerExtensions.MultipartRequests.Model; + + /// + /// The default schema factory, capable of creating singleton instances of + /// schemas, fully populated and ready to serve requests. + /// + public partial class DefaultGraphQLSchemaFactory + { + /// + /// Ensures that the root operation type (query, mutation etc.) exists on this schema and the associated virtual + /// type representing it also exists in the schema's type collection. + /// + /// Type of the operation. + protected virtual void EnsureGraphOperationType(GraphOperationType operationType) + { + if (operationType == GraphOperationType.Unknown) + { + throw new ArgumentOutOfRangeException($"The operation type '{operationType}' is " + + $"not supported by graphql."); + } + + if (!this.Schema.Operations.ContainsKey(operationType)) + { + var operation = new GraphOperation(operationType); + this.Schema.KnownTypes.EnsureGraphType(operation); + this.Schema.Operations.Add(operation.OperationType, operation); + } + } + + /// + /// An idempodent method that will parse and add the given type into the schema + /// in a manner appropriate to its definition and expected type kind. + /// + /// The type to ensure exists in the graph. + /// The type kind to add the provided + /// as. If the provided type can only be matched to one type kind (such as enums), this + /// value is ignored. + /// + /// + /// + /// is required to differentiate OBJECT from INPUT_OBJECT registrations + /// for explicit type inclusions. + /// + protected virtual void EnsureGraphType(Type type, TypeKind? typeKind = null) + { + Validation.ThrowIfNull(type, nameof(type)); + try + { + this.EnsureGraphTypeInternal(type, typeKind); + } + catch (GraphTypeDeclarationException ex) + { + if (ex.FailedObjectType == type) + throw; + + // wrap a thrown exception to be nested within this type that was being parsed + throw new GraphTypeDeclarationException( + $"An error occured while trying to add a dependent of '{type.FriendlyName()}' " + + $"to the target schema. See inner exception for details.", + type, + ex); + } + } + + /// + /// A method where performs its actual work. + /// Seperated to allow exceptions to be trapped and bubbled correctly. + /// + /// The type to ensure exists in the graph. + /// The type kind to add the provided + /// as. If the provided type can only be matched to one type kind (such as enums), this + /// value is ignored. + /// + /// + /// + /// is required to differentiate OBJECT from INPUT_OBJECT registrations + /// for explicit type inclusions. + /// + protected virtual void EnsureGraphTypeInternal(Type type, TypeKind? typeKind = null) + { + type = GraphValidation.EliminateWrappersFromCoreType(type); + + // if the type is already registered, early exit no point in running it through again + var existingGraphType = this.Schema.KnownTypes.FindGraphType(type); + if (existingGraphType != null) + { + if (existingGraphType.Kind == TypeKind.SCALAR) + return; + + if (this.Schema.KnownTypes.Contains(type, typeKind)) + return; + } + + var template = this.MakerFactory.MakeTemplate(type, typeKind); + if (template is IGraphControllerTemplate controllerTemplate) + { + this.AddController(controllerTemplate); + return; + } + + GraphTypeCreationResult makerResult = null; + var maker = this.MakerFactory.CreateTypeMaker(type, typeKind); + if (maker != null) + { + // if a maker can be assigned for this graph type + // create the graph type directly + makerResult = maker.CreateGraphType(template); + } + + this.AddMakerResult(makerResult); + } + + /// + /// Processes the result of creating a graph type from a template and adds its contents to the schema. + /// + /// The maker result to process. + protected virtual void AddMakerResult(GraphTypeCreationResult makerResult) + { + if (makerResult != null) + { + this.Schema.KnownTypes.EnsureGraphType(makerResult.GraphType, makerResult.ConcreteType); + this.EnsureDependents(makerResult); + } + } + + /// + /// Ensures the union proxy is incorporated into the schema appropriately. This method is used + /// when a union is discovered when parsing various field declarations. + /// + /// The union to include. + protected virtual void EnsureUnion(IGraphUnionProxy union) + { + Validation.ThrowIfNull(union, nameof(union)); + var maker = this.MakerFactory.CreateUnionMaker(); + var result = maker.CreateUnionFromProxy(union); + + this.Schema.KnownTypes.EnsureGraphType(result.GraphType, result.ConcreteType); + } + + /// + /// Ensures the dependents in the provided collection are part of the target . + /// + /// The dependency set to inspect. + protected virtual void EnsureDependents(IGraphItemDependencies dependencySet) + { + foreach (var abstractType in dependencySet.AbstractGraphTypes) + { + this.Schema.KnownTypes.EnsureGraphType(abstractType); + } + + foreach (var dependent in dependencySet.DependentTypes) + { + if (dependent.Type != null) + this.EnsureGraphType(dependent.Type, dependent.ExpectedKind); + else if (dependent.UnionDeclaration != null) + this.EnsureUnion(dependent.UnionDeclaration); + } + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_Introspection.cs b/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_Introspection.cs new file mode 100644 index 000000000..bbab53314 --- /dev/null +++ b/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_Introspection.cs @@ -0,0 +1,77 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Engine +{ + using System; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Introspection; + using GraphQL.AspNet.Schemas.TypeSystem.Introspection.Fields; + using GraphQL.AspNet.Schemas.TypeSystem.Introspection.Model; + + /// + /// The default schema factory, capable of creating singleton instances of + /// schemas, fully populated and ready to serve requests. + /// + public partial class DefaultGraphQLSchemaFactory + { + /// + /// Clears, builds and caches the introspection metadata used to describe this schema. If introspection + /// fields have not been added to the schema this method does nothing. No changes to the schema + /// items themselves happens during this method call. + /// + protected virtual void RebuildIntrospectionData() + { + if (this.Schema.Configuration.DeclarationOptions.DisableIntrospection) + return; + + this.EnsureGraphOperationType(GraphOperationType.Query); + this.AddIntrospectionFields(); + + var queryType = this.Schema.Operations[GraphOperationType.Query]; + if (!queryType.Fields.ContainsKey(Constants.ReservedNames.SCHEMA_FIELD)) + return; + + var field = queryType.Fields[Constants.ReservedNames.SCHEMA_FIELD] as Introspection_SchemaField; + field.IntrospectedSchema.Rebuild(); + } + + /// + /// Adds the internal introspection fields to the query operation type if and only if the contained schema allows + /// it through its internal configuration. This method is idempotent. + /// + protected virtual void AddIntrospectionFields() + { + this.EnsureGraphOperationType(GraphOperationType.Query); + var queryField = this.Schema.Operations[GraphOperationType.Query]; + + // Note: introspection fields are defined by the graphql spec, no custom name or item formatting is allowed + // for Type and field name formatting. + // spec: https://graphql.github.io/graphql-spec/October2021/#sec-Schema-Introspection + if (!queryField.Fields.ContainsKey(Constants.ReservedNames.SCHEMA_FIELD)) + { + var introspectedSchema = new IntrospectedSchema(this.Schema); + queryField.Extend(new Introspection_SchemaField(introspectedSchema)); + queryField.Extend(new Introspection_TypeGraphField(introspectedSchema)); + + this.EnsureGraphType(typeof(string)); + this.EnsureGraphType(typeof(bool)); + this.Schema.KnownTypes.EnsureGraphType(new Introspection_DirectiveLocationType(), typeof(DirectiveLocation)); + this.Schema.KnownTypes.EnsureGraphType(new Introspection_DirectiveType(), typeof(IntrospectedDirective)); + this.Schema.KnownTypes.EnsureGraphType(new Introspection_EnumValueType(), typeof(IntrospectedEnumValue)); + this.Schema.KnownTypes.EnsureGraphType(new Introspection_FieldType(), typeof(IntrospectedField)); + this.Schema.KnownTypes.EnsureGraphType(new Introspection_InputValueType(), typeof(IntrospectedInputValueType)); + this.Schema.KnownTypes.EnsureGraphType(new Introspection_SchemaType(), typeof(IntrospectedSchema)); + this.Schema.KnownTypes.EnsureGraphType(new Introspection_TypeKindType(), typeof(TypeKind)); + this.Schema.KnownTypes.EnsureGraphType(new Introspection_TypeType(), typeof(IntrospectedType)); + } + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_RuntimeDefinitions.cs b/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_RuntimeDefinitions.cs new file mode 100644 index 000000000..fedd6e9a4 --- /dev/null +++ b/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_RuntimeDefinitions.cs @@ -0,0 +1,75 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Engine +{ + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// The default schema factory, capable of creating instances of + /// schemas, fully populated and ready to serve requests. + /// + public partial class DefaultGraphQLSchemaFactory + { + /// + /// Adds a runtime declared field (with its assigned resolver) as a field in the schema. + /// + /// The runtime defined item to add to the schema. + protected virtual void AddRuntimeSchemaItemDefinition(IGraphQLRuntimeSchemaItemDefinition itemDefinition) + { + switch (itemDefinition) + { + case IGraphQLRuntimeResolvedFieldDefinition fieldDef: + this.AddRuntimeFieldDefinition(fieldDef); + break; + + case IGraphQLRuntimeDirectiveDefinition directiveDef: + this.AddRuntimeDirectiveDefinition(directiveDef); + break; + + // TODO: Add warning log entries for unsupported item defs. + } + } + + /// + /// Adds a new directive to the schema based on the runtime definition created during program startup. + /// + /// The directive definition. + protected virtual void AddRuntimeDirectiveDefinition(IGraphQLRuntimeDirectiveDefinition directiveDefinition) + { + Validation.ThrowIfNull(directiveDefinition, nameof(directiveDefinition)); + var template = new RuntimeGraphDirectiveTemplate(directiveDefinition); + + template.Parse(); + template.ValidateOrThrow(); + + var maker = this.MakerFactory.CreateTypeMaker(kind: TypeKind.DIRECTIVE); + var result = maker.CreateGraphType(template); + this.AddMakerResult(result); + } + + /// + /// Adds a new field to the schema based on the runtime definition created during program startup. + /// + /// The field definition to add. + protected virtual void AddRuntimeFieldDefinition(IGraphQLRuntimeResolvedFieldDefinition fieldDefinition) + { + Validation.ThrowIfNull(fieldDefinition, nameof(fieldDefinition)); + var template = new RuntimeGraphControllerTemplate(fieldDefinition); + + template.Parse(); + template.ValidateOrThrow(); + + this.AddController(template); + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_Validation.cs b/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_Validation.cs new file mode 100644 index 000000000..de6aff7ca --- /dev/null +++ b/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_Validation.cs @@ -0,0 +1,39 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Engine +{ + using System; + using System.Collections.Generic; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Schemas.Generation.SchemaItemValidators; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// The default schema factory, capable of creating singleton instances of + /// schemas, fully populated and ready to serve requests. + /// + public partial class DefaultGraphQLSchemaFactory + { + /// + /// Validates each registered type, field, argument and directive to ensure that its + /// internally consistance with itself and that the schema is in a usable state. + /// + protected virtual void ValidateSchemaIntegrity() + { + var allItems = this.Schema.AllSchemaItems(includeDirectives: true); + + foreach (var item in allItems) + { + var validator = SchemaItemValidationFactory.CreateValidator(item); + validator.ValidateOrThrow(item, this.Schema); + } + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/DefaultGraphTypeMakerProvider.cs b/src/graphql-aspnet/Engine/DefaultGraphTypeMakerProvider.cs deleted file mode 100644 index bdded513c..000000000 --- a/src/graphql-aspnet/Engine/DefaultGraphTypeMakerProvider.cs +++ /dev/null @@ -1,95 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Engine -{ - using System; - using System.Reflection; - using GraphQL.AspNet.Common; - using GraphQL.AspNet.Common.Extensions; - using GraphQL.AspNet.Common.Generics; - using GraphQL.AspNet.Engine.TypeMakers; - using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Interfaces.Engine; - using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Schemas.TypeSystem; - - /// - /// An abstract factory for creating type makers using all the default type makers types. - /// - public class DefaultGraphTypeMakerProvider : IGraphTypeMakerProvider - { - /// - public virtual IGraphFieldMaker CreateFieldMaker(ISchema schema) - { - return new GraphFieldMaker(schema); - } - - /// - public virtual IGraphTypeMaker CreateTypeMaker(ISchema schema, TypeKind kind) - { - if (schema == null) - return null; - - switch (kind) - { - case TypeKind.DIRECTIVE: - return new DirectiveMaker(schema); - - case TypeKind.SCALAR: - return new ScalarGraphTypeMaker(); - - case TypeKind.OBJECT: - return new ObjectGraphTypeMaker(schema); - - case TypeKind.INTERFACE: - return new InterfaceGraphTypeMaker(schema); - - case TypeKind.ENUM: - return new EnumGraphTypeMaker(schema); - - case TypeKind.INPUT_OBJECT: - return new InputObjectGraphTypeMaker(schema); - - // note: unions cannot currently be made via the type maker stack - } - - return null; - } - - /// - public IUnionGraphTypeMaker CreateUnionMaker(ISchema schema) - { - return new UnionGraphTypeMaker(schema); - } - - /// - public IGraphUnionProxy CreateUnionProxyFromType(Type proxyType) - { - if (proxyType == null) - return null; - - IGraphUnionProxy proxy = null; - if (Validation.IsCastable(proxyType)) - { - var paramlessConstructor = proxyType.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); - if (paramlessConstructor == null) - { - throw new GraphTypeDeclarationException( - $"The union proxy type '{proxyType.FriendlyName()}' could not be instantiated. " + - "All union proxy types must declare a parameterless constructor."); - } - - proxy = InstanceFactory.CreateInstance(proxyType) as IGraphUnionProxy; - } - - return proxy; - } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/DefaultQueryOperationComplexityCalculator{TSchema}.cs b/src/graphql-aspnet/Engine/DefaultQueryOperationComplexityCalculator{TSchema}.cs index 98ba2c495..dac61868f 100644 --- a/src/graphql-aspnet/Engine/DefaultQueryOperationComplexityCalculator{TSchema}.cs +++ b/src/graphql-aspnet/Engine/DefaultQueryOperationComplexityCalculator{TSchema}.cs @@ -14,7 +14,7 @@ namespace GraphQL.AspNet.Engine using GraphQL.AspNet.Interfaces.Engine; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.TypeSystem; /// diff --git a/src/graphql-aspnet/Engine/DefaultScalarGraphTypeProvider.cs b/src/graphql-aspnet/Engine/DefaultScalarGraphTypeProvider.cs deleted file mode 100644 index a9c2b3154..000000000 --- a/src/graphql-aspnet/Engine/DefaultScalarGraphTypeProvider.cs +++ /dev/null @@ -1,286 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Engine -{ - using System; - using System.Collections.Generic; - using System.Linq; - using GraphQL.AspNet.Common; - using GraphQL.AspNet.Common.Extensions; - using GraphQL.AspNet.Common.Generics; - using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Interfaces.Engine; - using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; - using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Schemas.TypeSystem.Scalars; - - /// - /// A built-in, default collection of instances of objects; - /// the most fundamental unit of graphql. - /// - public class DefaultScalarGraphTypeProvider : IScalarGraphTypeProvider - { - private readonly List _scalarReferences; - private readonly IDictionary _scalarsByConcreteType; - private readonly IDictionary _scalarsByName; - - /// - /// Initializes a new instance of the class. - /// - public DefaultScalarGraphTypeProvider() - { - _scalarReferences = new List(); - _scalarsByConcreteType = new Dictionary(); - _scalarsByName = new Dictionary(); - - this.RegisterScalar(typeof(IntScalarType)); - this.RegisterScalar(typeof(LongScalarType)); - this.RegisterScalar(typeof(UIntScalarType)); - this.RegisterScalar(typeof(ULongScalarType)); - this.RegisterScalar(typeof(FloatScalarType)); - this.RegisterScalar(typeof(DoubleScalarType)); - this.RegisterScalar(typeof(DecimalScalarType)); - this.RegisterScalar(typeof(BooleanScalarType)); - this.RegisterScalar(typeof(StringScalarType)); - this.RegisterScalar(typeof(DateTimeScalarType)); - this.RegisterScalar(typeof(DateTimeOffsetScalarType)); - this.RegisterScalar(typeof(ByteScalarType)); - this.RegisterScalar(typeof(SByteScalarType)); - this.RegisterScalar(typeof(GuidScalarType)); - this.RegisterScalar(typeof(UriScalarType)); - this.RegisterScalar(typeof(GraphIdScalarType)); - this.RegisterScalar(typeof(ShortScalarType)); - this.RegisterScalar(typeof(UShortScalarType)); - -#if NET6_0_OR_GREATER - this.RegisterScalar(typeof(DateOnlyScalarType)); - this.RegisterScalar(typeof(TimeOnlyScalarType)); -#endif - } - - /// - public virtual bool IsLeaf(Type type) - { - if (type == null) - return false; - - if (type.IsEnum) - return true; - - return _scalarsByConcreteType.ContainsKey(type); - } - - /// - public virtual Type EnsureBuiltInTypeReference(Type type) - { - if (this.IsScalar(type)) - { - return _scalarsByConcreteType[type].PrimaryType; - } - - return type; - } - - /// - public virtual bool IsScalar(Type concreteType) - { - return concreteType != null && _scalarsByConcreteType.ContainsKey(concreteType); - } - - /// - public virtual bool IsScalar(string scalarName) - { - return scalarName != null && _scalarsByName.ContainsKey(scalarName); - } - - /// - public virtual Type RetrieveConcreteType(string scalarName) - { - if (this.IsScalar(scalarName)) - return _scalarsByName[scalarName].PrimaryType; - return null; - } - - /// - public virtual string RetrieveScalarName(Type concreteType) - { - if (this.IsScalar(concreteType)) - return _scalarsByConcreteType[concreteType].Name; - - return null; - } - - /// - public virtual IScalarGraphType CreateScalar(string scalarName) - { - if (this.IsScalar(scalarName)) - return this.CreateScalarFromInstanceType(_scalarsByName[scalarName].InstanceType); - - return null; - } - - /// - public virtual IScalarGraphType CreateScalar(Type concreteType) - { - if (this.IsScalar(concreteType)) - { - var primaryInstanceType = this.EnsureBuiltInTypeReference(concreteType); - return this.CreateScalarFromInstanceType(_scalarsByConcreteType[primaryInstanceType].InstanceType); - } - - return null; - } - - /// - /// Creates a new instance of the scalar from its formal type declaration. - /// - /// Type of the scalar. - /// IScalarGraphType. - protected virtual IScalarGraphType CreateScalarFromInstanceType(Type scalarType) - { - return InstanceFactory.CreateInstance(scalarType) as IScalarGraphType; - } - - private ScalarReference FindReferenceByImplementationType(Type type) - { - var primaryType = this.EnsureBuiltInTypeReference(type); - if (this.IsScalar(primaryType)) - return _scalarsByConcreteType[primaryType]; - - return null; - } - - /// - public virtual void RegisterCustomScalar(Type scalarType) - { - this.RegisterScalar(scalarType); - } - - /// - /// Internal logic that must be excuted to register a scalar, regardless of what - /// any subclass may do. - /// - /// Type of the scalar. - private void RegisterScalar(Type scalarType) - { - Validation.ThrowIfNull(scalarType, nameof(scalarType)); - - if (!Validation.IsCastable(scalarType)) - { - throw new GraphTypeDeclarationException( - $"The scalar must implement the interface '{typeof(IScalarGraphType).FriendlyName()}'."); - } - - var paramlessConstructor = scalarType.GetConstructor(new Type[0]); - if (paramlessConstructor == null) - { - throw new GraphTypeDeclarationException( - "The scalar must declare a public, parameterless constructor."); - } - - var graphType = InstanceFactory.CreateInstance(scalarType) as IScalarGraphType; - if (string.IsNullOrWhiteSpace(graphType.Name)) - { - throw new GraphTypeDeclarationException( - "The scalar must supply a name that is not null or whitespace."); - } - - if (!GraphValidation.IsValidGraphName(graphType.Name)) - { - throw new GraphTypeDeclarationException( - $"The scalar must supply a name that that conforms to the standard rules for GraphQL. (Regex: {Constants.RegExPatterns.NameRegex})"); - } - - if (graphType.Kind != TypeKind.SCALAR) - { - throw new GraphTypeDeclarationException( - $"The scalar's type kind must be set to '{nameof(TypeKind.SCALAR)}'."); - } - - if (graphType.ObjectType == null) - { - throw new GraphTypeDeclarationException( - $"The scalar must supply a value for '{nameof(graphType.ObjectType)}', is cannot be null."); - } - - if (graphType.SourceResolver == null) - { - throw new GraphTypeDeclarationException( - $"The scalar must supply a value for '{nameof(graphType.SourceResolver)}' that can convert data from a " + - $"query into the primary object type of '{graphType.ObjectType.FriendlyName()}'."); - } - - if (graphType.ValueType == ScalarValueType.Unknown) - { - throw new GraphTypeDeclarationException( - $"The scalar must supply a value for '{nameof(graphType.ValueType)}'. This lets the validation engine " + - "know what data types submitted on a user query could be parsed into a value for this scale."); - } - - if (graphType.OtherKnownTypes == null) - { - throw new GraphTypeDeclarationException( - $"Custom scalars must supply a value for '{nameof(graphType.OtherKnownTypes)}', it cannot be null. " + - $"Use '{nameof(TypeCollection)}.{nameof(TypeCollection.Empty)}' if there are no other known types."); - } - - if (graphType.AppliedDirectives == null || graphType.AppliedDirectives.Parent != graphType) - { - throw new GraphTypeDeclarationException( - $"Custom scalars must supply a value for '{nameof(graphType.AppliedDirectives)}', it cannot be null. " + - $"The '{nameof(IAppliedDirectiveCollection.Parent)}' property of the directive collection must also be set to the scalar itself."); - } - - var isAScalarAlready = this.IsScalar(graphType.Name); - if (isAScalarAlready) - { - throw new GraphTypeDeclarationException( - $"A scalar named '{graphType.Name}' already exists in this graphql instance."); - } - - var reference = this.FindReferenceByImplementationType(graphType.ObjectType); - if (reference != null) - { - throw new GraphTypeDeclarationException( - $"The scalar's primary object type of '{graphType.ObjectType.FriendlyName()}' is " + - $"already reserved by the scalar '{reference.Name}'. Scalar object types must be unique."); - } - - foreach (var type in graphType.OtherKnownTypes) - { - var otherReference = this.FindReferenceByImplementationType(type); - if (otherReference != null) - { - throw new GraphTypeDeclarationException( - $"The scalar's other known type of '{type.FriendlyName()}' is " + - $"already reserved by the scalar '{otherReference.Name}'. Scalar object types must be unique."); - } - } - - var newReference = ScalarReference.Create(graphType, scalarType); - _scalarsByConcreteType.Add(newReference.PrimaryType, newReference); - foreach (var otherRef in newReference.OtherKnownTypes) - _scalarsByConcreteType.Add(otherRef, newReference); - - _scalarsByName.Add(newReference.Name, newReference); - _scalarReferences.Add(newReference); - } - - /// - /// Gets an enumeration of the known concrete type classes related to the scalars known to this provider. - /// - /// The concrete types. - public IEnumerable ConcreteTypes => _scalarsByConcreteType.Keys; - - /// - public IEnumerable ScalarInstanceTypes => _scalarReferences.Select(x => x.InstanceType); - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/DefaultTypeTemplateProvider.cs b/src/graphql-aspnet/Engine/DefaultTypeTemplateProvider.cs deleted file mode 100644 index a67cb698b..000000000 --- a/src/graphql-aspnet/Engine/DefaultTypeTemplateProvider.cs +++ /dev/null @@ -1,133 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Engine -{ - using System; - using System.Collections.Concurrent; - using GraphQL.AspNet.Common; - using GraphQL.AspNet.Common.Extensions; - using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Directives; - using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Interfaces.Engine; - using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; - using GraphQL.AspNet.Internal.TypeTemplates; - using GraphQL.AspNet.Schemas.TypeSystem; - - /// - /// A singular collection of all the graph type templates currently in the loaded app domain. Templates are - /// schema agnostic and expected to be reused across multiple schema instances. - /// - public class DefaultTypeTemplateProvider : IGraphTypeTemplateProvider - { - // maintain a collection of any already parsed - // templates to speed up any dynamic construction operations that may occur at run time. - private readonly ConcurrentDictionary, IGraphTypeTemplate> _knownObjects; - - /// - /// Initializes a new instance of the class. - /// - public DefaultTypeTemplateProvider() - { - _knownObjects = new ConcurrentDictionary, IGraphTypeTemplate>(); - this.CacheTemplates = true; - } - - /// - public ISchemaItemTemplate ParseType(TypeKind? kind = null) - { - return this.ParseType(typeof(TObjectType), kind); - } - - /// - public IGraphTypeTemplate ParseType(Type objectType, TypeKind? kind = null) - { - Validation.ThrowIfNull(objectType, nameof(objectType)); - - var typeKind = GraphValidation.ResolveTypeKind(objectType, kind); - var typeKey = Tuple.Create(typeKind, objectType); - - if (_knownObjects.TryGetValue(typeKey, out var template) && this.CacheTemplates) - return template; - - if (GraphQLProviders.ScalarProvider.IsScalar(objectType)) - { - throw new GraphTypeDeclarationException( - $"The type '{objectType.FriendlyName()}' is a known scalar type. Scalars must be explicitly defined and cannot be templated.", - objectType); - } - - if (Validation.IsCastable(objectType)) - { - throw new GraphTypeDeclarationException( - $"The union proxy '{objectType.FriendlyName()}' cannot be directly parsed as a graph type. Double check " + - "your field attribute declarations.", - objectType); - } - - GraphValidation.IsValidGraphType(objectType, true); - - template = this.MakeTemplate(objectType, typeKind); - template.Parse(); - - if (this.CacheTemplates) - _knownObjects.TryAdd(typeKey, template); - - return template; - } - - /// - public void Clear() - { - _knownObjects.Clear(); - } - - /// - /// Makes a graph template from the given type. - /// - /// Type of the object. - /// The kind of graph type to parse for. - /// IGraphItemTemplate. - protected virtual IGraphTypeTemplate MakeTemplate(Type objectType, TypeKind kind) - { - return MakeTemplateInternal(objectType, kind); - } - - /// - /// Internal overload of the default factory method for creating template objects. Used in various aspects of testing. - /// - /// Type of the object. - /// The kind. - /// IGraphTypeTemplate. - internal static IGraphTypeTemplate MakeTemplateInternal(Type objectType, TypeKind kind) - { - if (objectType.IsInterface) - return new InterfaceGraphTypeTemplate(objectType); - if (objectType.IsEnum) - return new EnumGraphTypeTemplate(objectType); - if (Validation.IsCastable(objectType)) - return new GraphControllerTemplate(objectType); - if (Validation.IsCastable(objectType)) - return new GraphDirectiveTemplate(objectType); - if (kind == TypeKind.INPUT_OBJECT) - return new InputObjectGraphTypeTemplate(objectType); - - return new ObjectGraphTypeTemplate(objectType); - } - - /// - public int Count => _knownObjects.Count; - - /// - public bool CacheTemplates { get; set; } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/TypeMakers/GraphArgumentMaker.cs b/src/graphql-aspnet/Engine/TypeMakers/GraphArgumentMaker.cs deleted file mode 100644 index 15c182d38..000000000 --- a/src/graphql-aspnet/Engine/TypeMakers/GraphArgumentMaker.cs +++ /dev/null @@ -1,68 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Engine.TypeMakers -{ - using GraphQL.AspNet.Common; - using GraphQL.AspNet.Interfaces.Engine; - using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Schemas.TypeSystem; - - /// - /// A maker capable of turning a into a usable on a graph field. - /// - public class GraphArgumentMaker : IGraphArgumentMaker - { - private readonly ISchema _schema; - - /// - /// Initializes a new instance of the class. - /// - /// The schema. - public GraphArgumentMaker(ISchema schema) - { - _schema = Validation.ThrowIfNullOrReturn(schema, nameof(schema)); - } - - /// - public GraphArgumentCreationResult CreateArgument(ISchemaItem owner, IGraphArgumentTemplate template) - { - Validation.ThrowIfNull(owner, nameof(owner)); - Validation.ThrowIfNull(template, nameof(template)); - - template.ValidateOrThrow(false); - - var formatter = _schema.Configuration.DeclarationOptions.GraphNamingFormatter; - - var directives = template.CreateAppliedDirectives(); - - var argument = new GraphFieldArgument( - owner, - formatter.FormatFieldName(template.Name), - template.TypeExpression.CloneTo(formatter.FormatGraphTypeName(template.TypeExpression.TypeName)), - template.Route, - template.ArgumentModifiers, - template.DeclaredArgumentName, - template.InternalFullName, - template.ObjectType, - template.HasDefaultValue, - template.DefaultValue, - template.Description, - directives); - - var result = new GraphArgumentCreationResult(); - result.Argument = argument; - - result.AddDependentRange(template.RetrieveRequiredTypes()); - - return result; - } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/TypeMakers/GraphFieldMaker.cs b/src/graphql-aspnet/Engine/TypeMakers/GraphFieldMaker.cs deleted file mode 100644 index 3ddb42e39..000000000 --- a/src/graphql-aspnet/Engine/TypeMakers/GraphFieldMaker.cs +++ /dev/null @@ -1,197 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* -namespace GraphQL.AspNet.Engine.TypeMakers -{ - using System; - using System.Collections.Generic; - using GraphQL.AspNet.Common; - using GraphQL.AspNet.Common.Extensions; - using GraphQL.AspNet.Common.Generics; - using GraphQL.AspNet.Configuration.Formatting; - using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Interfaces.Engine; - using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.TypeTemplates; - using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Security; - - /// - /// A maker capable of turning a into a usable field in an object graph. - /// - public class GraphFieldMaker : IGraphFieldMaker - { - /// - /// Initializes a new instance of the class. - /// - /// The schema. - public GraphFieldMaker(ISchema schema) - { - this.Schema = Validation.ThrowIfNullOrReturn(schema, nameof(schema)); - } - - /// - public virtual GraphFieldCreationResult CreateField(IGraphFieldTemplate template) - { - Validation.ThrowIfNull(template, nameof(template)); - - template.ValidateOrThrow(false); - - var formatter = this.Schema.Configuration.DeclarationOptions.GraphNamingFormatter; - var result = new GraphFieldCreationResult(); - - // if the owner of this field declared top level objects append them to the - // field for evaluation - var securityGroups = new List(); - - if (template.Parent?.SecurityPolicies?.Count > 0) - securityGroups.Add(template.Parent.SecurityPolicies); - - if (template.SecurityPolicies?.Count > 0) - securityGroups.Add(template.SecurityPolicies); - - MethodGraphField field = this.InstantiateField(formatter, template, securityGroups); - - field.Description = template.Description; - field.Complexity = template.Complexity; - field.FieldSource = template.FieldSource; - - if (template.Arguments != null) - { - var argumentMaker = this.CreateArgumentMaker(); - Validation.ThrowIfNull(argumentMaker, nameof(argumentMaker)); - - foreach (var argTemplate in template.Arguments) - { - var argumentResult = argumentMaker.CreateArgument(field, argTemplate); - field.Arguments.AddArgument(argumentResult.Argument); - - result.MergeDependents(argumentResult); - } - } - - result.AddDependentRange(template.RetrieveRequiredTypes()); - - if (template.UnionProxy != null) - { - var unionMaker = GraphQLProviders.GraphTypeMakerProvider.CreateUnionMaker(this.Schema); - var unionResult = unionMaker.CreateUnionFromProxy(template.UnionProxy); - if (unionResult != null) - { - result.AddAbstractDependent(unionResult.GraphType); - result.MergeDependents(unionResult); - } - } - - result.Field = field; - return result; - } - - /// - /// Creates an argument maker that will be used to create all the - /// arguments of a given field. - /// - /// IGraphArgumentMaker. - protected virtual IGraphArgumentMaker CreateArgumentMaker() - { - return new GraphArgumentMaker(this.Schema); - } - - /// - /// Instantiates the graph field according to the data provided. - /// - /// The formatter. - /// The template. - /// The security groups. - /// MethodGraphField. - protected virtual MethodGraphField InstantiateField( - GraphNameFormatter formatter, - IGraphFieldTemplate template, - List securityGroups) - { - var directives = template.CreateAppliedDirectives(); - - switch (template.FieldSource) - { - case GraphFieldSource.Method: - case GraphFieldSource.Action: - return new MethodGraphField( - formatter.FormatFieldName(template.Name), - template.TypeExpression.CloneTo(formatter.FormatGraphTypeName(template.TypeExpression.TypeName)), - template.Route, - template.ObjectType, - template.DeclaredReturnType, - template.Mode, - template.CreateResolver(), - securityGroups, - directives); - - case GraphFieldSource.Property: - return new PropertyGraphField( - formatter.FormatFieldName(template.Name), - template.TypeExpression.CloneTo(formatter.FormatGraphTypeName(template.TypeExpression.TypeName)), - template.Route, - template.DeclaredName, - template.ObjectType, - template.DeclaredReturnType, - template.Mode, - template.CreateResolver(), - securityGroups, - directives); - - default: - throw new ArgumentOutOfRangeException($"Template field source of {template.FieldSource.ToString()} is not supported by {this.GetType().FriendlyName()}."); - } - } - - /// - public GraphFieldCreationResult CreateField(IInputGraphFieldTemplate template) - { - var formatter = this.Schema.Configuration.DeclarationOptions.GraphNamingFormatter; - - var defaultInputObject = InstanceFactory.CreateInstance(template.Parent.ObjectType); - var propGetters = InstanceFactory.CreatePropertyGetterInvokerCollection(template.Parent.ObjectType); - - object defaultValue = null; - - if (!template.IsRequired && propGetters.ContainsKey(template.InternalName)) - { - defaultValue = propGetters[template.InternalName](ref defaultInputObject); - } - - var result = new GraphFieldCreationResult(); - - var directives = template.CreateAppliedDirectives(); - - var field = new InputGraphField( - formatter.FormatFieldName(template.Name), - template.TypeExpression.CloneTo(formatter.FormatGraphTypeName(template.TypeExpression.TypeName)), - template.Route, - template.DeclaredName, - template.ObjectType, - template.DeclaredReturnType, - template.IsRequired, - defaultValue, - directives); - - field.Description = template.Description; - - result.AddDependentRange(template.RetrieveRequiredTypes()); - - result.Field = field; - return result; - } - - /// - /// Gets the schema this field maker is creating fields for. - /// - /// The schema. - protected ISchema Schema { get; } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/TypeMakers/ObjectGraphTypeMaker.cs b/src/graphql-aspnet/Engine/TypeMakers/ObjectGraphTypeMaker.cs deleted file mode 100644 index a88ab53dc..000000000 --- a/src/graphql-aspnet/Engine/TypeMakers/ObjectGraphTypeMaker.cs +++ /dev/null @@ -1,137 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Engine.TypeMakers -{ - using System; - using System.Collections.Generic; - using System.Linq; - using GraphQL.AspNet.Common; - using GraphQL.AspNet.Common.Extensions; - using GraphQL.AspNet.Configuration; - using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Interfaces.Engine; - using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; - using GraphQL.AspNet.Internal.TypeTemplates; - using GraphQL.AspNet.Schemas.TypeSystem; - - /// - /// A "maker" capable of producing a qualified from its related template. - /// - public class ObjectGraphTypeMaker : IGraphTypeMaker - { - private readonly ISchema _schema; - - /// - /// Initializes a new instance of the class. - /// - /// The schema defining the graph type creation rules this generator should follow. - public ObjectGraphTypeMaker(ISchema schema) - { - _schema = Validation.ThrowIfNullOrReturn(schema, nameof(schema)); - } - - /// - /// Inspects the given type and, in accordance with the rules of this maker, will - /// generate a complete set of necessary graph types required to support it. - /// - /// The concrete type to incorporate into the schema. - /// GraphTypeCreationResult. - public virtual GraphTypeCreationResult CreateGraphType(Type concreteType) - { - if (concreteType == null) - return null; - - var template = GraphQLProviders.TemplateProvider.ParseType(concreteType, TypeKind.OBJECT) as IObjectGraphTypeTemplate; - if (template == null) - return null; - - template.ValidateOrThrow(false); - - var result = new GraphTypeCreationResult(); - - var formatter = _schema.Configuration.DeclarationOptions.GraphNamingFormatter; - var directives = template.CreateAppliedDirectives(); - - var objectType = new ObjectGraphType( - formatter.FormatGraphTypeName(template.Name), - concreteType, - template.Route, - directives) - { - Description = template.Description, - Publish = template.Publish, - }; - - result.GraphType = objectType; - result.ConcreteType = concreteType; - - // account for any potential type system directives - result.AddDependentRange(template.RetrieveRequiredTypes()); - - var fieldMaker = GraphQLProviders.GraphTypeMakerProvider.CreateFieldMaker(_schema); - var templatesToRender = ObjectGraphTypeMaker.GatherFieldTemplates(template, _schema); - foreach (var fieldTemplate in templatesToRender) - { - var fieldResult = fieldMaker.CreateField(fieldTemplate); - objectType.Extend(fieldResult.Field); - result.MergeDependents(fieldResult); - } - - // at least one field should have been rendered - // the type is invalid if there are no fields othe than __typename - if (objectType.Fields.Count == 1) - { - throw new GraphTypeDeclarationException( - $"The object graph type '{template.ObjectType.FriendlyName()}' defines 0 fields. " + - $"All object types must define at least one field.", - template.ObjectType); - } - - // add in declared interfaces by name - foreach (var iface in template.DeclaredInterfaces) - { - objectType.InterfaceNames.Add(formatter.FormatGraphTypeName(GraphTypeNames.ParseName(iface, TypeKind.INTERFACE))); - } - - return result; - } - - /// - /// Creates the collection of graph fields that belong to the template. - /// - /// The template to generate fields for. - /// The schema. - /// IEnumerable<IGraphField>. - internal static IEnumerable GatherFieldTemplates(IGraphTypeFieldTemplateContainer template, ISchema schema) - { - // gather the fields to include in the graph type - var requiredDeclarations = template.DeclarationRequirements ?? schema.Configuration.DeclarationOptions.FieldDeclarationRequirements; - - return template.FieldTemplates.Where(x => - { - if (x.IsExplicitDeclaration) - return true; - - switch (x.FieldSource) - { - case GraphFieldSource.Method: - return requiredDeclarations.AllowImplicitMethods(); - - case GraphFieldSource.Property: - return requiredDeclarations.AllowImplicitProperties(); - } - - return false; - }); - } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/TypeMakers/ScalarGraphTypeMaker.cs b/src/graphql-aspnet/Engine/TypeMakers/ScalarGraphTypeMaker.cs deleted file mode 100644 index 5f666739f..000000000 --- a/src/graphql-aspnet/Engine/TypeMakers/ScalarGraphTypeMaker.cs +++ /dev/null @@ -1,47 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Engine.TypeMakers -{ - using System; - using GraphQL.AspNet.Interfaces.Engine; - using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Schemas.TypeSystem; - - /// - /// A "maker" capable of producing a qualified from its related template. - /// - public class ScalarGraphTypeMaker : IGraphTypeMaker - { - /// - public GraphTypeCreationResult CreateGraphType(Type concreteType) - { - var scalarType = GraphQLProviders.ScalarProvider.CreateScalar(concreteType); - if (scalarType != null) - { - var result = new GraphTypeCreationResult() - { - GraphType = scalarType, - ConcreteType = concreteType, - }; - - // add any known diretives as dependents - foreach (var directiveToApply in scalarType.AppliedDirectives) - { - if (directiveToApply.DirectiveType != null) - result.AddDependent(directiveToApply.DirectiveType, TypeKind.DIRECTIVE); - } - - return result; - } - - return null; - } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Execution/BatchResultProcessor.cs b/src/graphql-aspnet/Execution/BatchResultProcessor.cs index dc167d063..b3f713c1d 100644 --- a/src/graphql-aspnet/Execution/BatchResultProcessor.cs +++ b/src/graphql-aspnet/Execution/BatchResultProcessor.cs @@ -20,7 +20,7 @@ namespace GraphQL.AspNet.Execution using GraphQL.AspNet.Execution.FieldResolution; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; + using GraphQL.AspNet.Schemas; /// /// A data processor that handles internal batch operations for items being processed through a graph query. diff --git a/src/graphql-aspnet/Execution/Contexts/DirectiveResolutionContext.cs b/src/graphql-aspnet/Execution/Contexts/DirectiveResolutionContext.cs index 64cb85b81..d572aa125 100644 --- a/src/graphql-aspnet/Execution/Contexts/DirectiveResolutionContext.cs +++ b/src/graphql-aspnet/Execution/Contexts/DirectiveResolutionContext.cs @@ -9,35 +9,73 @@ namespace GraphQL.AspNet.Execution.Contexts { + using System; using System.Diagnostics; using System.Security.Claims; + using System.Threading; + using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Interfaces.Logging; using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Structural; /// /// A context passed to a directive resolver to complete its resolution task for the field its attached to. /// + [GraphSkip] [DebuggerDisplay("Directive: {Request.InvocationContext.Directive.Name}")] public class DirectiveResolutionContext : SchemaItemResolutionContext { + private IGraphDirectiveRequest _directiveRequest; + /// /// Initializes a new instance of the class. /// + /// The service provider instance that should be used to resolve any + /// needed services or non-schema arguments to the target resolver. + /// The query session governing the request. /// The schema in scope for this resolution context. - /// The parent context from which this resolution context should - /// extract is base data values. + /// The master query request being executed. /// The resolution request to carry with the context. /// The arguments to be passed to the resolver when its executed. - /// Optional. The user context that authenticated and authorized for this + /// The messages. + /// (Optional) A logger instance that can be used to record scoped log entries. + /// (Optional) The user context that authenticated and authorized for this /// resolution context. + /// The cancel token governing the resolution of the schema item. public DirectiveResolutionContext( + IServiceProvider serviceProvider, + IQuerySession querySession, ISchema targetSchema, - IGraphQLMiddlewareExecutionContext parentContext, + IQueryExecutionRequest queryRequest, IGraphDirectiveRequest request, IExecutionArgumentCollection arguments, - ClaimsPrincipal user = null) - : base(targetSchema, parentContext, request, arguments, user) + IGraphMessageCollection messages = null, + IGraphEventLogger logger = null, + ClaimsPrincipal user = null, + CancellationToken cancelToken = default) + : base( + serviceProvider, + querySession, + targetSchema, + queryRequest, + request, + arguments, + messages, + logger, + user, + cancelToken) { + _directiveRequest = request; } + + /// + public override SchemaItemPath Route => _directiveRequest?.Directive.Route; + + /// + public override IGraphArgumentCollection SchemaDefinedArguments => _directiveRequest?.Directive.Arguments; + + /// + public override object SourceData => null; } } \ No newline at end of file diff --git a/src/graphql-aspnet/Execution/Contexts/FieldResolutionContext.cs b/src/graphql-aspnet/Execution/Contexts/FieldResolutionContext.cs index 61bed5335..a2f88a0fc 100644 --- a/src/graphql-aspnet/Execution/Contexts/FieldResolutionContext.cs +++ b/src/graphql-aspnet/Execution/Contexts/FieldResolutionContext.cs @@ -9,35 +9,64 @@ namespace GraphQL.AspNet.Execution.Contexts { + using System; using System.Diagnostics; using System.Security.Claims; + using System.Threading; + using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Interfaces.Logging; using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Structural; /// /// A context passed to a field resolver to complete its resolution task and generate data for a field. /// + [GraphSkip] [DebuggerDisplay("Field: {Request.Field.Route.Path} (Mode = {Request.Field.Mode})")] public class FieldResolutionContext : SchemaItemResolutionContext { + private readonly IGraphFieldRequest _fieldRequest; + /// /// Initializes a new instance of the class. /// + /// The service provider instance that should be used to resolve any + /// needed services or non-schema arguments to the target resolver. + /// The query session governing the request. /// The schema in scope for this resolution context. - /// The parent context from which this field resolution context is created. - /// The request to resolve a specific field. - /// The execution arguments that need to be passed to the field - /// resolver. - /// Optional. The user context that authenticated and authorized for this + /// The master query request being executed. + /// The resolution request to carry with the context. + /// The arguments to be passed to the resolver when its executed. + /// The messages. + /// (Optional) A logger instance that can be used to record scoped log entries. + /// (Optional) The user context that authenticated and authorized for this /// resolution context. + /// The cancel token governing the resolution of the schema item. public FieldResolutionContext( + IServiceProvider serviceProvider, + IQuerySession querySession, ISchema targetSchema, - IGraphQLMiddlewareExecutionContext parentContext, - IGraphFieldRequest fieldRequest, + IQueryExecutionRequest queryRequest, + IGraphFieldRequest request, IExecutionArgumentCollection arguments, - ClaimsPrincipal user = null) - : base(targetSchema, parentContext, fieldRequest, arguments, user) + IGraphMessageCollection messages = null, + IGraphEventLogger logger = null, + ClaimsPrincipal user = null, + CancellationToken cancelToken = default) + : base( + serviceProvider, + querySession, + targetSchema, + queryRequest, + request, + arguments, + messages, + logger, + user, + cancelToken) { + _fieldRequest = request; } /// @@ -45,5 +74,14 @@ public FieldResolutionContext( /// /// The result of executing a field's resolver. public object Result { get; set; } + + /// + public override SchemaItemPath Route => _fieldRequest?.Field.Route; + + /// + public override IGraphArgumentCollection SchemaDefinedArguments => _fieldRequest.Field.Arguments; + + /// + public override object SourceData => _fieldRequest?.Data?.Value; } } \ No newline at end of file diff --git a/src/graphql-aspnet/Execution/Contexts/SchemaItemResolutionContext.cs b/src/graphql-aspnet/Execution/Contexts/SchemaItemResolutionContext.cs index 14eb31d2d..db0a7bb26 100644 --- a/src/graphql-aspnet/Execution/Contexts/SchemaItemResolutionContext.cs +++ b/src/graphql-aspnet/Execution/Contexts/SchemaItemResolutionContext.cs @@ -9,47 +9,107 @@ namespace GraphQL.AspNet.Execution.Contexts { + using System; using System.Security.Claims; + using System.Threading; using GraphQL.AspNet.Common; using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Interfaces.Logging; using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Structural; /// /// A base context used by all field and directive resolution contexts in order to successfully invoke /// a controller action, object method or object property and retrieve a data value for a field. /// - public abstract class SchemaItemResolutionContext : MiddlewareExecutionContextBase + public abstract class SchemaItemResolutionContext { /// /// Initializes a new instance of the class. /// + /// The service provider instance that should be used to resolve any + /// needed services or non-schema arguments to the target resolver. + /// The query session governing the request. /// The schema in scope for this resolution context. - /// The parent context from which this resolution context should - /// extract is base data values. + /// The master query request being executed. /// The resolution request to carry with the context. /// The arguments to be passed to the resolver when its executed. + /// (Optional) A collection of messages that can be written to during resolution. These messages + /// will be transmitted to the requestor. + /// (Optional) A logger instance that can be used to record scoped log entries. /// (Optional) The user context that authenticated and authorized for this /// resolution context. + /// The cancel token governing the resolution of the schema item. protected SchemaItemResolutionContext( + IServiceProvider serviceProvider, + IQuerySession querySession, ISchema targetSchema, - IGraphQLMiddlewareExecutionContext parentContext, + IQueryExecutionRequest queryRequest, IDataRequest request, IExecutionArgumentCollection arguments, - ClaimsPrincipal user = null) - : base(parentContext) + IGraphMessageCollection messages = null, + IGraphEventLogger logger = null, + ClaimsPrincipal user = null, + CancellationToken cancelToken = default) { + this.Session = Validation.ThrowIfNullOrReturn(querySession, nameof(querySession)); + this.ServiceProvider = Validation.ThrowIfNullOrReturn(serviceProvider, nameof(serviceProvider)); + this.QueryRequest = Validation.ThrowIfNullOrReturn(queryRequest, nameof(queryRequest)); this.Request = Validation.ThrowIfNullOrReturn(request, nameof(request)); - this.Arguments = Validation.ThrowIfNullOrReturn(arguments, nameof(arguments)); + this.ExecutionSuppliedArguments = Validation.ThrowIfNullOrReturn(arguments, nameof(arguments)); this.User = user; + this.Logger = logger; this.Schema = Validation.ThrowIfNullOrReturn(targetSchema, nameof(targetSchema)); + this.ExecutionSuppliedArguments = this.ExecutionSuppliedArguments.ForContext(this); + this.Messages = messages ?? new GraphMessageCollection(); + this.CancellationToken = cancelToken; } + /// + /// Cancels this resolution context indicating it did not complete successfully. + /// + public virtual void Cancel() + { + this.IsCancelled = true; + } + + /// + /// Gets a value indicating whether this resolution context was canceled, indicating it did not complete + /// its resolution operation successfully. + /// + /// true if this instance is canceled; otherwise, false. + public virtual bool IsCancelled { get; private set; } + /// /// Gets the set of argument, if any, to be supplied to the method the resolver will call to /// complete its operation. /// /// The arguments. - public IExecutionArgumentCollection Arguments { get; } + public IExecutionArgumentCollection ExecutionSuppliedArguments { get; } + + /// + /// Gets a collection of messages that be written to. These messages will be transmitted to the requestor. + /// + /// The message collection available to this context. + public IGraphMessageCollection Messages { get; } + + /// + /// Gets the cancellation token governing this resolution. Any raised cancel requests via this token should be obeyed. + /// + /// The cancellation token governing the resolution of the target schema item. + public CancellationToken CancellationToken { get; } + + /// + /// Gets a service provider instance that can be used to resolve services during this schema item's resolution cycle. + /// + /// The service provider instance available for resolution of services. + public IServiceProvider ServiceProvider { get; } + + /// + /// Gets the master query request that was initially supplied to the runtime. + /// + /// The query request. + public IQueryExecutionRequest QueryRequest { get; } /// /// Gets the request governing the resolver's operation. @@ -64,10 +124,45 @@ protected SchemaItemResolutionContext( /// The user. public ClaimsPrincipal User { get; } + /// + /// Gets a logger instance can be written to to record scoped log entries. + /// + /// The logger. + public IGraphEventLogger Logger { get; } + /// /// Gets the schema that is targeted by this context. /// /// The schema. public ISchema Schema { get; } + + /// + /// Gets the route to the item being resolved. + /// + /// The route. + public abstract SchemaItemPath Route { get; } + + /// + /// Gets the set of arguments defined on the schema that are to be resolved to fulfill this request. + /// + /// The set of arguments to use in resolution. + public abstract IGraphArgumentCollection SchemaDefinedArguments { get; } + + /// + /// Gets the source data item resolved from a parent resolver, if any. May be null. + /// + /// The source data item supplied to this context. + public abstract object SourceData { get; } + + /// + /// Gets the object used to track runtime session data for a single query. + /// + /// + /// This is an internal entity reserved for use by graphql's pipelines and + /// should not be utilized upon by controller action methods. Modification of the data within + /// the session can cause the query execution to break. + /// + /// The active query session. + public IQuerySession Session { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Execution/Contexts/SchemaItemResolutionContext{TRequest}.cs b/src/graphql-aspnet/Execution/Contexts/SchemaItemResolutionContext{TRequest}.cs index 6cbdd2c4c..830c2d1d0 100644 --- a/src/graphql-aspnet/Execution/Contexts/SchemaItemResolutionContext{TRequest}.cs +++ b/src/graphql-aspnet/Execution/Contexts/SchemaItemResolutionContext{TRequest}.cs @@ -9,8 +9,11 @@ namespace GraphQL.AspNet.Execution.Contexts { + using System; using System.Security.Claims; + using System.Threading; using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Interfaces.Logging; using GraphQL.AspNet.Interfaces.Schema; /// @@ -24,20 +27,40 @@ public abstract class SchemaItemResolutionContext : SchemaItemResoluti /// /// Initializes a new instance of the class. /// + /// The service provider instance that should be used to resolve any + /// needed services or non-schema arguments to the target resolver. + /// The query session governing the request. /// The schema in scope for this resolution context. - /// The parent context from which this resolution context should - /// extract is base data values. + /// The master query request being executed. /// The resolution request to carry with the context. /// The arguments to be passed to the resolver when its executed. - /// Optional. The user context that authenticated and authorized for this + /// The messages. + /// (Optional) A logger instance that can be used to record scoped log entries. + /// (Optional) The user context that authenticated and authorized for this /// resolution context. + /// The cancel token governing the resolution of the schema item. protected SchemaItemResolutionContext( + IServiceProvider serviceProvider, + IQuerySession querySession, ISchema targetSchema, - IGraphQLMiddlewareExecutionContext parentContext, - TRequest request, + IQueryExecutionRequest queryRequest, + IDataRequest request, IExecutionArgumentCollection arguments, - ClaimsPrincipal user = null) - : base(targetSchema, parentContext, request, arguments, user) + IGraphMessageCollection messages = null, + IGraphEventLogger logger = null, + ClaimsPrincipal user = null, + CancellationToken cancelToken = default) + : base( + serviceProvider, + querySession, + targetSchema, + queryRequest, + request, + arguments, + messages, + logger, + user, + cancelToken) { } diff --git a/src/graphql-aspnet/Execution/DirectiveProcessorTypeSystem.cs b/src/graphql-aspnet/Execution/DirectiveProcessorTypeSystem.cs index 349c7b488..009da89cb 100644 --- a/src/graphql-aspnet/Execution/DirectiveProcessorTypeSystem.cs +++ b/src/graphql-aspnet/Execution/DirectiveProcessorTypeSystem.cs @@ -59,22 +59,31 @@ public DirectiveProcessorTypeSystem(IServiceProvider serviceProvider, IQuerySess /// using standard directive pipeline. /// /// The schema to apply directives too. - public void ApplyDirectives(TSchema schema) + /// The total number of directives applied across the entire schema. + public int ApplyDirectives(TSchema schema) { + Validation.ThrowIfNull(schema, nameof(schema)); + // all schema items - var anyDirectivesApplied = false; + var totalApplied = 0; foreach (var item in schema.AllSchemaItems()) - anyDirectivesApplied = this.ApplyDirectivesToItem(schema, item) || anyDirectivesApplied; + { + totalApplied += this.ApplyDirectivesToItem(schema, item); + } + + return totalApplied; } /// - /// Applies the directives to item. + /// Applies queued directives to item. /// - /// The schema. - /// The item. - private bool ApplyDirectivesToItem(TSchema schema, ISchemaItem item) + /// The schema the belongs to. + /// The item to apply directives on. + /// The total number of directives applied to . + private int ApplyDirectivesToItem(TSchema schema, ISchemaItem item) { var invokedDirectives = new HashSet(); + foreach (var appliedDirective in item.AppliedDirectives) { var scopedProvider = _serviceProvider.CreateScope(); @@ -207,7 +216,7 @@ private bool ApplyDirectivesToItem(TSchema schema, ISchemaItem item) } } - return item.AppliedDirectives.Count > 0; + return item.AppliedDirectives.Count; } private IInputArgumentCollection GatherInputArguments(IDirective targetDirective, object[] arguments) diff --git a/src/graphql-aspnet/Execution/ExecutionArgumentCollection.cs b/src/graphql-aspnet/Execution/ExecutionArgumentCollection.cs index e8db459ad..9ce93fb16 100644 --- a/src/graphql-aspnet/Execution/ExecutionArgumentCollection.cs +++ b/src/graphql-aspnet/Execution/ExecutionArgumentCollection.cs @@ -9,14 +9,19 @@ namespace GraphQL.AspNet.Execution { + using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; + using System.Reflection; using GraphQL.AspNet.Common; + using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution.Contexts; using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Execution.RulesEngine.RuleSets.DocumentValidation.FieldSelectionSetSteps; using GraphQL.AspNet.Interfaces.Execution; - using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Interfaces.Web; using GraphQL.AspNet.Schemas.TypeSystem; /// @@ -26,8 +31,8 @@ namespace GraphQL.AspNet.Execution [DebuggerDisplay("Count = {Count}")] internal class ExecutionArgumentCollection : IExecutionArgumentCollection { - private readonly Dictionary _arguments; - private readonly GraphFieldExecutionContext _fieldContext; + private readonly Dictionary _suppliedArgumentDataValues; + private readonly SchemaItemResolutionContext _resolutionContext; /// /// Initializes a new instance of the class. @@ -35,7 +40,7 @@ internal class ExecutionArgumentCollection : IExecutionArgumentCollection /// The initial capacity of the collection, if known. public ExecutionArgumentCollection(int? capacity = null) { - _arguments = capacity.HasValue + _suppliedArgumentDataValues = capacity.HasValue ? new Dictionary(capacity.Value) : new Dictionary(); } @@ -43,27 +48,27 @@ public ExecutionArgumentCollection(int? capacity = null) /// /// Initializes a new instance of the class. /// - /// The argument list. - /// The field context. + /// The argument list keyed by the argument's name in the graph. + /// The resolution context. private ExecutionArgumentCollection( IDictionary argumentList, - GraphFieldExecutionContext fieldContext) + SchemaItemResolutionContext resolutionContext) { - _arguments = new Dictionary(argumentList); - _fieldContext = fieldContext; + _suppliedArgumentDataValues = new Dictionary(argumentList); + _resolutionContext = resolutionContext; } /// public void Add(ExecutionArgument argument) { Validation.ThrowIfNull(argument, nameof(argument)); - _arguments.Add(argument.Name, argument); + _suppliedArgumentDataValues.Add(argument.Name, argument); } /// - public IExecutionArgumentCollection ForContext(GraphFieldExecutionContext fieldContext) + public IExecutionArgumentCollection ForContext(SchemaItemResolutionContext resolutionContext) { - return new ExecutionArgumentCollection(_arguments, fieldContext); + return new ExecutionArgumentCollection(_suppliedArgumentDataValues, resolutionContext); } /// @@ -85,90 +90,177 @@ public bool TryGetArgument(string argumentName, out TType argumentValue) } /// - public object[] PrepareArguments(IGraphFieldResolverMethod graphMethod) + public object[] PrepareArguments(IGraphFieldResolverMetaData resolverMetadata) { - var preparedParams = new List(); - var paramInfos = graphMethod.Parameters; + Validation.ThrowIfNull(resolverMetadata, nameof(resolverMetadata)); - for (var i = 0; i < graphMethod.Arguments.Count; i++) + var preparedParams = new object[resolverMetadata.Parameters.Count]; + + for (var i = 0; i < resolverMetadata.Parameters.Count; i++) { - var argTemplate = graphMethod.Arguments[i]; - object passedValue = this.ResolveParameterFromArgumentTemplate(argTemplate); - if (passedValue == null && !argTemplate.TypeExpression.IsNullable) - { - // technically shouldn't be throwable given the validation routines - // but captured here as a saftey net for users - // doing custom extensions or implementations - throw new GraphExecutionException( - $"The parameter '{argTemplate.Name}' for field '{graphMethod.Route.Path}' could not be resolved from the query document " + - "or variable collection and no default value was found."); - } + var parameter = resolverMetadata.Parameters[i]; + + object passedValue = this.ResolveParameterValue(parameter); // ensure compatible list types between the internally // tracked data and the target type of the method being invoked - // i.e. convert List => T[] when needed - if (argTemplate.TypeExpression.IsListOfItems) + // e.g. convert List => T[] when needed + if (parameter.IsList) { - var listMangler = new ListMangler(paramInfos[i].ParameterType); + var listMangler = new ListMangler(parameter.ExpectedType); var result = listMangler.Convert(passedValue); passedValue = result.Data; } - preparedParams.Add(passedValue); + preparedParams[i] = passedValue; } - return preparedParams.ToArray(); + return preparedParams; } - /// - /// Attempts to deserialize a parameter value from the graph ql context supplied. - /// - /// The argument definition. - /// System.Object. - private object ResolveParameterFromArgumentTemplate(IGraphArgumentTemplate argDefinition) + private object ResolveParameterValue(IGraphFieldResolverParameterMetaData paramDef) { - if (argDefinition == null) - return null; + Validation.ThrowIfNull(paramDef, nameof(paramDef)); + + if (paramDef.ArgumentModifiers.IsSourceParameter()) + return _resolutionContext?.SourceData; + + if (paramDef.ArgumentModifiers.IsCancellationToken()) + return _resolutionContext?.CancellationToken ?? default; + + if (paramDef.ArgumentModifiers.IsResolverContext()) + { + if (_resolutionContext != null && Validation.IsCastable(_resolutionContext.GetType(), paramDef.ExpectedType)) + return _resolutionContext; + + if (paramDef.HasDefaultValue) + return paramDef.DefaultValue; + + // check fallback rules for missing, non-schema items + if (_resolutionContext != null && _resolutionContext.Schema.Configuration.ExecutionOptions.ResolverParameterResolutionRule == Configuration.ResolverParameterResolutionRules.UseNullorDefault) + return this.CreateNullOrDefault(paramDef.ExpectedType); + + throw new GraphExecutionException( + $"The resolution context parameter '{paramDef.InternalName}' of type {paramDef.ExpectedType.FriendlyName()} for resolver '{paramDef.ParentInternalName}' could not be resolved and " + + $"does not declare a default value. Unable to complete the request."); + } + + if (paramDef.ArgumentModifiers.IsHttpContext()) + { + if (_resolutionContext?.QueryRequest is IQueryExecutionWebRequest req) + return req.HttpContext; + + if (paramDef.HasDefaultValue) + return paramDef.DefaultValue; + + // check fallback rules for missing, non-schema items + if (_resolutionContext != null && _resolutionContext.Schema.Configuration.ExecutionOptions.ResolverParameterResolutionRule == Configuration.ResolverParameterResolutionRules.UseNullorDefault) + return this.CreateNullOrDefault(paramDef.ExpectedType); + + throw new GraphExecutionException( + $"The http context parameter '{paramDef.InternalName}' of type {paramDef.ExpectedType.FriendlyName()} for resolver '{paramDef.ParentInternalName}' could not be resolved and " + + $"does not declare a default value. Unable to complete the request."); + } - if (argDefinition.ArgumentModifiers.IsSourceParameter()) - return this.SourceData; + // if there an argument supplied on the query for this parameter, use that + if (this.TryGetValue(paramDef.ParameterInfo.Name, out var arg)) + return arg.Value; - if (argDefinition.ArgumentModifiers.IsCancellationToken()) - return _fieldContext?.CancellationToken ?? default; + // if the parameter is part of the graph, use the related argument's default value + if (paramDef.ArgumentModifiers.CouldBePartOfTheSchema()) + { + // additional checks and coersion if this the value is (or should be) + // supplied from a query + var graphArgument = _resolutionContext? + .SchemaDefinedArguments? + .FindArgumentByParameterName(paramDef.ParameterInfo.Name); + + if (graphArgument != null) + { + if (graphArgument.HasDefaultValue) + return graphArgument.DefaultValue; + + if (graphArgument.TypeExpression.IsNullable) + return null; + + // When an argument is found on the schema + // and no value was supplied on the query + // and that argument has no default value defined + // and that argument is not allowed to be null + // then error out + // + // This situation is technically not possible given the validation routines in place at runtime + // but captured here as a saftey net for users + // doing custom extensions or implementations + // this prevents resolver execution with indeterminate or unexpected data + // as well as cryptic error messages related to object invocation + var path = _resolutionContext?.Route.Path; + throw new GraphExecutionException( + $"The parameter '{paramDef.InternalName}' for schema item '{path}' could not be resolved from the query document " + + "or variable collection and no default value was found."); + } + } + + // its not a formal argument in the schema, try and resolve from DI container + object serviceResolvedValue = _resolutionContext?.ServiceProvider?.GetService(paramDef.ExpectedType); - return this.ContainsKey(argDefinition.DeclaredArgumentName) - ? this[argDefinition.DeclaredArgumentName].Value - : argDefinition.DefaultValue; + // the service was found in the DI container!! *happy* + if (serviceResolvedValue != null) + return serviceResolvedValue; + + // it wasn't found but the developer declared a fall back. *thankful* + if (paramDef.HasDefaultValue) + return paramDef.DefaultValue; + + // check fallback rules for missing, non-schema items + if (_resolutionContext != null && _resolutionContext.Schema.Configuration.ExecutionOptions.ResolverParameterResolutionRule == Configuration.ResolverParameterResolutionRules.UseNullorDefault) + return this.CreateNullOrDefault(paramDef.ExpectedType); + + var schemaItemName = _resolutionContext?.Route.Path + ?? paramDef.InternalName; + + // error unable to resolve correctly. *womp womp* + throw new GraphExecutionException( + $"The parameter '{paramDef.InternalName}' targeting '{schemaItemName}' was expected to be resolved from a " + + $"service provider but a suitable instance could not be obtained from the current invocation context " + + $"and no default value was declared."); + } + + private object CreateNullOrDefault(Type typeToCreateFor) + { + if (typeToCreateFor.IsValueType) + { + return Activator.CreateInstance(typeToCreateFor); + } + + return null; } /// - public bool ContainsKey(string key) => _arguments.ContainsKey(key); + public bool ContainsKey(string key) => _suppliedArgumentDataValues.ContainsKey(key); /// public bool TryGetValue(string key, out ExecutionArgument value) { - return _arguments.TryGetValue(key, out value); + return _suppliedArgumentDataValues.TryGetValue(key, out value); } /// - public ExecutionArgument this[string key] => _arguments[key]; - - /// - public IEnumerable Keys => _arguments.Keys; + public ExecutionArgument this[string key] => _suppliedArgumentDataValues[key]; /// - public IEnumerable Values => _arguments.Values; + public IEnumerable Keys => _suppliedArgumentDataValues.Keys; /// - public int Count => _arguments.Count; + public IEnumerable Values => _suppliedArgumentDataValues.Values; /// - public object SourceData => _fieldContext?.Request?.Data?.Value; + public int Count => _suppliedArgumentDataValues.Count; /// public IEnumerator> GetEnumerator() { - return _arguments.GetEnumerator(); + return _suppliedArgumentDataValues.GetEnumerator(); } /// diff --git a/src/graphql-aspnet/Execution/ExecutionArgumentGenerator.cs b/src/graphql-aspnet/Execution/ExecutionArgumentGenerator.cs index 6d6ef6735..8c336efd5 100644 --- a/src/graphql-aspnet/Execution/ExecutionArgumentGenerator.cs +++ b/src/graphql-aspnet/Execution/ExecutionArgumentGenerator.cs @@ -54,25 +54,20 @@ public static bool TryConvert( var argDefinition = arg.Argument; var resolvedValue = arg.Value.Resolve(variableData); - // no schema arguments are internally controlled - // the resolved value is the value we want - if (argDefinition.ArgumentModifiers.IsPartOfTheSchema()) + // its possible for a non-nullable variable to receive a + // null value due to a variable supplying null + // trap it and fail out the execution if so + // see: https://spec.graphql.org/October2021/#sel-GALbLHNCCBCGIp9O + if (resolvedValue == null && argDefinition.TypeExpression.IsNonNullable) { - // its possible for a non-nullable variable to receive a - // null value due to a variable supplying null - // trap it and fail out the execution if so - // see: https://spec.graphql.org/October2021/#sel-GALbLHNCCBCGIp9O - if (resolvedValue == null && argDefinition.TypeExpression.IsNonNullable) - { - messages.Critical( - $"The value supplied to argument '{argDefinition.Name}' was but its expected type expression " + - $"is {argDefinition.TypeExpression}.", - Constants.ErrorCodes.INVALID_ARGUMENT_VALUE, - arg.Origin); + messages.Critical( + $"The value supplied to argument '{argDefinition.Name}' was but its expected type expression " + + $"is {argDefinition.TypeExpression}.", + Constants.ErrorCodes.INVALID_ARGUMENT_VALUE, + arg.Origin); - successful = false; - continue; - } + successful = false; + continue; } collection.Add(new ExecutionArgument(arg.Argument, resolvedValue)); diff --git a/src/graphql-aspnet/Execution/ExecutionExtensionMethods.cs b/src/graphql-aspnet/Execution/ExecutionExtensionMethods.cs index 769cddb80..13275bb4b 100644 --- a/src/graphql-aspnet/Execution/ExecutionExtensionMethods.cs +++ b/src/graphql-aspnet/Execution/ExecutionExtensionMethods.cs @@ -13,8 +13,8 @@ namespace GraphQL.AspNet.Execution using System.Threading; using System.Threading.Tasks; using GraphQL.AspNet.Execution.Contexts; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Interfaces.Execution; - using GraphQL.AspNet.Internal.Resolvers; using RouteConstants = GraphQL.AspNet.Constants.Routing; /// diff --git a/src/graphql-aspnet/Execution/FieldResolution/FieldDataItem.cs b/src/graphql-aspnet/Execution/FieldResolution/FieldDataItem.cs index bfe6f39ed..2ad5ca315 100644 --- a/src/graphql-aspnet/Execution/FieldResolution/FieldDataItem.cs +++ b/src/graphql-aspnet/Execution/FieldResolution/FieldDataItem.cs @@ -24,7 +24,6 @@ namespace GraphQL.AspNet.Execution.FieldResolution using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Internal; /// /// An ecapsulation of a piece of real data supplied to, or resolved from, a graph field. @@ -369,8 +368,9 @@ public bool GenerateResult(out IQueryResponseItem result) if (!this.Status.IncludeInOutput()) return false; - // leafs have nothing underneath them, the resolved data IS the item value. - if (this.FieldContext.Field.IsLeaf) + // leafs have nothing underneath them, the resolved data IS the item value. + var graphType = this.Schema.KnownTypes.FindGraphType(this.FieldContext.Field.TypeExpression.TypeName); + if (graphType.Kind.IsLeafKind()) { // List and List are leafs since there is no further // resolution to the data but its still must be projected diff --git a/src/graphql-aspnet/Execution/FieldSourceCollection.cs b/src/graphql-aspnet/Execution/FieldSourceCollection.cs index f1ed3786b..44f8e8e7e 100644 --- a/src/graphql-aspnet/Execution/FieldSourceCollection.cs +++ b/src/graphql-aspnet/Execution/FieldSourceCollection.cs @@ -14,7 +14,7 @@ namespace GraphQL.AspNet.Execution using GraphQL.AspNet.Common; using GraphQL.AspNet.Execution.FieldResolution; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; /// diff --git a/src/graphql-aspnet/Execution/GraphMessageCollection.cs b/src/graphql-aspnet/Execution/GraphMessageCollection.cs index 844f747f2..9fecbe3b1 100644 --- a/src/graphql-aspnet/Execution/GraphMessageCollection.cs +++ b/src/graphql-aspnet/Execution/GraphMessageCollection.cs @@ -48,7 +48,7 @@ public GraphMessageCollection(int capacity) /// public void AddRange(IGraphMessageCollection messagesToAdd) { - if (messagesToAdd == null || messagesToAdd.Count == 0) + if (messagesToAdd == null || messagesToAdd == this || messagesToAdd.Count == 0) return; lock (_messages) diff --git a/src/graphql-aspnet/Execution/GraphQLFieldResolverIsolationManager.cs b/src/graphql-aspnet/Execution/GraphQLFieldResolverIsolationManager.cs index 62202abe1..e9ecca729 100644 --- a/src/graphql-aspnet/Execution/GraphQLFieldResolverIsolationManager.cs +++ b/src/graphql-aspnet/Execution/GraphQLFieldResolverIsolationManager.cs @@ -16,7 +16,7 @@ namespace GraphQL.AspNet.Execution using GraphQL.AspNet.Configuration; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; /// /// A default implementation of the resolver isolation manager, wrapping a simple diff --git a/src/graphql-aspnet/Execution/GraphSchemaInitializer.cs b/src/graphql-aspnet/Execution/GraphSchemaInitializer.cs deleted file mode 100644 index d41865444..000000000 --- a/src/graphql-aspnet/Execution/GraphSchemaInitializer.cs +++ /dev/null @@ -1,96 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Execution -{ - using System; - using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Common; - using GraphQL.AspNet.Common.Extensions; - using GraphQL.AspNet.Configuration; - using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Schemas; - - /// - /// Perform a set of standardized steps to setup and configure any graph schema according to the rules - /// for document operation execution used by the various schema pipelines. - /// - /// The type of the schema that the initializer - /// can work with. - internal sealed class GraphSchemaInitializer - where TSchema : class, ISchema - { - private readonly SchemaOptions _options; - private readonly IServiceProvider _serviceProvider; - - /// - /// Initializes a new instance of the class. - /// - /// The options. - /// The service provider from which to draw componentry for - /// initailization. - public GraphSchemaInitializer(SchemaOptions options, IServiceProvider serviceProvider) - { - _options = Validation.ThrowIfNullOrReturn(options, nameof(options)); - _serviceProvider = Validation.ThrowIfNullOrReturn(serviceProvider, nameof(serviceProvider)); - } - - /// - /// Initializes the schema: - /// * Add any controllers to the schema instance that were configured during startup. - /// * Add all methods, virtual graph types, return types parameters and property configurations. - /// * Add any additional types added at startup. - /// * Register introspection meta-fields. - /// - /// The schema to initialize. - public void Initialize(TSchema schema) - { - Validation.ThrowIfNull(schema, nameof(schema)); - if (schema.IsInitialized) - return; - - lock (schema) - { - if (schema.IsInitialized) - return; - - schema.Configuration.Merge(_options.CreateConfiguration()); - - var manager = new GraphSchemaManager(schema); - manager.AddBuiltInDirectives(); - - // add any configured types to this instance - foreach (var registration in _options.SchemaTypesToRegister) - { - var typeDeclaration = registration.Type.SingleAttributeOrDefault(); - if (typeDeclaration != null && typeDeclaration.PreventAutoInclusion) - continue; - - manager.EnsureGraphType(registration.Type, registration.TypeKind); - } - - // execute any assigned schema configuration extensions - // - // this includes any late bound directives added to the type - // system via .ApplyDirective() - foreach (var extension in _options.ConfigurationExtensions) - extension.Configure(schema); - - // apply all queued type system directives - var processor = new DirectiveProcessorTypeSystem( - _serviceProvider, - new QuerySession()); - processor.ApplyDirectives(schema); - - manager.RebuildIntrospectionData(); - schema.IsInitialized = true; - } - } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Execution/ListMangler.cs b/src/graphql-aspnet/Execution/ListMangler.cs index a95077718..39350dc44 100644 --- a/src/graphql-aspnet/Execution/ListMangler.cs +++ b/src/graphql-aspnet/Execution/ListMangler.cs @@ -18,7 +18,7 @@ namespace GraphQL.AspNet.Execution using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Common.Generics; using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Internal; + using GraphQL.AspNet.Schemas; /* Motivation * ------------------------------ diff --git a/src/graphql-aspnet/Execution/MetaDataCollection.cs b/src/graphql-aspnet/Execution/MetaDataCollection.cs index dfd12e371..b4107aa6a 100644 --- a/src/graphql-aspnet/Execution/MetaDataCollection.cs +++ b/src/graphql-aspnet/Execution/MetaDataCollection.cs @@ -33,7 +33,7 @@ public sealed class MetaDataCollection : IEnumerable _localDictionary; diff --git a/src/graphql-aspnet/Execution/QueryPlans/InputArguments/ArgumentGenerator.cs b/src/graphql-aspnet/Execution/QueryPlans/InputArguments/ArgumentGenerator.cs index 8066b8866..25b9e2fbd 100644 --- a/src/graphql-aspnet/Execution/QueryPlans/InputArguments/ArgumentGenerator.cs +++ b/src/graphql-aspnet/Execution/QueryPlans/InputArguments/ArgumentGenerator.cs @@ -15,10 +15,10 @@ namespace GraphQL.AspNet.Execution.QueryPlans.InputArguments using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Execution.QueryPlans.DocumentParts.SuppliedValues; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Execution.Source; using GraphQL.AspNet.Interfaces.Execution.QueryPlans.DocumentParts; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.Resolvers; using GraphQL.AspNet.Schemas.TypeSystem; /// @@ -55,8 +55,7 @@ public ArgumentGenerationResult CreateInputArgument( if (!suppliedArgumentData.ContainsKey(argumentDefinition.Name)) { - if (argumentDefinition.IsRequired - && argumentDefinition.ArgumentModifiers.IsPartOfTheSchema()) + if (argumentDefinition.IsRequired) { // this should be an impossible scenario due to validation middleware // However, the pipeline can be changed by the developer so we must diff --git a/src/graphql-aspnet/Internal/Resolvers/EnumInputValueResolver.cs b/src/graphql-aspnet/Execution/Resolvers/EnumInputValueResolver.cs similarity index 93% rename from src/graphql-aspnet/Internal/Resolvers/EnumInputValueResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/EnumInputValueResolver.cs index 969557165..431565293 100644 --- a/src/graphql-aspnet/Internal/Resolvers/EnumInputValueResolver.cs +++ b/src/graphql-aspnet/Execution/Resolvers/EnumInputValueResolver.cs @@ -7,14 +7,12 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers +namespace GraphQL.AspNet.Execution.Resolvers { using System; using GraphQL.AspNet.Common; using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Execution.Source; using GraphQL.AspNet.Interfaces.Execution; - using GraphQL.AspNet.Interfaces.Execution.QueryPlans.DocumentParts; using GraphQL.AspNet.Interfaces.Execution.QueryPlans.Resolvables; using GraphQL.AspNet.Interfaces.Execution.Variables; using GraphQL.AspNet.Interfaces.Schema; diff --git a/src/graphql-aspnet/Internal/Resolvers/EnumLeafValueResolver.cs b/src/graphql-aspnet/Execution/Resolvers/EnumLeafValueResolver.cs similarity index 97% rename from src/graphql-aspnet/Internal/Resolvers/EnumLeafValueResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/EnumLeafValueResolver.cs index 1d910ecce..ccef9e797 100644 --- a/src/graphql-aspnet/Internal/Resolvers/EnumLeafValueResolver.cs +++ b/src/graphql-aspnet/Execution/Resolvers/EnumLeafValueResolver.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers +namespace GraphQL.AspNet.Execution.Resolvers { using System; using GraphQL.AspNet.Common; diff --git a/src/graphql-aspnet/Internal/Resolvers/ExtendedGraphFieldResolver.cs b/src/graphql-aspnet/Execution/Resolvers/ExtendedGraphFieldResolver.cs similarity index 93% rename from src/graphql-aspnet/Internal/Resolvers/ExtendedGraphFieldResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/ExtendedGraphFieldResolver.cs index 8c9fdb3fc..b85c32a7d 100644 --- a/src/graphql-aspnet/Internal/Resolvers/ExtendedGraphFieldResolver.cs +++ b/src/graphql-aspnet/Execution/Resolvers/ExtendedGraphFieldResolver.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers +namespace GraphQL.AspNet.Execution.Resolvers { using System; using System.Diagnostics; @@ -47,6 +47,6 @@ public async Task ResolveAsync(FieldResolutionContext context, CancellationToken } /// - public Type ObjectType => _primaryResolver.ObjectType; + public IGraphFieldResolverMetaData MetaData => _primaryResolver.MetaData; } } \ No newline at end of file diff --git a/src/graphql-aspnet/Execution/Resolvers/FieldResolverMetaData.cs b/src/graphql-aspnet/Execution/Resolvers/FieldResolverMetaData.cs new file mode 100644 index 000000000..47815ed92 --- /dev/null +++ b/src/graphql-aspnet/Execution/Resolvers/FieldResolverMetaData.cs @@ -0,0 +1,92 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Execution.Resolvers +{ + using System; + using System.Diagnostics; + using System.Reflection; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + + /// + /// A metadata object containing parsed and computed values related to + /// C# method that is used a a resolver to a graph field. + /// + [DebuggerDisplay("Method: {InternalName}")] + internal class FieldResolverMetaData : IGraphFieldResolverMetaData + { + /// + /// Initializes a new instance of the class. + /// + /// The method info will be invoked to fulfill the resolver. + /// The parameters metadata collection related to this resolver method. + /// Expected type of the data to be returned by the method. May be different + /// from concrete return types (e.g. expecting an interface but actually returning a concrete type that implements that interface). + /// if set to true the invoked method is asyncronous. + /// The internal name of the resolver method or property that can uniquely identify it in + /// exceptions and log entries. + /// The exact name of the resolver method or property name as its declared in source code. + /// The type of the .NET class or struct where the resolver method is declared. + /// The name of the .NET class or struct where the resolver method is declared. + /// A value indicating where the code that this resolver represents was declared. + public FieldResolverMetaData( + MethodInfo method, + IGraphFieldResolverParameterMetaDataCollection parameters, + Type expectedReturnType, + bool isAsyncField, + string internalName, + string declaredName, + Type parentObjectType, + string parentInternalName, + ItemSource itemSource) + { + this.Method = Validation.ThrowIfNullOrReturn(method, nameof(method)); + + this.ExpectedReturnType = Validation.ThrowIfNullOrReturn(expectedReturnType, nameof(expectedReturnType)); + + this.IsAsyncField = isAsyncField; + this.Parameters = Validation.ThrowIfNullOrReturn(parameters, nameof(parameters)); + + this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalName, nameof(internalName)); + this.DeclaredName = Validation.ThrowIfNullWhiteSpaceOrReturn(declaredName, nameof(declaredName)); + this.ParentObjectType = Validation.ThrowIfNullOrReturn(parentObjectType, nameof(parentObjectType)); + this.ParentInternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(parentInternalName, nameof(parentInternalName)); + this.DefinitionSource = itemSource; + } + + /// + public Type ExpectedReturnType { get; } + + /// + public MethodInfo Method { get; } + + /// + public bool IsAsyncField { get; } + + /// + public string InternalName { get; } + + /// + public IGraphFieldResolverParameterMetaDataCollection Parameters { get; } + + /// + public string ParentInternalName { get; } + + /// + public Type ParentObjectType { get; } + + /// + public string DeclaredName { get; } + + /// + public ItemSource DefinitionSource { get; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Execution/Resolvers/FieldResolverParameterMetaData.cs b/src/graphql-aspnet/Execution/Resolvers/FieldResolverParameterMetaData.cs new file mode 100644 index 000000000..43dd49547 --- /dev/null +++ b/src/graphql-aspnet/Execution/Resolvers/FieldResolverParameterMetaData.cs @@ -0,0 +1,92 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Execution.Resolvers +{ + using System; + using System.Diagnostics; + using System.Reflection; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// A metadata object containing parsed and computed values related to a single parameter + /// on a C# method that is used a a resolver to a graph field. + /// + [DebuggerDisplay("Parameter: {InternalName}")] + internal class FieldResolverParameterMetaData : IGraphFieldResolverParameterMetaData + { + /// + /// Initializes a new instance of the class. + /// + /// The parameter info for a single parameter within a resolver method. + /// The internal name of the parameter as its declared in source code. + /// The internal name of the parent method that owns this parameter. + /// Any modifier attributes for this parameter discovered via templating or set + /// at runtime by the target schema. + /// if set to true this parameter is expecting a list + /// of items to be passed to it at runtime. + /// if set to true this parameter was declared with an explicitly set default value. + /// The default value assigned to this parameter in source code when the parameter + /// was declared. + public FieldResolverParameterMetaData( + ParameterInfo paramInfo, + string internalName, + string parentInternalName, + GraphArgumentModifiers modifiers, + bool isListBasedParameter, + bool hasDefaultValue, + object defaultValue = null) + { + this.ParameterInfo = Validation.ThrowIfNullOrReturn(paramInfo, nameof(paramInfo)); + this.ExpectedType = this.ParameterInfo.ParameterType; + this.UnwrappedExpectedParameterType = GraphValidation.EliminateWrappersFromCoreType( + this.ExpectedType, + eliminateEnumerables: true, + eliminateTask: true, + eliminateNullableT: false); + + this.ParentInternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(parentInternalName, nameof(parentInternalName)); + this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalName, nameof(internalName)); + this.DefaultValue = defaultValue; + this.ArgumentModifiers = modifiers; + this.HasDefaultValue = hasDefaultValue; + this.IsList = isListBasedParameter; + } + + /// + public ParameterInfo ParameterInfo { get; } + + /// + public string InternalName { get; } + + /// + public GraphArgumentModifiers ArgumentModifiers { get; private set; } + + /// + public object DefaultValue { get; } + + /// + public Type ExpectedType { get; } + + /// + public Type UnwrappedExpectedParameterType { get; } + + /// + public bool IsList { get; } + + /// + public bool HasDefaultValue { get; } + + /// + public string ParentInternalName { get; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Execution/Resolvers/FieldResolverParameterMetaDataCollection.cs b/src/graphql-aspnet/Execution/Resolvers/FieldResolverParameterMetaDataCollection.cs new file mode 100644 index 000000000..e93a65cd2 --- /dev/null +++ b/src/graphql-aspnet/Execution/Resolvers/FieldResolverParameterMetaDataCollection.cs @@ -0,0 +1,84 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Execution.Resolvers +{ + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// A collection of parameter metadata that can be accessed by index in its parent method or by name. + /// + [DebuggerDisplay("Count = {Count}")] + internal class FieldResolverParameterMetaDataCollection : IGraphFieldResolverParameterMetaDataCollection + { + private List _parameters; + private Dictionary _parametersByName; + + /// + /// Initializes a new instance of the class. + /// + /// The parameters to include in this collection. + public FieldResolverParameterMetaDataCollection(IEnumerable parameters = null) + { + parameters = parameters ?? Enumerable.Empty(); + + _parameters = new List(parameters); + + _parametersByName = new Dictionary(_parameters.Count); + + foreach (var paramItem in _parameters) + { + _parametersByName.Add(paramItem.ParameterInfo.Name, paramItem); + if (paramItem.ArgumentModifiers.IsSourceParameter()) + this.SourceParameter = paramItem; + } + } + + /// + public IGraphFieldResolverParameterMetaData FindByName(string parameterName) + { + if (parameterName == null) + return null; + + if (_parametersByName.ContainsKey(parameterName)) + return _parametersByName[parameterName]; + + return null; + } + + /// + public IEnumerator GetEnumerator() + { + return _parameters.GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + /// + public IGraphFieldResolverParameterMetaData this[string parameterName] => _parametersByName[parameterName]; + + /// + public IGraphFieldResolverParameterMetaData this[int index] => _parameters[index]; + + /// + public int Count => _parameters.Count; + + /// + public IGraphFieldResolverParameterMetaData SourceParameter { get; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/Resolvers/FunctionGraphFieldResolver{TSource,TReturn}.cs b/src/graphql-aspnet/Execution/Resolvers/FunctionGraphFieldResolver{TSource,TReturn}.cs similarity index 81% rename from src/graphql-aspnet/Internal/Resolvers/FunctionGraphFieldResolver{TSource,TReturn}.cs rename to src/graphql-aspnet/Execution/Resolvers/FunctionGraphFieldResolver{TSource,TReturn}.cs index 27294b640..af8cef282 100644 --- a/src/graphql-aspnet/Internal/Resolvers/FunctionGraphFieldResolver{TSource,TReturn}.cs +++ b/src/graphql-aspnet/Execution/Resolvers/FunctionGraphFieldResolver{TSource,TReturn}.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers +namespace GraphQL.AspNet.Execution.Resolvers { using System; using System.Threading; @@ -23,7 +23,8 @@ namespace GraphQL.AspNet.Internal.Resolvers /// The expected type of the source data. /// The expected type of the returned data. /// - /// This resolver is used heavily by the introspection system. + /// This resolver is used heavily by the introspection system for simple, static data resolution + /// and extending other more involved resolvers with simple add-on functionality. /// internal class FunctionGraphFieldResolver : IGraphFieldResolver where TSource : class @@ -37,16 +38,17 @@ internal class FunctionGraphFieldResolver : IGraphFieldResolve public FunctionGraphFieldResolver(Func> func) { _func = Validation.ThrowIfNullOrReturn(func, nameof(func)); + this.MetaData = InternalFieldResolverMetaData.CreateMetadata(this.GetType()); } /// public async Task ResolveAsync(FieldResolutionContext context, CancellationToken cancelToken = default) { - var data = await _func(context?.Arguments.SourceData as TSource).ConfigureAwait(false); + var data = await _func(context?.SourceData as TSource).ConfigureAwait(false); context.Result = data; } /// - public Type ObjectType => typeof(TReturn); + public IGraphFieldResolverMetaData MetaData { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/Resolvers/GraphControllerActionResolver.cs b/src/graphql-aspnet/Execution/Resolvers/GraphControllerActionResolver.cs similarity index 86% rename from src/graphql-aspnet/Internal/Resolvers/GraphControllerActionResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/GraphControllerActionResolver.cs index 9fac1eb0e..ed376426a 100644 --- a/src/graphql-aspnet/Internal/Resolvers/GraphControllerActionResolver.cs +++ b/src/graphql-aspnet/Execution/Resolvers/GraphControllerActionResolver.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers +namespace GraphQL.AspNet.Execution.Resolvers { using System; using System.Diagnostics; @@ -28,19 +28,17 @@ namespace GraphQL.AspNet.Internal.Resolvers /// internal class GraphControllerActionResolver : GraphControllerActionResolverBase, IGraphFieldResolver { - private readonly IGraphFieldResolverMethod _actionMethod; - /// /// Initializes a new instance of the class. /// - /// The action method that this resolver will invoke. - public GraphControllerActionResolver(IGraphFieldResolverMethod actionMethod) + /// The metadata describing an action + /// method that this resolver will invoke. + public GraphControllerActionResolver(IGraphFieldResolverMetaData actionResolverMetadata) { - _actionMethod = Validation.ThrowIfNullOrReturn(actionMethod, nameof(actionMethod)); + this.MetaData = Validation.ThrowIfNullOrReturn(actionResolverMetadata, nameof(actionResolverMetadata)); } /// - [DebuggerStepThrough] public async Task ResolveAsync(FieldResolutionContext context, CancellationToken cancelToken = default) { IGraphActionResult result; @@ -52,7 +50,7 @@ public async Task ResolveAsync(FieldResolutionContext context, CancellationToken // create a scoped controller instance for this invocation var controller = context .ServiceProvider? - .GetService(_actionMethod.Parent.ObjectType) as GraphController; + .GetService(this.MetaData.ParentObjectType) as GraphController; isolationManager = context .ServiceProvider? @@ -80,7 +78,7 @@ public async Task ResolveAsync(FieldResolutionContext context, CancellationToken } // invoke the right action method and set a result. - var task = controller.InvokeActionAsync(_actionMethod, context); + var task = controller.InvokeActionAsync(this.MetaData, context); var returnedItem = await task.ConfigureAwait(false); result = this.EnsureGraphActionResult(returnedItem); } @@ -102,6 +100,6 @@ public async Task ResolveAsync(FieldResolutionContext context, CancellationToken } /// - public Type ObjectType => _actionMethod.ObjectType; + public IGraphFieldResolverMetaData MetaData { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/Resolvers/GraphControllerActionResolverBase.cs b/src/graphql-aspnet/Execution/Resolvers/GraphControllerActionResolverBase.cs similarity index 91% rename from src/graphql-aspnet/Internal/Resolvers/GraphControllerActionResolverBase.cs rename to src/graphql-aspnet/Execution/Resolvers/GraphControllerActionResolverBase.cs index 6e8fc0883..fe6344a46 100644 --- a/src/graphql-aspnet/Internal/Resolvers/GraphControllerActionResolverBase.cs +++ b/src/graphql-aspnet/Execution/Resolvers/GraphControllerActionResolverBase.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers +namespace GraphQL.AspNet.Execution.Resolvers { using System.Diagnostics; using GraphQL.AspNet.Controllers.ActionResults; @@ -31,7 +31,7 @@ protected virtual IGraphActionResult EnsureGraphActionResult(object result) if (result is IGraphActionResult actionResult) return actionResult; - return new ObjectReturnedGraphActionResult(result); + return new OperationCompleteGraphActionResult(result); } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/Resolvers/GraphControllerRouteFieldResolver.cs b/src/graphql-aspnet/Execution/Resolvers/GraphControllerRouteFieldResolver.cs similarity index 89% rename from src/graphql-aspnet/Internal/Resolvers/GraphControllerRouteFieldResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/GraphControllerRouteFieldResolver.cs index c32d61b59..bf418aa5d 100644 --- a/src/graphql-aspnet/Internal/Resolvers/GraphControllerRouteFieldResolver.cs +++ b/src/graphql-aspnet/Execution/Resolvers/GraphControllerRouteFieldResolver.cs @@ -7,9 +7,8 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers +namespace GraphQL.AspNet.Execution.Resolvers { - using System; using System.Threading; using System.Threading.Tasks; using GraphQL.AspNet.Common; @@ -35,6 +34,8 @@ public GraphControllerRouteFieldResolver(VirtualResolvedObject dataObject) { Validation.ThrowIfNull(dataObject, nameof(dataObject)); _dataObject = dataObject; + + this.MetaData = InternalFieldResolverMetaData.CreateMetadata(this.GetType()); } /// @@ -45,6 +46,6 @@ public Task ResolveAsync(FieldResolutionContext context, CancellationToken cance } /// - public Type ObjectType => typeof(object); + public IGraphFieldResolverMetaData MetaData { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/Resolvers/GraphDirectiveActionResolver.cs b/src/graphql-aspnet/Execution/Resolvers/GraphDirectiveActionResolver.cs similarity index 77% rename from src/graphql-aspnet/Internal/Resolvers/GraphDirectiveActionResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/GraphDirectiveActionResolver.cs index db5213a48..32670cca9 100644 --- a/src/graphql-aspnet/Internal/Resolvers/GraphDirectiveActionResolver.cs +++ b/src/graphql-aspnet/Execution/Resolvers/GraphDirectiveActionResolver.cs @@ -7,9 +7,10 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers +namespace GraphQL.AspNet.Execution.Resolvers { using System; + using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using GraphQL.AspNet.Common; @@ -21,7 +22,8 @@ namespace GraphQL.AspNet.Internal.Resolvers using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Interfaces.Execution; - using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Schemas.TypeSystem; using Microsoft.Extensions.DependencyInjection; /// @@ -31,27 +33,25 @@ namespace GraphQL.AspNet.Internal.Resolvers /// internal class GraphDirectiveActionResolver : GraphControllerActionResolverBase, IGraphDirectiveResolver { - private readonly IGraphDirectiveTemplate _directiveTemplate; - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The directive template from which this resolver will - /// query for lifecycle methods. - public GraphDirectiveActionResolver(IGraphDirectiveTemplate directiveTemplate) + /// The directive metadata items, per location, + /// that this resolver will use for executing the directive. + public GraphDirectiveActionResolver(IReadOnlyDictionary directiveMetadataItems) { - _directiveTemplate = Validation.ThrowIfNullOrReturn(directiveTemplate, nameof(directiveTemplate)); + this.MetaData = Validation.ThrowIfNullOrReturn(directiveMetadataItems, nameof(directiveMetadataItems)); } /// public async Task ResolveAsync(DirectiveResolutionContext context, CancellationToken cancelToken = default) { - var action = _directiveTemplate.FindMethod(context.Request.InvocationContext.Location); - // if no action is found skip processing of this directive - if (action == null) + if (!this.MetaData.ContainsKey(context.Request.InvocationContext.Location)) return; + var action = this.MetaData[context.Request.InvocationContext.Location]; + IGraphActionResult result; var isolationObtained = false; IGraphQLFieldResolverIsolationManager isolationManager = null; @@ -61,7 +61,7 @@ public async Task ResolveAsync(DirectiveResolutionContext context, CancellationT // create a directive instance for this invocation var directive = context .ServiceProvider? - .GetService(_directiveTemplate.ObjectType) as GraphDirective; + .GetService(action.ParentObjectType) as GraphDirective; isolationManager = context .ServiceProvider? @@ -72,7 +72,7 @@ public async Task ResolveAsync(DirectiveResolutionContext context, CancellationT // fallback: attempt to create the directive if it has no constructor parameters try { - directive = InstanceFactory.CreateInstance(_directiveTemplate.ObjectType) as GraphDirective; + directive = InstanceFactory.CreateInstance(action.ParentObjectType) as GraphDirective; } catch (InvalidOperationException) { @@ -83,7 +83,7 @@ public async Task ResolveAsync(DirectiveResolutionContext context, CancellationT if (directive == null) { result = new RouteNotFoundGraphActionResult( - $"The directive '{_directiveTemplate.InternalFullName}' " + + $"The directive '{action.InternalName}' " + "was not found in the scoped service provider. Any directives that have constructor parameters " + $"must also be registered to the service provider; Try using '{nameof(SchemaOptions.AddGraphType)}' " + $"with the type of your directive at startup."); @@ -92,12 +92,12 @@ public async Task ResolveAsync(DirectiveResolutionContext context, CancellationT { throw new GraphExecutionException( $"No {nameof(IGraphQLFieldResolverIsolationManager)} was configured for the request. " + - $"Unable to determine the isolation requirements for the directive '{_directiveTemplate.InternalFullName}'."); + $"Unable to determine the isolation requirements for the directive '{context.Request.Directive.InternalName}'."); } else { var shouldIsolate = isolationManager - .ShouldIsolate(context.Schema, TypeTemplates.GraphFieldSource.Action); + .ShouldIsolate(context.Schema, GraphFieldSource.Action); if (shouldIsolate) { @@ -127,5 +127,8 @@ public async Task ResolveAsync(DirectiveResolutionContext context, CancellationT // in what ever manner is appropriate for the result itself await result.CompleteAsync(context); } + + /// + public IReadOnlyDictionary MetaData { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/Resolvers/InputObjectValueResolver.cs b/src/graphql-aspnet/Execution/Resolvers/InputObjectValueResolver.cs similarity index 98% rename from src/graphql-aspnet/Internal/Resolvers/InputObjectValueResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/InputObjectValueResolver.cs index c2f73605a..cba037b8c 100644 --- a/src/graphql-aspnet/Internal/Resolvers/InputObjectValueResolver.cs +++ b/src/graphql-aspnet/Execution/Resolvers/InputObjectValueResolver.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers +namespace GraphQL.AspNet.Execution.Resolvers { using System; using System.Collections.Generic; @@ -105,7 +105,9 @@ public object Resolve(IResolvableValueItem resolvableItem, IResolvedVariableColl var actualField = _graphType.Fields.FindField(inputField.Key); if (actualField != null) { - propSetter = _propSetters.ContainsKey(actualField.InternalName) ? _propSetters[actualField.InternalName] : null; + propSetter = _propSetters.ContainsKey(actualField.DeclaredName) + ? _propSetters[actualField.DeclaredName] + : null; } if (resolver == null || propSetter == null) diff --git a/src/graphql-aspnet/Internal/Resolvers/InputValueResolverBase.cs b/src/graphql-aspnet/Execution/Resolvers/InputValueResolverBase.cs similarity index 98% rename from src/graphql-aspnet/Internal/Resolvers/InputValueResolverBase.cs rename to src/graphql-aspnet/Execution/Resolvers/InputValueResolverBase.cs index 327f9633c..ed7596c07 100644 --- a/src/graphql-aspnet/Internal/Resolvers/InputValueResolverBase.cs +++ b/src/graphql-aspnet/Execution/Resolvers/InputValueResolverBase.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers +namespace GraphQL.AspNet.Execution.Resolvers { using GraphQL.AspNet.Interfaces.Execution.QueryPlans.Resolvables; using GraphQL.AspNet.Interfaces.Execution.Variables; diff --git a/src/graphql-aspnet/Internal/Resolvers/InputValueResolverMethodGenerator.cs b/src/graphql-aspnet/Execution/Resolvers/InputValueResolverMethodGenerator.cs similarity index 99% rename from src/graphql-aspnet/Internal/Resolvers/InputValueResolverMethodGenerator.cs rename to src/graphql-aspnet/Execution/Resolvers/InputValueResolverMethodGenerator.cs index a84bc1a3b..40fa69d11 100644 --- a/src/graphql-aspnet/Internal/Resolvers/InputValueResolverMethodGenerator.cs +++ b/src/graphql-aspnet/Execution/Resolvers/InputValueResolverMethodGenerator.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers +namespace GraphQL.AspNet.Execution.Resolvers { using System; using System.Collections.Generic; diff --git a/src/graphql-aspnet/Execution/Resolvers/InternalFieldResolverMetaData.cs b/src/graphql-aspnet/Execution/Resolvers/InternalFieldResolverMetaData.cs new file mode 100644 index 000000000..c9bdaf5cf --- /dev/null +++ b/src/graphql-aspnet/Execution/Resolvers/InternalFieldResolverMetaData.cs @@ -0,0 +1,54 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Execution.Resolvers +{ + using System; + using System.Reflection; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + + /// + /// A helper class for generating valid metadata for introspection and other internal value resolvers + /// + internal static class InternalFieldResolverMetaData + { + /// + /// Creates a metadata item for a method that will function correctly for any calls, checks or log entries. This + /// metadata points to a method that would never be invoked. + /// + /// The type that will masqurade as "owning" the resolver. + /// IGraphFieldResolverMetaData. + internal static IGraphFieldResolverMetaData CreateMetadata(Type owningType) + { + Validation.ThrowIfNull(owningType, nameof(owningType)); + + var methodInfo = typeof(InternalFieldResolverMetaData) + .GetMethod(nameof(InternalValueResolver), BindingFlags.Static | BindingFlags.NonPublic); + + return new FieldResolverMetaData( + methodInfo, + new FieldResolverParameterMetaDataCollection(), + typeof(int), + false, + nameof(InternalValueResolver), + methodInfo.Name, + owningType, + owningType.FriendlyName(), + ItemSource.DesignTime); + } + + private static int InternalValueResolver() + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/Resolvers/Introspeection/Schema_TypeGraphFieldResolver.cs b/src/graphql-aspnet/Execution/Resolvers/Introspeection/Schema_TypeGraphFieldResolver.cs similarity index 86% rename from src/graphql-aspnet/Internal/Resolvers/Introspeection/Schema_TypeGraphFieldResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/Introspeection/Schema_TypeGraphFieldResolver.cs index e2259db3c..0a2fbe5d9 100644 --- a/src/graphql-aspnet/Internal/Resolvers/Introspeection/Schema_TypeGraphFieldResolver.cs +++ b/src/graphql-aspnet/Execution/Resolvers/Introspeection/Schema_TypeGraphFieldResolver.cs @@ -7,9 +7,8 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers.Introspeection +namespace GraphQL.AspNet.Execution.Resolvers.Introspeection { - using System; using System.Threading; using System.Threading.Tasks; using GraphQL.AspNet.Common; @@ -33,12 +32,14 @@ internal class Schema_TypeGraphFieldResolver : IGraphFieldResolver public Schema_TypeGraphFieldResolver(IntrospectedSchema schema) { _schema = Validation.ThrowIfNullOrReturn(schema, nameof(schema)); + + this.MetaData = InternalFieldResolverMetaData.CreateMetadata(this.GetType()); } /// public Task ResolveAsync(FieldResolutionContext resolutionContext, CancellationToken cancelToken = default) { - if (!resolutionContext.Arguments.TryGetArgument("name", out var name)) + if (!resolutionContext.ExecutionSuppliedArguments.TryGetArgument("name", out var name)) { resolutionContext.Messages.Critical("Required Argument 'name' not found."); } @@ -59,6 +60,6 @@ public Task ResolveAsync(FieldResolutionContext resolutionContext, CancellationT } /// - public Type ObjectType => typeof(IntrospectedType); + public IGraphFieldResolverMetaData MetaData { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/Resolvers/Introspeection/Type_EnumValuesGraphFieldResolver.cs b/src/graphql-aspnet/Execution/Resolvers/Introspeection/Type_EnumValuesGraphFieldResolver.cs similarity index 76% rename from src/graphql-aspnet/Internal/Resolvers/Introspeection/Type_EnumValuesGraphFieldResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/Introspeection/Type_EnumValuesGraphFieldResolver.cs index 77ee80b02..c2f1c1eb3 100644 --- a/src/graphql-aspnet/Internal/Resolvers/Introspeection/Type_EnumValuesGraphFieldResolver.cs +++ b/src/graphql-aspnet/Execution/Resolvers/Introspeection/Type_EnumValuesGraphFieldResolver.cs @@ -7,9 +7,8 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers.Introspeection +namespace GraphQL.AspNet.Execution.Resolvers.Introspeection { - using System; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -27,12 +26,13 @@ internal class Type_EnumValuesGraphFieldResolver : IGraphFieldResolver /// public Type_EnumValuesGraphFieldResolver() { + this.MetaData = InternalFieldResolverMetaData.CreateMetadata(this.GetType()); } /// public Task ResolveAsync(FieldResolutionContext resolutionContext, CancellationToken cancelToken = default) { - var sourceData = resolutionContext.Arguments.SourceData as IntrospectedType; + var sourceData = resolutionContext.SourceData as IntrospectedType; if (sourceData == null) { @@ -40,8 +40,8 @@ public Task ResolveAsync(FieldResolutionContext resolutionContext, CancellationT } else { - var includedDeprecated = resolutionContext.Arguments.ContainsKey(Constants.ReservedNames.DEPRECATED_ARGUMENT_NAME) - && (bool)resolutionContext.Arguments[Constants.ReservedNames.DEPRECATED_ARGUMENT_NAME].Value; + var includedDeprecated = resolutionContext.ExecutionSuppliedArguments.ContainsKey(Constants.ReservedNames.DEPRECATED_ARGUMENT_NAME) + && (bool)resolutionContext.ExecutionSuppliedArguments[Constants.ReservedNames.DEPRECATED_ARGUMENT_NAME].Value; if (includedDeprecated) resolutionContext.Result = sourceData.EnumValues; @@ -53,6 +53,6 @@ public Task ResolveAsync(FieldResolutionContext resolutionContext, CancellationT } /// - public Type ObjectType => typeof(IntrospectedEnumValue); + public IGraphFieldResolverMetaData MetaData { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/Resolvers/Introspeection/Type_TypeGraphFieldResolver.cs b/src/graphql-aspnet/Execution/Resolvers/Introspeection/Type_TypeGraphFieldResolver.cs similarity index 76% rename from src/graphql-aspnet/Internal/Resolvers/Introspeection/Type_TypeGraphFieldResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/Introspeection/Type_TypeGraphFieldResolver.cs index 38c03f13a..058fbdb75 100644 --- a/src/graphql-aspnet/Internal/Resolvers/Introspeection/Type_TypeGraphFieldResolver.cs +++ b/src/graphql-aspnet/Execution/Resolvers/Introspeection/Type_TypeGraphFieldResolver.cs @@ -7,9 +7,8 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers.Introspeection +namespace GraphQL.AspNet.Execution.Resolvers.Introspeection { - using System; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -32,7 +31,7 @@ public Type_TypeGraphFieldResolver() /// public Task ResolveAsync(FieldResolutionContext context, CancellationToken cancelToken = default) { - var sourceData = context.Arguments.SourceData as IntrospectedType; + var sourceData = context.SourceData as IntrospectedType; if (sourceData == null) { @@ -40,8 +39,8 @@ public Task ResolveAsync(FieldResolutionContext context, CancellationToken cance } else { - var includedDeprecated = context.Arguments.ContainsKey(Constants.ReservedNames.DEPRECATED_ARGUMENT_NAME) - && (bool)context.Arguments[Constants.ReservedNames.DEPRECATED_ARGUMENT_NAME].Value; + var includedDeprecated = context.ExecutionSuppliedArguments.ContainsKey(Constants.ReservedNames.DEPRECATED_ARGUMENT_NAME) + && (bool)context.ExecutionSuppliedArguments[Constants.ReservedNames.DEPRECATED_ARGUMENT_NAME].Value; if (includedDeprecated) context.Result = sourceData.Fields; @@ -53,6 +52,6 @@ public Task ResolveAsync(FieldResolutionContext context, CancellationToken cance } /// - public Type ObjectType => typeof(IntrospectedField); + public IGraphFieldResolverMetaData MetaData { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/Resolvers/ListInputValueResolver.cs b/src/graphql-aspnet/Execution/Resolvers/ListInputValueResolver.cs similarity index 98% rename from src/graphql-aspnet/Internal/Resolvers/ListInputValueResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/ListInputValueResolver.cs index 481a768b2..82cd369b3 100644 --- a/src/graphql-aspnet/Internal/Resolvers/ListInputValueResolver.cs +++ b/src/graphql-aspnet/Execution/Resolvers/ListInputValueResolver.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers +namespace GraphQL.AspNet.Execution.Resolvers { using System; using System.Collections; diff --git a/src/graphql-aspnet/Internal/Resolvers/ObjectMethodGraphFieldResolver.cs b/src/graphql-aspnet/Execution/Resolvers/ObjectMethodGraphFieldResolver.cs similarity index 76% rename from src/graphql-aspnet/Internal/Resolvers/ObjectMethodGraphFieldResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/ObjectMethodGraphFieldResolver.cs index 2ce9f09fc..2a248b0ab 100644 --- a/src/graphql-aspnet/Internal/Resolvers/ObjectMethodGraphFieldResolver.cs +++ b/src/graphql-aspnet/Execution/Resolvers/ObjectMethodGraphFieldResolver.cs @@ -7,9 +7,10 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers +namespace GraphQL.AspNet.Execution.Resolvers { using System; + using System.Diagnostics; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -23,32 +24,32 @@ namespace GraphQL.AspNet.Internal.Resolvers /// /// A field resolver that will invoke a schema pipeline for whatever schema is beng processed - /// resulting in the configured handling the request. + /// resulting in the configured handling the request. /// + [DebuggerDisplay("Prop Resolver: {MetaData.InternalName}")] internal class ObjectMethodGraphFieldResolver : IGraphFieldResolver { - private readonly IGraphFieldResolverMethod _graphMethod; private readonly MethodInfo _methodInfo; /// /// Initializes a new instance of the class. /// - /// A resolver method that points to a .NET method. - public ObjectMethodGraphFieldResolver(IGraphFieldResolverMethod graphMethod) + /// A resolver method that points to a .NET method. + public ObjectMethodGraphFieldResolver(IGraphFieldResolverMetaData resolverMetadata) { - _graphMethod = Validation.ThrowIfNullOrReturn(graphMethod, nameof(graphMethod)); - _methodInfo = _graphMethod.Method; + this.MetaData = Validation.ThrowIfNullOrReturn(resolverMetadata, nameof(resolverMetadata)); + _methodInfo = this.MetaData.Method; } /// public virtual async Task ResolveAsync(FieldResolutionContext context, CancellationToken cancelToken = default) { - var sourceData = context.Arguments?.SourceData; + var sourceData = context.SourceData; if (sourceData == null) { context.Messages.Critical( "No source data was provided to the field resolver " + - $"for '{_graphMethod.Route.Path}'. Unable to complete the request.", + $"for '{context.Request.Field.Route.Path}'. Unable to complete the request.", Constants.ErrorCodes.INVALID_OBJECT, context.Request.Origin); @@ -56,16 +57,16 @@ public virtual async Task ResolveAsync(FieldResolutionContext context, Cancellat } var typeCheck = _methodInfo.ReflectedType ?? _methodInfo.DeclaringType; - if (context.Arguments.SourceData.GetType() != typeCheck) + if (context.SourceData?.GetType() != typeCheck) { context.Messages.Critical( "The source data provided to the field resolver " + - $"for '{_graphMethod.Route.Path}' could not be coerced into the expected source graph type. See exception for details.", + $"for '{context.Request.Field.Route.Path}' could not be coerced into the expected source graph type. See exception for details.", Constants.ErrorCodes.INVALID_OBJECT, context.Request.Origin, new GraphExecutionException( - $"The method '{_graphMethod.InternalFullName}' expected source data of type " + - $"'{_graphMethod.Parent.ObjectType.FriendlyName()}' but received '{sourceData.GetType().FriendlyName()}' " + + $"The method '{this.MetaData.InternalName}' expected source data of type " + + $"'{this.MetaData.ParentObjectType.FriendlyName()}' but received '{sourceData.GetType().FriendlyName()}' " + "which is not compatible.")); return; @@ -96,17 +97,17 @@ public virtual async Task ResolveAsync(FieldResolutionContext context, Cancellat object data = null; - var paramSet = context.Arguments.PrepareArguments(_graphMethod); - var invoker = InstanceFactory.CreateInstanceMethodInvoker(_graphMethod.Method); + var paramSet = context.ExecutionSuppliedArguments.PrepareArguments(this.MetaData); + var invoker = InstanceFactory.CreateInstanceMethodInvoker(this.MetaData.Method); - var invokableObject = context.Arguments.SourceData as object; + var invokableObject = context.SourceData as object; var invokeReturn = invoker(ref invokableObject, paramSet); - if (_graphMethod.IsAsyncField) + if (this.MetaData.IsAsyncField) { if (invokeReturn is Task task) { await task.ConfigureAwait(false); - data = task.ResultOfTypeOrNull(_graphMethod.ExpectedReturnType); + data = task.ResultOfTypeOrNull(this.MetaData.ExpectedReturnType); } } else @@ -126,7 +127,7 @@ public virtual async Task ResolveAsync(FieldResolutionContext context, Cancellat catch (Exception ex) { context.Messages.Critical( - $"An unknown error occured atttempting to resolve the field '{_graphMethod.Route.Path}'. " + + $"An unknown error occured atttempting to resolve the field '{context.Request.Field.Route.Path}'. " + "See exception for details.", Constants.ErrorCodes.UNHANDLED_EXCEPTION, context.Request.Origin, @@ -140,6 +141,6 @@ public virtual async Task ResolveAsync(FieldResolutionContext context, Cancellat } /// - public Type ObjectType => _graphMethod.ObjectType; + public IGraphFieldResolverMetaData MetaData { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/Resolvers/ObjectPropertyGraphFieldResolver.cs b/src/graphql-aspnet/Execution/Resolvers/ObjectPropertyGraphFieldResolver.cs similarity index 74% rename from src/graphql-aspnet/Internal/Resolvers/ObjectPropertyGraphFieldResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/ObjectPropertyGraphFieldResolver.cs index 1d4f6a630..2c759f55d 100644 --- a/src/graphql-aspnet/Internal/Resolvers/ObjectPropertyGraphFieldResolver.cs +++ b/src/graphql-aspnet/Execution/Resolvers/ObjectPropertyGraphFieldResolver.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers +namespace GraphQL.AspNet.Execution.Resolvers { using System; using System.Diagnostics; @@ -25,29 +25,27 @@ namespace GraphQL.AspNet.Internal.Resolvers /// A resolver that extracts a property from an object and returns it as a field value. /// /// - [DebuggerDisplay("Prop Resolver: {_graphMethod.Name}")] + [DebuggerDisplay("Prop Resolver: {MetaData.InternalFullName}")] internal class ObjectPropertyGraphFieldResolver : IGraphFieldResolver { - private readonly IGraphFieldResolverMethod _graphMethod; - /// /// Initializes a new instance of the class. /// - /// A resolver method that points to a .NET property getter. - public ObjectPropertyGraphFieldResolver(IGraphFieldResolverMethod propertyGetMethod) + /// A set of metadata items that points to a .NET property getter. + public ObjectPropertyGraphFieldResolver(IGraphFieldResolverMetaData resolverMetaData) { - _graphMethod = Validation.ThrowIfNullOrReturn(propertyGetMethod, nameof(propertyGetMethod)); + this.MetaData = Validation.ThrowIfNullOrReturn(resolverMetaData, nameof(resolverMetaData)); } /// public async Task ResolveAsync(FieldResolutionContext context, CancellationToken cancelToken = default) { - var sourceData = context.Arguments.SourceData; + var sourceData = context.SourceData; if (sourceData == null) { context.Messages.Critical( "No source data was provided to the field resolver " + - $"for '{_graphMethod.Name}'. Unable to complete the request.", + $"for '{context.Request.Field.Route.Path}'. Unable to complete the request.", Constants.ErrorCodes.INVALID_OBJECT, context.Request.Origin); @@ -57,33 +55,33 @@ public async Task ResolveAsync(FieldResolutionContext context, CancellationToken // valdidate the incoming source data to ensure its process-able by this property // resolver. If the data is being resolved through an interface or object reference // ensure the provided source data can be converted otherwise ensure the types match exactly. - if (_graphMethod.Parent.ObjectType.IsInterface || _graphMethod.Parent.ObjectType.IsClass) + if (this.MetaData.ParentObjectType.IsInterface || this.MetaData.ParentObjectType.IsClass) { - if (!Validation.IsCastable(sourceData.GetType(), _graphMethod.Parent.ObjectType)) + if (!Validation.IsCastable(sourceData.GetType(), this.MetaData.ParentObjectType)) { context.Messages.Critical( "The source data provided to the field resolver " + - $"for '{_graphMethod.Route.Path}' could not be coerced into the expected source graph type. See exception for details.", + $"for '{context.Request.Field.Route.Path}' could not be coerced into the expected source graph type. See exception for details.", Constants.ErrorCodes.INVALID_OBJECT, context.Request.Origin, new GraphExecutionException( - $"The property '{_graphMethod.InternalFullName}' expected source data that implements the interface " + - $"'{_graphMethod.Parent.ObjectType.FriendlyName()}' but received '{sourceData.GetType().FriendlyName()}' which " + + $"The property '{this.MetaData.InternalName}' expected source data that implements the interface " + + $"'{this.MetaData.ParentObjectType.FriendlyName()}' but received '{sourceData.GetType().FriendlyName()}' which " + "is not compatible.")); return; } } - else if (sourceData.GetType() != _graphMethod.Parent.ObjectType) + else if (sourceData.GetType() != this.MetaData.ParentObjectType) { context.Messages.Critical( "The source data provided to the field resolver " + - $"for '{_graphMethod.Route.Path}' could not be coerced into the expected source graph type. See exception for details.", + $"for '{context.Request.Field.Route.Path}' could not be coerced into the expected source graph type. See exception for details.", Constants.ErrorCodes.INVALID_OBJECT, context.Request.Origin, new GraphExecutionException( - $"The property '{_graphMethod.InternalFullName}' expected source data of type " + - $"'{_graphMethod.Parent.ObjectType.FriendlyName()}' but received '{sourceData.GetType().FriendlyName()}' " + + $"The property '{this.MetaData.InternalName}' expected source data of type " + + $"'{this.MetaData.ParentObjectType.FriendlyName()}' but received '{sourceData.GetType().FriendlyName()}' " + "which is not compatible.")); return; @@ -112,9 +110,9 @@ public async Task ResolveAsync(FieldResolutionContext context, CancellationToken isolationObtained = true; } - var invoker = InstanceFactory.CreateInstanceMethodInvoker(_graphMethod.Method); + var invoker = InstanceFactory.CreateInstanceMethodInvoker(this.MetaData.Method); var invokeReturn = invoker(ref sourceData, new object[0]); - if (_graphMethod.IsAsyncField) + if (this.MetaData.IsAsyncField) { if (invokeReturn is Task task) { @@ -122,17 +120,17 @@ public async Task ResolveAsync(FieldResolutionContext context, CancellationToken if (task.IsFaulted) throw task.UnwrapException(); - invokeReturn = task.ResultOfTypeOrNull(_graphMethod.ExpectedReturnType); + invokeReturn = task.ResultOfTypeOrNull(this.MetaData.ExpectedReturnType); } else { context.Messages.Critical( "The source data provided to the field resolver " + - $"for '{_graphMethod.Route.Path}' could not be coerced into the expected source graph type. See exception for details.", + $"for '{context.Request.Field.Route.Path}' could not be coerced into the expected source graph type. See exception for details.", Constants.ErrorCodes.INVALID_OBJECT, context.Request.Origin, new GraphExecutionException( - $"The method '{_graphMethod.Route.Path}' is defined " + + $"The method '{context.Request.Field.Route.Path}' is defined " + $"as asyncronous but it did not return a {typeof(Task)}.")); invokeReturn = null; } @@ -150,7 +148,7 @@ public async Task ResolveAsync(FieldResolutionContext context, CancellationToken catch (Exception ex) { context.Messages.Critical( - $"An unknown error occured atttempting to resolve the field '{_graphMethod.Route.Path}'. See exception for details.", + $"An unknown error occured atttempting to resolve the field '{context.Request.Field.Route.Path}'. See exception for details.", Constants.ErrorCodes.UNHANDLED_EXCEPTION, context.Request.Origin, ex); @@ -163,6 +161,6 @@ public async Task ResolveAsync(FieldResolutionContext context, CancellationToken } /// - public Type ObjectType => _graphMethod.ObjectType; + public IGraphFieldResolverMetaData MetaData { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/Resolvers/ScalarInputValueResolver.cs b/src/graphql-aspnet/Execution/Resolvers/ScalarInputValueResolver.cs similarity index 98% rename from src/graphql-aspnet/Internal/Resolvers/ScalarInputValueResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/ScalarInputValueResolver.cs index c11a270c1..93027b54e 100644 --- a/src/graphql-aspnet/Internal/Resolvers/ScalarInputValueResolver.cs +++ b/src/graphql-aspnet/Execution/Resolvers/ScalarInputValueResolver.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.Resolvers +namespace GraphQL.AspNet.Execution.Resolvers { using GraphQL.AspNet.Common; using GraphQL.AspNet.Execution.Exceptions; diff --git a/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DirectiveExecution/DirectiveValidation/Rule_5_7_ArgumentsMustbeValid.cs b/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DirectiveExecution/DirectiveValidation/Rule_5_7_ArgumentsMustbeValid.cs index 39511d217..bd9920cdc 100644 --- a/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DirectiveExecution/DirectiveValidation/Rule_5_7_ArgumentsMustbeValid.cs +++ b/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DirectiveExecution/DirectiveValidation/Rule_5_7_ArgumentsMustbeValid.cs @@ -18,7 +18,7 @@ namespace GraphQL.AspNet.Execution.RulesEngine.RuleSets.DirectiveExecution.Direc using GraphQL.AspNet.Execution.QueryPlans.InputArguments; using GraphQL.AspNet.Execution.RulesEngine.RuleSets.DirectiveExecution.Common; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; + using GraphQL.AspNet.Schemas; /// /// A rule to ensure that the location where the directive is being diff --git a/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/FieldSelectionSteps/Rule_5_4_2_1_RequiredArgumentMustBeSuppliedOrHaveDefaultValueOnField.cs b/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/FieldSelectionSteps/Rule_5_4_2_1_RequiredArgumentMustBeSuppliedOrHaveDefaultValueOnField.cs index 4b322e613..f5b9ed001 100644 --- a/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/FieldSelectionSteps/Rule_5_4_2_1_RequiredArgumentMustBeSuppliedOrHaveDefaultValueOnField.cs +++ b/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/FieldSelectionSteps/Rule_5_4_2_1_RequiredArgumentMustBeSuppliedOrHaveDefaultValueOnField.cs @@ -32,14 +32,6 @@ public override bool Execute(DocumentValidationContext context) var suppliedArguments = fieldSelection.Arguments; foreach (var argument in fieldSelection.Field.Arguments) { - // any argument flaged as being a source input (such as for type extensions) - // or internal (such as subscription event sources) - // and can be skipped when validating query document - if (argument.ArgumentModifiers.IsSourceParameter()) - continue; - if (argument.ArgumentModifiers.IsInternalParameter()) - continue; - if (argument.IsRequired && !suppliedArguments.ContainsKey(argument.Name.AsMemory())) { diff --git a/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/QueryDirectiveSteps/Rule_5_4_2_1_RequiredArgumentMustBeSuppliedOrHaveDefaultValueOnDirective.cs b/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/QueryDirectiveSteps/Rule_5_4_2_1_RequiredArgumentMustBeSuppliedOrHaveDefaultValueOnDirective.cs index 7f8e61530..17cd1ad4b 100644 --- a/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/QueryDirectiveSteps/Rule_5_4_2_1_RequiredArgumentMustBeSuppliedOrHaveDefaultValueOnDirective.cs +++ b/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/QueryDirectiveSteps/Rule_5_4_2_1_RequiredArgumentMustBeSuppliedOrHaveDefaultValueOnDirective.cs @@ -37,14 +37,6 @@ public override bool Execute(DocumentValidationContext context) // inspect all declared arguments from the schema foreach (var argument in directive.Arguments) { - // any argument flaged as being a source input (such as for type extensions) - // or internal (such as subscription event sources) - // and can be skipped when validating query document - if (argument.ArgumentModifiers.IsSourceParameter()) - continue; - if (argument.ArgumentModifiers.IsInternalParameter()) - continue; - if (argument.IsRequired && !suppliedArgs.ContainsKey(argument.Name.AsMemory())) { this.ValidationError( diff --git a/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/QueryOperationSteps/Rule_5_8_VariableDeclarationChecks.cs b/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/QueryOperationSteps/Rule_5_8_VariableDeclarationChecks.cs index 919e54591..4a378523c 100644 --- a/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/QueryOperationSteps/Rule_5_8_VariableDeclarationChecks.cs +++ b/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/QueryOperationSteps/Rule_5_8_VariableDeclarationChecks.cs @@ -317,7 +317,6 @@ private bool Check585_IsVariableUsageAllowed( if (iof.Field == null) return true; - // TODO: Add support for default input values on fields (github issue #70) hasLocationDefaultValue = !iof.Field.IsRequired; originalLocationType = iof.Field.TypeExpression; argName = iof.Name; diff --git a/src/graphql-aspnet/Execution/RulesEngine/RuleSets/FieldResolution/FieldCompletion/GraphDataItem_ResolveFieldStatus.cs b/src/graphql-aspnet/Execution/RulesEngine/RuleSets/FieldResolution/FieldCompletion/GraphDataItem_ResolveFieldStatus.cs index 9c7411a6a..d9194c806 100644 --- a/src/graphql-aspnet/Execution/RulesEngine/RuleSets/FieldResolution/FieldCompletion/GraphDataItem_ResolveFieldStatus.cs +++ b/src/graphql-aspnet/Execution/RulesEngine/RuleSets/FieldResolution/FieldCompletion/GraphDataItem_ResolveFieldStatus.cs @@ -12,6 +12,7 @@ namespace GraphQL.AspNet.Execution.RulesEngine.RuleSets.FieldResolution.FieldCom using GraphQL.AspNet.Execution.Contexts; using GraphQL.AspNet.Execution.FieldResolution; using GraphQL.AspNet.Execution.RulesEngine.RuleSets.FieldResolution.Common; + using GraphQL.AspNet.Schemas.TypeSystem; /// /// Updates the status of the data item on the context based on its current state. @@ -32,10 +33,18 @@ public override bool Execute(FieldValidationContext context) if (context.DataItem.Status == FieldDataItemResolutionStatus.ResultAssigned) { // if a data value was set ensure any potnetial children are processed - if (context.DataItem.ResultData == null || context.DataItem.FieldContext.Field.IsLeaf) + if (context.DataItem.ResultData == null) + { context.DataItem.Complete(); + } else - context.DataItem.RequireChildResolution(); + { + var graphType = context.Schema.KnownTypes.FindGraphType(context.Field.TypeExpression.TypeName); + if (graphType != null && graphType.Kind.IsLeafKind()) + context.DataItem.Complete(); + else + context.DataItem.RequireChildResolution(); + } } else { diff --git a/src/graphql-aspnet/Execution/RulesEngine/RuleSets/FieldResolution/FieldCompletion/Rule_6_4_3_SchemaValueCompletion.cs b/src/graphql-aspnet/Execution/RulesEngine/RuleSets/FieldResolution/FieldCompletion/Rule_6_4_3_SchemaValueCompletion.cs index a7b2edb91..3fd5e9507 100644 --- a/src/graphql-aspnet/Execution/RulesEngine/RuleSets/FieldResolution/FieldCompletion/Rule_6_4_3_SchemaValueCompletion.cs +++ b/src/graphql-aspnet/Execution/RulesEngine/RuleSets/FieldResolution/FieldCompletion/Rule_6_4_3_SchemaValueCompletion.cs @@ -12,7 +12,6 @@ namespace GraphQL.AspNet.Execution.RulesEngine.RuleSets.FieldResolution.FieldCom using GraphQL.AspNet.Execution.Contexts; using GraphQL.AspNet.Execution.FieldResolution; using GraphQL.AspNet.Execution.RulesEngine.RuleSets.FieldResolution.Common; - using GraphQL.AspNet.Internal; using GraphQL.AspNet.Schemas; /// diff --git a/src/graphql-aspnet/Execution/RulesEngine/RuleSets/FieldResolution/FieldCompletion/Rule_6_4_3_ServerValueCompletion.cs b/src/graphql-aspnet/Execution/RulesEngine/RuleSets/FieldResolution/FieldCompletion/Rule_6_4_3_ServerValueCompletion.cs index 449e26e2e..5cce4dace 100644 --- a/src/graphql-aspnet/Execution/RulesEngine/RuleSets/FieldResolution/FieldCompletion/Rule_6_4_3_ServerValueCompletion.cs +++ b/src/graphql-aspnet/Execution/RulesEngine/RuleSets/FieldResolution/FieldCompletion/Rule_6_4_3_ServerValueCompletion.cs @@ -16,7 +16,7 @@ namespace GraphQL.AspNet.Execution.RulesEngine.RuleSets.FieldResolution.FieldCom using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Execution.FieldResolution; using GraphQL.AspNet.Execution.RulesEngine.RuleSets.FieldResolution.Common; - using GraphQL.AspNet.Internal; + using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.TypeSystem; /// diff --git a/src/graphql-aspnet/Execution/Variables/ResolvedVariableGenerator.cs b/src/graphql-aspnet/Execution/Variables/ResolvedVariableGenerator.cs index 7bf2ba08a..652259ef6 100644 --- a/src/graphql-aspnet/Execution/Variables/ResolvedVariableGenerator.cs +++ b/src/graphql-aspnet/Execution/Variables/ResolvedVariableGenerator.cs @@ -13,12 +13,12 @@ namespace GraphQL.AspNet.Execution.Variables using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Execution.Parsing.NodeBuilders; using GraphQL.AspNet.Execution.QueryPlans; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Execution.QueryPlans.DocumentParts; using GraphQL.AspNet.Interfaces.Execution.QueryPlans.Resolvables; using GraphQL.AspNet.Interfaces.Execution.Variables; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.Resolvers; /// /// An object that attempts to convert the untyped keyvalue pairs (usually pulled from a json doc) diff --git a/src/graphql-aspnet/GraphQLProviders.cs b/src/graphql-aspnet/GraphQLProviders.cs deleted file mode 100644 index f40e3f761..000000000 --- a/src/graphql-aspnet/GraphQLProviders.cs +++ /dev/null @@ -1,43 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet -{ - using GraphQL.AspNet.Engine; - using GraphQL.AspNet.Interfaces.Engine; - using GraphQL.AspNet.Interfaces.Schema; - - /// - /// A global set of providers used throughout GraphQL.AspNet. These objects are static, unchanged and expected to - /// not change at runtime. Do not alter the contents of the static properties after calling .AddGraphQL(). - /// - public static class GraphQLProviders - { - /// - /// Gets or sets the globally available template provider used by this graphql server. This object manages the schema agnostic collection - /// of meta data for all .NET types being used as a graph type in a schema; be that controllers, interfaces, unions model/data POCOs. - /// - /// The global template provider. - public static IGraphTypeTemplateProvider TemplateProvider { get; set; } = new DefaultTypeTemplateProvider(); - - /// - /// Gets or sets the globally available provider for managing scalars. This object manages all known scalars - /// across all schemas registered to this application domain. - /// - /// The global scalar provider. - public static IScalarGraphTypeProvider ScalarProvider { get; set; } = new DefaultScalarGraphTypeProvider(); - - /// - /// Gets or sets an abstract factory that generates "type makers" that can create a new instance of - /// any from a template for use in a schema. - /// - /// The graph type maker provider. - public static IGraphTypeMakerProvider GraphTypeMakerProvider { get; set; } = new DefaultGraphTypeMakerProvider(); - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/GraphQLServerSettings.cs b/src/graphql-aspnet/GraphQLServerSettings.cs index daa1cf59e..21483aa84 100644 --- a/src/graphql-aspnet/GraphQLServerSettings.cs +++ b/src/graphql-aspnet/GraphQLServerSettings.cs @@ -11,7 +11,6 @@ namespace GraphQL.AspNet { using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Directives; - using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; /// diff --git a/src/graphql-aspnet/Interfaces/Configuration/IGraphQLServerExtension.cs b/src/graphql-aspnet/Interfaces/Configuration/IGraphQLServerExtension.cs index 47e25dda0..9f6c9e65e 100644 --- a/src/graphql-aspnet/Interfaces/Configuration/IGraphQLServerExtension.cs +++ b/src/graphql-aspnet/Interfaces/Configuration/IGraphQLServerExtension.cs @@ -11,11 +11,12 @@ namespace GraphQL.AspNet.Interfaces.Configuration { using System; using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Interfaces.Schema; using Microsoft.AspNetCore.Builder; /// /// - /// An interface that can be used to configure custom extensions to a schema. An extension can be almost + /// An object that can be used to configure custom extensions to a schema. An extension can be almost /// anthing that customizes the runtime for the target schema. /// /// @@ -27,27 +28,82 @@ public interface IGraphQLServerExtension /// /// /// This method is called by the schema configuration just before it is added to the extensions - /// collection. Use this method to do any sort of internal configuration, default settings, + /// collection for the target schema. Use this method to do any sort of internal configuration, default settings, /// additional DI container registrations etc. /// /// - /// This method represents the last opportunity for this extension to modify its own required - /// service collection before being incorporated with the DI container. + /// This method represents the last opportunity for this extension to add its own required + /// service collection registrations to the DI container. /// /// - /// The schema options collection to which this extension + /// The schema options representing the schema to which this extension /// is being registered. - void Configure(SchemaOptions options); + public void Configure(SchemaOptions options) + { + } /// - /// Instructs this extension to perform any final setup requirements as part of - /// its configuration during startup. + /// Instructs this extension to perform any final setup requirements before the server starts. This + /// method is called as part of .UseGraphQL() during startup. All extensions are called in the order + /// they are registered. /// + /// + /// When this method is called, construction of the DI container is complete. The schema has not been + /// generated yet. + /// /// The application builder to register against. May be null in some rare instances /// where the middleware pipeline is not being setup. Usually during some unit testing edge cases. - /// The configured service provider completed during setup. In + public void UseExtension(IApplicationBuilder app) + { + this.UseExtension(app, null); + } + + /// + /// Instructs this extension to perform any final setup requirements before the server starts. This + /// method is called as part of .UseGraphQL() during startup. All extensions are called in the order + /// they are registered. + /// + /// + /// When this method is called, construction of the DI container is complete. The schema has not been + /// generated yet. + /// + /// The configured service provider created during setup. + public void UseExtension(IServiceProvider serviceProvider) + { + this.UseExtension(null, serviceProvider); + } + + /// + /// Instructs this extension to perform any final setup requirements before the server starts. This + /// method is called as part of .UseGraphQL() during startup. All extensions are called in the order + /// they are registered. + /// + /// + /// When this method is called, construction of the DI container is complete. The schema has not been + /// generated yet. + /// + /// The application builder to register against. May be null in some rare instances + /// where the middleware pipeline is not being setup. Usually during some unit testing edge cases. + /// The configured service provider created during setup. In /// most instances, this will be the instances /// from . - void UseExtension(IApplicationBuilder app = null, IServiceProvider serviceProvider = null); + public void UseExtension(IApplicationBuilder app, IServiceProvider serviceProvider) + { + } + + /// + /// + /// Allows the extension the option to modify or inspect the integrity of a schema instance as its being built in order + /// to apply any necessary logic or updates to the schema items that have been registered and parsed during setup. + /// + /// + /// This method is invoked after all graph types and directives have been discovered and + /// just before type system directives are applied. + /// + /// + /// The schema to process. + public void EnsureSchema(ISchema schema) + { + } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Configuration/ISchemaBuilder.cs b/src/graphql-aspnet/Interfaces/Configuration/ISchemaBuilder.cs index 02498c01b..885be25aa 100644 --- a/src/graphql-aspnet/Interfaces/Configuration/ISchemaBuilder.cs +++ b/src/graphql-aspnet/Interfaces/Configuration/ISchemaBuilder.cs @@ -17,42 +17,12 @@ namespace GraphQL.AspNet.Interfaces.Configuration /// /// A builder for performing advanced configuration of a schema's pipeline and processing settings. /// - /// The type of the schema this builder is created for. - public interface ISchemaBuilder - where TSchema : class, ISchema + public interface ISchemaBuilder { /// /// Gets the completed options used to configure this schema. /// - /// The options. + /// The options used to create and configure this builder. SchemaOptions Options { get; } - - /// - /// Gets a builder to construct the field execution pipeline. This pipeline is invoked per field resolution request to generate a - /// piece of data in the process of fulfilling the primary query. - /// - /// The field execution pipeline. - ISchemaPipelineBuilder FieldExecutionPipeline { get; } - - /// - /// Gets a builder to construct the field authorization pipeline. This pipeline is invoked per field resolution request to authorize - /// the user to the field allowing or denying them access to it. - /// - /// The field authorization pipeline. - ISchemaPipelineBuilder SchemaItemSecurityPipeline { get; } - - /// - /// Gets a builder to construct the primary query pipeline. This pipeline oversees the processing of a query and is invoked - /// directly by the http handler. - /// - /// The query execution pipeline. - ISchemaPipelineBuilder QueryExecutionPipeline { get; } - - /// - /// Gets the build useto construct the primary directive execution pipeline. This pipeline oversees the processing of directives against - /// data items for both the type system initial construction as well as during query execution. - /// - /// The directive execution pipeline. - ISchemaPipelineBuilder DirectiveExecutionPipeline { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Configuration/ISchemaBuilder{TSchema}.cs b/src/graphql-aspnet/Interfaces/Configuration/ISchemaBuilder{TSchema}.cs new file mode 100644 index 000000000..052ab9385 --- /dev/null +++ b/src/graphql-aspnet/Interfaces/Configuration/ISchemaBuilder{TSchema}.cs @@ -0,0 +1,52 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Interfaces.Configuration +{ + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution.Contexts; + using GraphQL.AspNet.Interfaces.Middleware; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// A builder for performing advanced configuration of a schema's pipeline and processing settings. + /// + /// The type of the schema this builder is created for. + public interface ISchemaBuilder : ISchemaBuilder + where TSchema : class, ISchema + { + /// + /// Gets a builder to construct the field execution pipeline. This pipeline is invoked per field resolution request to generate a + /// piece of data in the process of fulfilling the primary query. + /// + /// The field execution pipeline. + ISchemaPipelineBuilder FieldExecutionPipeline { get; } + + /// + /// Gets a builder to construct the field authorization pipeline. This pipeline is invoked per field resolution request to authorize + /// the user to the field allowing or denying them access to it. + /// + /// The field authorization pipeline. + ISchemaPipelineBuilder SchemaItemSecurityPipeline { get; } + + /// + /// Gets a builder to construct the primary query pipeline. This pipeline oversees the processing of a query and is invoked + /// directly by the http handler. + /// + /// The query execution pipeline. + ISchemaPipelineBuilder QueryExecutionPipeline { get; } + + /// + /// Gets the build useto construct the primary directive execution pipeline. This pipeline oversees the processing of directives against + /// data items for both the type system initial construction as well as during query execution. + /// + /// The directive execution pipeline. + ISchemaPipelineBuilder DirectiveExecutionPipeline { get; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Configuration/ISchemaConfigurationExtension.cs b/src/graphql-aspnet/Interfaces/Configuration/ISchemaConfigurationExtension.cs deleted file mode 100644 index 1f26b78c5..000000000 --- a/src/graphql-aspnet/Interfaces/Configuration/ISchemaConfigurationExtension.cs +++ /dev/null @@ -1,25 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* -namespace GraphQL.AspNet.Interfaces.Configuration -{ - using GraphQL.AspNet.Interfaces.Schema; - - /// - /// An object that needs to apply some configuration or setup to the schema - /// before its considered "complete" and ready to serve. - /// - public interface ISchemaConfigurationExtension - { - /// - /// Instructs this configuration mechanism to apply itself to the supplied schema. - /// - /// The schema to inspect. - void Configure(ISchema schema); - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Configuration/ISchemaDeclarationConfiguration.cs b/src/graphql-aspnet/Interfaces/Configuration/ISchemaDeclarationConfiguration.cs index c34218189..44919d849 100644 --- a/src/graphql-aspnet/Interfaces/Configuration/ISchemaDeclarationConfiguration.cs +++ b/src/graphql-aspnet/Interfaces/Configuration/ISchemaDeclarationConfiguration.cs @@ -13,7 +13,6 @@ namespace GraphQL.AspNet.Interfaces.Configuration using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Configuration; using GraphQL.AspNet.Configuration.Formatting; - using GraphQL.AspNet.Execution; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas.TypeSystem; @@ -42,6 +41,12 @@ public interface ISchemaDeclarationConfiguration /// The declaration requirements for this schema. TemplateDeclarationRequirements FieldDeclarationRequirements { get; } + /// + /// Gets the tie-breaker selection rule this schema should use when determining how to handle unattributed method parameters. + /// + /// The tie breaker rule to use when evaluating method parameters as potential field arguments. + SchemaArgumentBindingRules ArgumentBindingRule { get; } + /// /// Gets an object used to format the declared names in your C# code as various items in the type system /// for this . diff --git a/src/graphql-aspnet/Interfaces/Configuration/ISchemaExecutionConfiguration.cs b/src/graphql-aspnet/Interfaces/Configuration/ISchemaExecutionConfiguration.cs index 35d864c52..1ac2689c4 100644 --- a/src/graphql-aspnet/Interfaces/Configuration/ISchemaExecutionConfiguration.cs +++ b/src/graphql-aspnet/Interfaces/Configuration/ISchemaExecutionConfiguration.cs @@ -82,5 +82,15 @@ public interface ISchemaExecutionConfiguration /// /// An integer representing the maximum calculated query complexity of a single query plan before its rejected. float? MaxQueryComplexity { get; } + + /// + /// Gets a value that determines the behaviort of the runtime when a required parameter + /// to a field or directive resolver cannot be found. + /// + /// + /// Required refers to a method parameter that does not supply a default value. + /// + /// The resolver parameter resolution rule. + ResolverParameterResolutionRules ResolverParameterResolutionRule { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Engine/IGraphArgumentMaker.cs b/src/graphql-aspnet/Interfaces/Engine/IGraphArgumentMaker.cs index 09eea5015..93bd73cb9 100644 --- a/src/graphql-aspnet/Interfaces/Engine/IGraphArgumentMaker.cs +++ b/src/graphql-aspnet/Interfaces/Engine/IGraphArgumentMaker.cs @@ -8,9 +8,9 @@ // ************************************************************* namespace GraphQL.AspNet.Interfaces.Engine { - using GraphQL.AspNet.Engine.TypeMakers; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; /// /// A maker that can generate input arguments. diff --git a/src/graphql-aspnet/Interfaces/Engine/IGraphFieldMaker.cs b/src/graphql-aspnet/Interfaces/Engine/IGraphFieldMaker.cs index 9da6a05c7..ba954d436 100644 --- a/src/graphql-aspnet/Interfaces/Engine/IGraphFieldMaker.cs +++ b/src/graphql-aspnet/Interfaces/Engine/IGraphFieldMaker.cs @@ -9,9 +9,9 @@ namespace GraphQL.AspNet.Interfaces.Engine { - using GraphQL.AspNet.Engine.TypeMakers; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; /// /// A "maker" that can generate fully qualified graph fields from a given template. diff --git a/src/graphql-aspnet/Interfaces/Engine/IGraphQLSchemaFactory{TSchema}.cs b/src/graphql-aspnet/Interfaces/Engine/IGraphQLSchemaFactory{TSchema}.cs new file mode 100644 index 000000000..aa8597f2e --- /dev/null +++ b/src/graphql-aspnet/Interfaces/Engine/IGraphQLSchemaFactory{TSchema}.cs @@ -0,0 +1,46 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Interfaces.Engine +{ + using System.Collections.Generic; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using Microsoft.Extensions.DependencyInjection; + + /// + /// A factory that, for the given schema type, can generate a fully qualified and usable + /// schema instance. + /// + /// The type of the schema this factory will generate. + public interface IGraphQLSchemaFactory + where TSchema : class, ISchema + { + /// + /// Creates a new, fully populated instance of the schema + /// + /// The service scope used to generate service + /// instances when needed during schema generation. + /// The configuration options + /// that will govern how the schema instantiated. + /// The explicit types register + /// on the schema. + /// The runtime field and type definitions (i.e. minimal api) to add to the schema. + /// The schema extensions to apply to the schema. + /// The completed schema instance. + TSchema CreateInstance( + IServiceScope serviceScope, + ISchemaConfiguration configuration, + IEnumerable typesToRegister = null, + IEnumerable runtimeItemDefinitions = null, + IEnumerable extensionsToExecute = null); + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Engine/IGraphTypeMaker.cs b/src/graphql-aspnet/Interfaces/Engine/IGraphTypeMaker.cs index 41b2127ca..24c86d2d9 100644 --- a/src/graphql-aspnet/Interfaces/Engine/IGraphTypeMaker.cs +++ b/src/graphql-aspnet/Interfaces/Engine/IGraphTypeMaker.cs @@ -10,7 +10,8 @@ namespace GraphQL.AspNet.Interfaces.Engine { using System; - using GraphQL.AspNet.Engine.TypeMakers; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; /// /// An object that can create a specific graph type from its associated concrete type according to a set of rules @@ -20,10 +21,11 @@ public interface IGraphTypeMaker { /// /// Inspects the given type and, in accordance with the rules of this maker, will - /// generate a complete set of necessary graph types required to support it. + /// generate a complete a graph type and a complete set of dependencies required to support it. /// - /// The concrete type to incorporate into the schema. + /// The graph type template to use when creating + /// a new graph type. /// GraphTypeCreationResult. - GraphTypeCreationResult CreateGraphType(Type concreteType); + GraphTypeCreationResult CreateGraphType(IGraphTypeTemplate typeTemplate); } } \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Engine/IGraphTypeMakerProvider.cs b/src/graphql-aspnet/Interfaces/Engine/IGraphTypeMakerProvider.cs deleted file mode 100644 index 6887c37eb..000000000 --- a/src/graphql-aspnet/Interfaces/Engine/IGraphTypeMakerProvider.cs +++ /dev/null @@ -1,51 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Interfaces.Engine -{ - using System; - using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Schemas.TypeSystem; - - /// - /// A abstract factory interface for creating type generator for the various graph type creation operations. - /// - public interface IGraphTypeMakerProvider - { - /// - /// Creates an appropriate graph type maker for the given concrete type. - /// - /// The schema for which the maker should generate graph types for. - /// The kind of graph type to create. If null, the factory will attempt to deteremine the - /// most correct maker to use. - /// IGraphTypeMaker. - IGraphTypeMaker CreateTypeMaker(ISchema schema, TypeKind kind); - - /// - /// Creates a "maker" that can generate graph fields. - /// - /// The schema to which the created fields should belong. - /// IGraphFieldMaker. - IGraphFieldMaker CreateFieldMaker(ISchema schema); - - /// - /// Creates a "maker" that can generate unions for the target schema. - /// - /// The schema to generate unions for. - /// IUnionGraphTypeMaker. - IUnionGraphTypeMaker CreateUnionMaker(ISchema schema); - - /// - /// Attempts to create a union proxy from the given proxy type definition. - /// - /// The type definition of the union proxy to create. - /// IGraphUnionProxy. - IGraphUnionProxy CreateUnionProxyFromType(Type proxyType); - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Engine/IGraphTypeTemplateProvider.cs b/src/graphql-aspnet/Interfaces/Engine/IGraphTypeTemplateProvider.cs deleted file mode 100644 index e38a269ef..000000000 --- a/src/graphql-aspnet/Interfaces/Engine/IGraphTypeTemplateProvider.cs +++ /dev/null @@ -1,58 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Interfaces.Engine -{ - using System; - using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Schemas.TypeSystem; - - /// - /// An interface describing the templating classes used by a schema, at runtime, to parse C# types and build valid graph types. - /// This object is used as a singleton instance for this graphql server and any object implementing this interface - /// should be designed in a thread-safe, singleton fashion. Only one instance of this provider exists for the application instance. - /// - public interface IGraphTypeTemplateProvider - { - /// - /// Removes all cached templates. - /// - void Clear(); - - /// - /// Parses the provided type, extracting the metadata to used in type generation for the object graph. - /// - /// The type of the object to parse. - /// The graph to create a template for. If not supplied the template provider - /// will attempt to assign the best graph type possible. - /// IGraphItemTemplate. - ISchemaItemTemplate ParseType(TypeKind? kind = null); - - /// - /// Parses the provided type, extracting the metadata to used in type generation for the object graph. - /// - /// The type of the object to parse. - /// The graph to create a template for. If not supplied the template provider - /// will attempt to assign the best graph type possible. - /// IGraphTypeTemplate. - IGraphTypeTemplate ParseType(Type objectType, TypeKind? kind = null); - - /// - /// Gets the count of registered objects. - /// - /// The count. - int Count { get; } - - /// - /// Gets or sets a value indicating whether templates, once parsed, are retained. - /// - /// true if templates are cached after the first creation; otherwise, false. - bool CacheTemplates { get; set; } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Engine/IQueryExecutionPlanCacheProvider.cs b/src/graphql-aspnet/Interfaces/Engine/IQueryExecutionPlanCacheProvider.cs index 80c123c1c..a800e03d2 100644 --- a/src/graphql-aspnet/Interfaces/Engine/IQueryExecutionPlanCacheProvider.cs +++ b/src/graphql-aspnet/Interfaces/Engine/IQueryExecutionPlanCacheProvider.cs @@ -12,10 +12,11 @@ namespace GraphQL.AspNet.Interfaces.Engine using System; using System.Threading.Tasks; using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Schemas.TypeSystem; /// /// An interface describing the query plan cache. Build your own cache against any technology you wish - /// and subsitute it in the at start up. This cache instance is a singleton reference + /// and subsitute it in the at start up. This cache instance is a singleton reference /// per server instance. /// public interface IQueryExecutionPlanCacheProvider diff --git a/src/graphql-aspnet/Interfaces/Engine/IScalarGraphTypeProvider.cs b/src/graphql-aspnet/Interfaces/Engine/IScalarGraphTypeProvider.cs deleted file mode 100644 index c4dd8d435..000000000 --- a/src/graphql-aspnet/Interfaces/Engine/IScalarGraphTypeProvider.cs +++ /dev/null @@ -1,113 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Interfaces.Engine -{ - using System; - using System.Collections.Generic; - using GraphQL.AspNet.Interfaces.Schema; - - /// - /// An interface describing the scalar collection used by a schema, at runtime. Scalars are a - /// fundimental unit of graphql and must be explicitly defined. - /// - /// - /// The object implementing this - /// provider should be designed in a thread-safe, singleton fashion. Only one - /// instance of this provider exists for the application instance. - /// - public interface IScalarGraphTypeProvider - { - /// - /// Determines whether the supplied type is considered a leaf type in the graph system - /// (i.e. is the type a scalar or an enumeration). - /// - /// The type to check. - /// true if the specified type is a leaf type; otherwise, false. - bool IsLeaf(Type type); - - /// - /// Converts the given type to its formal reference type removing any - /// nullability modifications that may be applied (e.g. converts int? to int). - /// If the supplied type is already a a formal reference or if it is not a valid scalar type - /// it is returned unchanged. - /// - /// The type to check. - /// The supplied type or the formal scalar type representation if the supplied type - /// is a scalar. - Type EnsureBuiltInTypeReference(Type type); - - /// - /// Determines whether the specified concrete type is a known scalar. - /// - /// Type of the concrete. - /// true if the specified concrete type is a scalar; otherwise, false. - bool IsScalar(Type concreteType); - - /// - /// Determines whether the specified name represents a known scalar. - /// - /// Name of the scalar. This value is case-sensitive. - /// true if the specified name is a scalar; otherwise, false. - bool IsScalar(string scalarName); - - /// - /// Retrieves the mapped concrete type assigned to the given scalar name or null if no - /// scalar is registered. - /// - /// - /// This method converts a scalar name to its primary represented type. (e.g. "Int" => int). - /// - /// Name of the scalar. - /// The system type representing the scalar. - Type RetrieveConcreteType(string scalarName); - - /// - /// Retrieves the name of the scalar registered for the given concrete type. - /// - /// - /// This method converts a type representation to the scalar's common name. (e.g. int => "Int"). - /// - /// The concrete type which is registered as a known scalars. - /// System.String. - string RetrieveScalarName(Type concreteType); - - /// - /// Creates a new instance of the scalar by its defined graph type name or null if no - /// scalar is registered. - /// - /// The common name of the scalar as it exists - /// in a schema. - /// IScalarType. - IScalarGraphType CreateScalar(string scalarName); - - /// - /// Creates a new instance of the scalar by an assigned concrete type or null if no - /// scalar is registered. - /// - /// The concrete type representing the scalar (e.g. int, float, string etc.). - /// IScalarType. - IScalarGraphType CreateScalar(Type concreteType); - - /// - /// Registers the custom scalar type as a pre-parsed template to the provider. This type - /// must implement . - /// - /// The graph type definition of the scalar to register. - /// This type must implement . - void RegisterCustomScalar(Type scalarType); - - /// - /// Gets a list of all registered scalar instance types (i.e. the types that - /// implement ). - /// - /// An enumeration of all registered scalar instance types. - IEnumerable ScalarInstanceTypes { get; } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Engine/IUnionGraphTypeMaker.cs b/src/graphql-aspnet/Interfaces/Engine/IUnionGraphTypeMaker.cs index 8cfe7a628..838393a35 100644 --- a/src/graphql-aspnet/Interfaces/Engine/IUnionGraphTypeMaker.cs +++ b/src/graphql-aspnet/Interfaces/Engine/IUnionGraphTypeMaker.cs @@ -8,8 +8,8 @@ // ************************************************************* namespace GraphQL.AspNet.Interfaces.Engine { - using GraphQL.AspNet.Engine.TypeMakers; using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; /// /// A type maker targeting the generation of unions from pre-configured proxy classes. diff --git a/src/graphql-aspnet/Interfaces/Execution/IExecutionArgumentCollection.cs b/src/graphql-aspnet/Interfaces/Execution/IExecutionArgumentCollection.cs index f92505b43..844e0ef91 100644 --- a/src/graphql-aspnet/Interfaces/Execution/IExecutionArgumentCollection.cs +++ b/src/graphql-aspnet/Interfaces/Execution/IExecutionArgumentCollection.cs @@ -20,24 +20,24 @@ namespace GraphQL.AspNet.Interfaces.Execution public interface IExecutionArgumentCollection : IReadOnlyDictionary { /// - /// Augments the collection with a source data object for a specific field execution and returns - /// a copy of itself with that data attached. + /// Augments the collection with data related the specific resolution. This is used to extract and apply + /// execution arguments supplied on the query to the target resolver. /// - /// The field context being executed. + /// The field or directive context being executed. /// IExecutionArgumentCollection. - IExecutionArgumentCollection ForContext(GraphFieldExecutionContext fieldExecutionContext); + IExecutionArgumentCollection ForContext(SchemaItemResolutionContext resolutionContext); /// - /// Adds the specified argument to the collection. + /// Adds the specified argument value found at runtime. /// - /// The argument. + /// The argument value. void Add(ExecutionArgument argument); /// /// Attempts to retrieve and cast the given argument to the value provided. /// /// The type to cast to. - /// Name of the argument. + /// Name of the argument as it appears in the schema. /// The value to be filled if cast. /// true if the argument was found and cast, false otherwise. bool TryGetArgument(string argumentName, out T value); @@ -48,12 +48,6 @@ public interface IExecutionArgumentCollection : IReadOnlyDictionary /// The graph method. /// System.Object[]. - object[] PrepareArguments(IGraphFieldResolverMethod graphMethod); - - /// - /// Gets the source data, if any, that is supplying values for this execution run. - /// - /// The source data. - object SourceData { get; } + object[] PrepareArguments(IGraphFieldResolverMetaData graphMethod); } } \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Execution/IGraphDirectiveResolver.cs b/src/graphql-aspnet/Interfaces/Execution/IGraphDirectiveResolver.cs index 9f5ad4bec..88b1c78fb 100644 --- a/src/graphql-aspnet/Interfaces/Execution/IGraphDirectiveResolver.cs +++ b/src/graphql-aspnet/Interfaces/Execution/IGraphDirectiveResolver.cs @@ -9,9 +9,11 @@ namespace GraphQL.AspNet.Interfaces.Execution { + using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using GraphQL.AspNet.Execution.Contexts; + using GraphQL.AspNet.Schemas.TypeSystem; /// /// A resolver that can process requests to invoke a directive and produce a result. @@ -26,5 +28,13 @@ public interface IGraphDirectiveResolver /// The cancel token monitoring the execution of a graph request. /// Task. Task ResolveAsync(DirectiveResolutionContext directiveRequest, CancellationToken cancelToken = default); + + /// + /// Gets the metadata set that describes this instance's implementation in the source code. The resolver will + /// use this data to properly instantiate and invoke the configured object methods, Func, dynamic delegates or properties + /// as appropriate. + /// + /// The resolver's metadata collection. + IReadOnlyDictionary MetaData { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Execution/IGraphFieldResolver.cs b/src/graphql-aspnet/Interfaces/Execution/IGraphFieldResolver.cs index db4f1f8a1..aeb381b27 100644 --- a/src/graphql-aspnet/Interfaces/Execution/IGraphFieldResolver.cs +++ b/src/graphql-aspnet/Interfaces/Execution/IGraphFieldResolver.cs @@ -9,7 +9,6 @@ namespace GraphQL.AspNet.Interfaces.Execution { - using System; using System.Threading; using System.Threading.Tasks; using GraphQL.AspNet.Execution.Contexts; @@ -31,10 +30,11 @@ public interface IGraphFieldResolver Task ResolveAsync(FieldResolutionContext context, CancellationToken cancelToken = default); /// - /// Gets the concrete type this resolver attempts to create as a during its invocation (the data type it returns). - /// If this resolver may generate a list, this type should represent a single list item. (i.e. 'string' not 'List{string}'). + /// Gets the metadata that describes this instances implementation in the source code. The resolver will + /// use this data to properly instantiate and invoke the configured object methods, Func, dynamic delegates or properties + /// as appropriate. /// - /// The type of the return. - Type ObjectType { get; } + /// The resolver's metadata collection. + IGraphFieldResolverMetaData MetaData { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Execution/IGraphFieldResolverMethod.cs b/src/graphql-aspnet/Interfaces/Execution/IGraphFieldResolverMetaData.cs similarity index 51% rename from src/graphql-aspnet/Interfaces/Execution/IGraphFieldResolverMethod.cs rename to src/graphql-aspnet/Interfaces/Execution/IGraphFieldResolverMetaData.cs index 22e4a4579..2698456ea 100644 --- a/src/graphql-aspnet/Interfaces/Execution/IGraphFieldResolverMethod.cs +++ b/src/graphql-aspnet/Interfaces/Execution/IGraphFieldResolverMetaData.cs @@ -10,10 +10,8 @@ namespace GraphQL.AspNet.Interfaces.Execution { using System; - using System.Collections.Generic; using System.Reflection; - using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; /// /// A data package describing the details necessary to @@ -23,23 +21,12 @@ namespace GraphQL.AspNet.Interfaces.Execution /// This interface describes the "bridge" between a field on an schema /// and the C# code from which that field originated. /// - public interface IGraphFieldResolverMethod + public interface IGraphFieldResolverMetaData { - /// - /// Gets the type template from which this method was generated. - /// - /// The type template that owns this method. - IGraphTypeTemplate Parent { get; } - - /// - /// Gets the singular concrete type this method is defined on. - /// - /// The objec type that defines this method. - Type ObjectType { get; } - /// /// Gets the type, unwrapped of any tasks, that this graph method should return upon completion. This value - /// represents the implementation return type as opposed to the expected graph type. + /// represents the implementation return type that is needed by the runtime to be success. It may differ from the + /// declared return type of in the case of returning interfaces. /// /// The expected type of data returned by this method. Type ExpectedReturnType { get; } @@ -50,13 +37,6 @@ public interface IGraphFieldResolverMethod /// The method to be invoked. MethodInfo Method { get; } - /// - /// Gets the raw parameters that exist on the - /// that must be supplied at invocation. - /// - /// The parameters of the target . - IReadOnlyList Parameters { get; } - /// /// Gets a value indicating whether the method described by this instance should be /// invoked asyncronously. @@ -65,35 +45,50 @@ public interface IGraphFieldResolverMethod bool IsAsyncField { get; } /// - /// Gets the method's field name in the object graph. + /// Gets the name that defines this item internally. Typically a qualifed method name or property name. (e.g. MyObject.MyMethodName). /// - /// This method's name in the object graph. - string Name { get; } + /// + /// This can be changed by the developer to facilitate logging and messaging identification. + /// + /// The internal name given to this item. + string InternalName { get; } /// - /// Gets the fully qualified name, including namespace, of this item as it exists in the - /// .NET code (e.g. Namespace.ObjectType.MethodName). + /// Gets the name of the resolver (method name or property name) exactly as its declared in source code. /// - /// The fully qualified name given to this item. - string InternalFullName { get; } + /// + /// This cannot be changed by the developer and is used for internal indexing and searching. + /// + /// The name of the resolver as its declared in source code. + string DeclaredName { get; } /// - /// Gets the name that defines this item within the .NET code of the application; - /// typically a method name or property name. + /// Gets the type representing the graph type that will invoke the resolver identified by this + /// metadata object. /// - /// The internal name given to this item. - string InternalName { get; } + /// The concrete type of the parent object that owns the resolver. + Type ParentObjectType { get; } /// - /// Gets the unique route that points to the field in the object graph. + /// Gets the internal name of the parent item that ows the which generated + /// this metdata object. /// - /// The route. - SchemaItemPath Route { get; } + /// The name of the parent. + string ParentInternalName { get; } /// /// Gets the templatized field arguments representing the field (if any). /// /// The arguments defined on this field. - IReadOnlyList Arguments { get; } + IGraphFieldResolverParameterMetaDataCollection Parameters { get; } + + /// + /// Gets a value indicating whether this resolver is defined at runtime. + /// + /// + /// A runtime defined resolver indicates use of minimal api methods. + /// + /// true if this instance is defined at runtime; otherwise, false. + ItemSource DefinitionSource { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Execution/IGraphFieldResolverParameterMetaData.cs b/src/graphql-aspnet/Interfaces/Execution/IGraphFieldResolverParameterMetaData.cs new file mode 100644 index 000000000..4684b06cd --- /dev/null +++ b/src/graphql-aspnet/Interfaces/Execution/IGraphFieldResolverParameterMetaData.cs @@ -0,0 +1,90 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Interfaces.Execution +{ + using System; + using System.Reflection; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// A metadata object containing parsed and computed values related to a single parameter + /// on a C# method that is used a a resolver to a graph field. + /// + /// + /// This metadata object is expressed in terms of the implementation method. That is, .NET + /// terms not GraphQL terms. + /// + public interface IGraphFieldResolverParameterMetaData + { + /// + /// Gets the parameter info that defines the argument. + /// + /// The method to be invoked. + ParameterInfo ParameterInfo { get; } + + /// + /// Gets the type of data expected to be passed to this parameter when its target + /// method is invoked. + /// + /// The expected type of data this parameter can accept. + Type ExpectedType { get; } + + /// + /// Gets the core type represented by + /// + /// + /// If this parameter is not a + /// list this property will be the same as . When this parameter is a list + /// this property will represent the T in List<T> + /// + /// The type of the unwrapped expected parameter. + Type UnwrappedExpectedParameterType { get; } + + /// + /// Gets the name that identifies this item within the .NET code of the application. + /// This is typically a method name or property name. + /// + /// The internal name given to this item. + string InternalName { get; } + + /// + /// Gets the set of argument modifiers created for this parameter via the template that created + /// it. + /// + /// The argument modifiers. + GraphArgumentModifiers ArgumentModifiers { get; } + + /// + /// Gets the default value assigned to this parameter, if any. + /// + /// The default value. + object DefaultValue { get; } + + /// + /// Gets a value indicating whether this instance has an explicitly declared default value + /// + /// true if this instance has an explicitly declared default value; otherwise, false. + bool HasDefaultValue { get; } + + /// + /// Gets a value indicating whether this parameter is expecting a list or collection of items. + /// + /// true if this instance is a list or collection; otherwise, false. + bool IsList { get; } + + /// + /// Gets the internal name of the parent resolver that owns the which generated + /// this metdata object. + /// + /// The name of the parent method. + string ParentInternalName { get; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Execution/IGraphFieldResolverParameterMetaDataCollection.cs b/src/graphql-aspnet/Interfaces/Execution/IGraphFieldResolverParameterMetaDataCollection.cs new file mode 100644 index 000000000..1dbbbd060 --- /dev/null +++ b/src/graphql-aspnet/Interfaces/Execution/IGraphFieldResolverParameterMetaDataCollection.cs @@ -0,0 +1,42 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Interfaces.Execution +{ + using System.Collections.Generic; + + /// + /// A set of metadata items for the parameters of a given resolver. + /// + public interface IGraphFieldResolverParameterMetaDataCollection : IReadOnlyList + { + /// + /// Attempts to find a parameter by its declared name in source code. This name is case sensitive. + /// If not found, null is returned. + /// + /// Name of the parameter as it exists in source code. + /// IGraphFieldResolverParameterMetaData. + IGraphFieldResolverParameterMetaData FindByName(string parameterName); + + /// + /// Gets the with the specified parameter name. This + /// name is case sensitive and should match the parameter declaration in the source code. + /// + /// Name of the parameter as it exists in source code. + /// IGraphFieldResolverParameterMetaData. + IGraphFieldResolverParameterMetaData this[string parameterName] { get; } + + /// + /// Gets the parameter that is to be filled with the value that is supplying source data for the + /// field that will be resolved. (e.g. the result of the nearest ancestor in the query. + /// + /// The source parameter metadata item. + IGraphFieldResolverParameterMetaData SourceParameter { get; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Execution/IGraphQLFieldResolverIsolationManager.cs b/src/graphql-aspnet/Interfaces/Execution/IGraphQLFieldResolverIsolationManager.cs index 2cb96633b..6193f032d 100644 --- a/src/graphql-aspnet/Interfaces/Execution/IGraphQLFieldResolverIsolationManager.cs +++ b/src/graphql-aspnet/Interfaces/Execution/IGraphQLFieldResolverIsolationManager.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Interfaces.Execution using System.Threading; using System.Threading.Tasks; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; /// /// A manager that will allow GraphQL field resolvers to take isolated control diff --git a/src/graphql-aspnet/Interfaces/Internal/IEnumValueTemplate.cs b/src/graphql-aspnet/Interfaces/Internal/IEnumValueTemplate.cs index d8a7e56c5..8d59bda46 100644 --- a/src/graphql-aspnet/Interfaces/Internal/IEnumValueTemplate.cs +++ b/src/graphql-aspnet/Interfaces/Internal/IEnumValueTemplate.cs @@ -32,5 +32,11 @@ public interface IEnumValueTemplate : ISchemaItemTemplate /// /// The numeric value as string. string NumericValueAsString { get; } + + /// + /// Gets the label assigned to this enum value in the source code. (e.g. "Value1" in an enum value named MyEnum.Value1). + /// + /// The declared label. + string DeclaredLabel { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Internal/IGraphArgumentTemplate.cs b/src/graphql-aspnet/Interfaces/Internal/IGraphArgumentTemplate.cs index bcce097a0..804fd25b1 100644 --- a/src/graphql-aspnet/Interfaces/Internal/IGraphArgumentTemplate.cs +++ b/src/graphql-aspnet/Interfaces/Internal/IGraphArgumentTemplate.cs @@ -10,6 +10,7 @@ namespace GraphQL.AspNet.Interfaces.Internal { using System; + using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.TypeSystem; @@ -18,6 +19,12 @@ namespace GraphQL.AspNet.Interfaces.Internal /// public interface IGraphArgumentTemplate : ISchemaItemTemplate { + /// + /// Creates a metadata object representing the parameter parsed by this template. + /// + /// IGraphFieldResolverParameterMetaData. + IGraphFieldResolverParameterMetaData CreateResolverMetaData(); + /// /// Gets the parent method this parameter belongs to. /// @@ -37,18 +44,17 @@ public interface IGraphArgumentTemplate : ISchemaItemTemplate GraphTypeExpression TypeExpression { get; } /// - /// Gets a value indicating that this argument represents the resolved data item created - /// by the resolution of the parent field to this field. If true, this argument will not be available - /// on the object graph. + /// Gets a value indicating what role this argument plays in a resolver, whether it be part of the schema, + /// a parent resolved data value, an injected value from a service provider etc. /// - /// true if this instance is source data field; otherwise, false. - GraphArgumentModifiers ArgumentModifiers { get; } + /// The argument modifier value applied to this parameter. + GraphArgumentModifiers ArgumentModifier { get; } /// /// Gets the name of the argument as its declared in the server side code. /// /// The name of the declared argument. - string DeclaredArgumentName { get; } + string ParameterName { get; } /// /// Gets the input type of this argument as its declared in the C# code base with no modifications or diff --git a/src/graphql-aspnet/Interfaces/Internal/IGraphControllerTemplate.cs b/src/graphql-aspnet/Interfaces/Internal/IGraphControllerTemplate.cs index 58ed0878e..ea4463d22 100644 --- a/src/graphql-aspnet/Interfaces/Internal/IGraphControllerTemplate.cs +++ b/src/graphql-aspnet/Interfaces/Internal/IGraphControllerTemplate.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Interfaces.Internal { using System.Collections.Generic; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; /// /// A special marker interface that identifies a valid diff --git a/src/graphql-aspnet/Interfaces/Internal/IGraphDirectiveTemplate.cs b/src/graphql-aspnet/Interfaces/Internal/IGraphDirectiveTemplate.cs index d7dbb6c38..ba84910c1 100644 --- a/src/graphql-aspnet/Interfaces/Internal/IGraphDirectiveTemplate.cs +++ b/src/graphql-aspnet/Interfaces/Internal/IGraphDirectiveTemplate.cs @@ -24,7 +24,7 @@ public interface IGraphDirectiveTemplate : IGraphTypeTemplate /// /// The location. /// IGraphMethod. - IGraphFieldResolverMethod FindMethod(DirectiveLocation location); + IGraphFieldResolverMetaData FindMetaData(DirectiveLocation location); /// /// Creates a resolver capable of completing a resolution of this directive. diff --git a/src/graphql-aspnet/Interfaces/Internal/IGraphFieldTemplate.cs b/src/graphql-aspnet/Interfaces/Internal/IGraphFieldTemplate.cs index 5d1a0caa9..736c000fa 100644 --- a/src/graphql-aspnet/Interfaces/Internal/IGraphFieldTemplate.cs +++ b/src/graphql-aspnet/Interfaces/Internal/IGraphFieldTemplate.cs @@ -27,6 +27,13 @@ public interface IGraphFieldTemplate : IGraphFieldTemplateBase, ISecureItem /// IGraphFieldResolver. IGraphFieldResolver CreateResolver(); + /// + /// Creates the metadata object that describes the resolver that would be invoked at runtime + /// to fulfil a request the field. + /// + /// IGraphFieldResolverMetaData. + IGraphFieldResolverMetaData CreateResolverMetaData(); + /// /// Gets the return type of this field as its declared in the C# code base with no modifications or /// coerions applied. diff --git a/src/graphql-aspnet/Interfaces/Internal/IGraphFieldTemplateBase.cs b/src/graphql-aspnet/Interfaces/Internal/IGraphFieldTemplateBase.cs index b416dcce3..fad3b3add 100644 --- a/src/graphql-aspnet/Interfaces/Internal/IGraphFieldTemplateBase.cs +++ b/src/graphql-aspnet/Interfaces/Internal/IGraphFieldTemplateBase.cs @@ -10,8 +10,8 @@ namespace GraphQL.AspNet.Interfaces.Internal { using System; using System.Collections.Generic; - using GraphQL.AspNet.Internal.TypeTemplates; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.TypeSystem; /// diff --git a/src/graphql-aspnet/Interfaces/Internal/IGraphItemDependencies.cs b/src/graphql-aspnet/Interfaces/Internal/IGraphItemDependencies.cs index 4b92956d0..cad74ae6c 100644 --- a/src/graphql-aspnet/Interfaces/Internal/IGraphItemDependencies.cs +++ b/src/graphql-aspnet/Interfaces/Internal/IGraphItemDependencies.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Interfaces.Internal { using System.Collections.Generic; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; /// /// An interface representing the collection of dependencies rendered during the creation of an item related to the object graph. diff --git a/src/graphql-aspnet/Interfaces/Internal/IGraphTypeTemplate.cs b/src/graphql-aspnet/Interfaces/Internal/IGraphTypeTemplate.cs index 6f2b7e928..8fa3ba049 100644 --- a/src/graphql-aspnet/Interfaces/Internal/IGraphTypeTemplate.cs +++ b/src/graphql-aspnet/Interfaces/Internal/IGraphTypeTemplate.cs @@ -11,6 +11,7 @@ namespace GraphQL.AspNet.Interfaces.Internal { using GraphQL.AspNet.Configuration; using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.TypeSystem; /// @@ -37,5 +38,11 @@ public interface IGraphTypeTemplate : ISchemaItemTemplate, ISecureItem /// /// true if publish; otherwise, false. bool Publish { get; } + + /// + /// Gets the location where the type this template represents was declared. + /// + /// The template source. + ItemSource TemplateSource { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Internal/IMemberInfoProvider.cs b/src/graphql-aspnet/Interfaces/Internal/IMemberInfoProvider.cs new file mode 100644 index 000000000..259c63c10 --- /dev/null +++ b/src/graphql-aspnet/Interfaces/Internal/IMemberInfoProvider.cs @@ -0,0 +1,33 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Interfaces.Internal +{ + using System.Reflection; + + /// + /// A common interface that exposes the various attributes of a + /// and to the templating system. + /// + public interface IMemberInfoProvider + { + /// + /// Gets the member info object that defines a field. + /// + /// The member information. + public MemberInfo MemberInfo { get; } + + /// + /// Gets the attribute provider that serves up the various control and + /// configuration attributes for generating a graph field from the . + /// + /// The attribute provider. + public ICustomAttributeProvider AttributeProvider { get; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Internal/IScalarGraphTypeTemplate.cs b/src/graphql-aspnet/Interfaces/Internal/IScalarGraphTypeTemplate.cs new file mode 100644 index 000000000..aee5f6ff1 --- /dev/null +++ b/src/graphql-aspnet/Interfaces/Internal/IScalarGraphTypeTemplate.cs @@ -0,0 +1,26 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Interfaces.Internal +{ + using System; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// A template interface representing an SCALAR graph type. + /// + public interface IScalarGraphTypeTemplate : IGraphTypeTemplate + { + /// + /// Gets the type declared as the scalar; the type that implements . + /// + /// The type of the scalar. + Type ScalarType { get; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Internal/ISchemaItemTemplate.cs b/src/graphql-aspnet/Interfaces/Internal/ISchemaItemTemplate.cs index 5311d9381..ca031b956 100644 --- a/src/graphql-aspnet/Interfaces/Internal/ISchemaItemTemplate.cs +++ b/src/graphql-aspnet/Interfaces/Internal/ISchemaItemTemplate.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Interfaces.Internal using System; using System.Collections.Generic; using System.Reflection; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; /// @@ -64,13 +64,8 @@ public interface ISchemaItemTemplate : INamedItemTemplate Type ObjectType { get; } /// - /// Gets the fully qualified name, including namespace, of this item as it exists in the .NET code (e.g. 'Namespace.ObjectType.MethodName'). - /// - /// The internal name given to this item. - string InternalFullName { get; } - - /// - /// Gets the name that defines this item within the .NET code of the application; typically a method name or property name. + /// Gets the name that defines this item within the .NET code of the application; + /// typically a class, struct, method or property name. /// /// The internal name given to this item. string InternalName { get; } diff --git a/src/graphql-aspnet/Interfaces/Internal/IUnionGraphTypeTemplate.cs b/src/graphql-aspnet/Interfaces/Internal/IUnionGraphTypeTemplate.cs new file mode 100644 index 000000000..8fd0f8296 --- /dev/null +++ b/src/graphql-aspnet/Interfaces/Internal/IUnionGraphTypeTemplate.cs @@ -0,0 +1,26 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Interfaces.Internal +{ + using System; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// A template interface representing a UNION graph type. + /// + public interface IUnionGraphTypeTemplate : IGraphTypeTemplate + { + /// + /// Gets the type declared as the union proxy; the type that implements . + /// + /// The type of the union proxy. + Type ProxyType { get; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Logging/IGraphEventLogger.cs b/src/graphql-aspnet/Interfaces/Logging/IGraphEventLogger.cs index 6eca2ff3e..e6306dfef 100644 --- a/src/graphql-aspnet/Interfaces/Logging/IGraphEventLogger.cs +++ b/src/graphql-aspnet/Interfaces/Logging/IGraphEventLogger.cs @@ -137,7 +137,7 @@ void QueryPlanCacheFetchHit(string key) /// /// The action method on the controller being invoked. /// The request being completed by the action method. - void ActionMethodInvocationRequestStarted(IGraphFieldResolverMethod action, IDataRequest request); + void ActionMethodInvocationRequestStarted(IGraphFieldResolverMetaData action, IDataRequest request); /// /// Recorded when a controller completes validation of the model data that will be passed @@ -146,7 +146,7 @@ void QueryPlanCacheFetchHit(string key) /// The action method on the controller being invoked. /// The request being completed by the action method. /// The model data that was validated. - void ActionMethodModelStateValidated(IGraphFieldResolverMethod action, IDataRequest request, InputModelStateDictionary modelState); + void ActionMethodModelStateValidated(IGraphFieldResolverMetaData action, IDataRequest request, InputModelStateDictionary modelState); /// /// Recorded after a controller invokes and receives a result from an action method. @@ -154,7 +154,7 @@ void QueryPlanCacheFetchHit(string key) /// The action method on the controller being invoked. /// The request being completed by the action method. /// The result object that was returned from the action method. - void ActionMethodInvocationCompleted(IGraphFieldResolverMethod action, IDataRequest request, object result); + void ActionMethodInvocationCompleted(IGraphFieldResolverMetaData action, IDataRequest request, object result); /// /// Recorded when the invocation of action method generated a known exception; generally @@ -163,7 +163,7 @@ void QueryPlanCacheFetchHit(string key) /// The action method on the controller being invoked. /// The request being completed by the action method. /// The exception that was generated. - void ActionMethodInvocationException(IGraphFieldResolverMethod action, IDataRequest request, Exception exception); + void ActionMethodInvocationException(IGraphFieldResolverMetaData action, IDataRequest request, Exception exception); /// /// Recorded when the invocation of action method generated an unknown exception. This @@ -172,7 +172,7 @@ void QueryPlanCacheFetchHit(string key) /// The action method on the controller being invoked. /// The request being completed by the action method. /// The exception that was generated. - void ActionMethodUnhandledException(IGraphFieldResolverMethod action, IDataRequest request, Exception exception); + void ActionMethodUnhandledException(IGraphFieldResolverMetaData action, IDataRequest request, Exception exception); /// /// Recorded by an executor after the entire graphql operation has been completed diff --git a/src/graphql-aspnet/Interfaces/Schema/IEnumValue.cs b/src/graphql-aspnet/Interfaces/Schema/IEnumValue.cs index f22abb683..eef409022 100644 --- a/src/graphql-aspnet/Interfaces/Schema/IEnumValue.cs +++ b/src/graphql-aspnet/Interfaces/Schema/IEnumValue.cs @@ -24,13 +24,13 @@ public interface IEnumValue : ISchemaItem, IDeprecatable /// Gets the declared numerical value of the enum. /// /// The value of the neum. - object InternalValue { get; } + object DeclaredValue { get; } /// /// Gets the declared label applied to the enum value by .NET. /// (e.g. 'Value1' for the enum value MyEnum.Value1). /// - /// The internal label. - string InternalLabel { get; } + /// The declared label in source code. + string DeclaredLabel { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Schema/IGraphArgument.cs b/src/graphql-aspnet/Interfaces/Schema/IGraphArgument.cs index 9a12c3b96..9309e2e10 100644 --- a/src/graphql-aspnet/Interfaces/Schema/IGraphArgument.cs +++ b/src/graphql-aspnet/Interfaces/Schema/IGraphArgument.cs @@ -10,7 +10,6 @@ namespace GraphQL.AspNet.Interfaces.Schema { using GraphQL.AspNet.Schemas; - using GraphQL.AspNet.Schemas.TypeSystem; /// /// An argument/input value that can be applied to a field. @@ -24,12 +23,6 @@ public interface IGraphArgument : ITypedSchemaItem, IDefaultValueSchemaItem, ISc /// IGraphField. IGraphArgument Clone(ISchemaItem parent); - /// - /// Gets the argument modifiers that modify how this argument is interpreted by the runtime. - /// - /// The argument modifiers. - GraphArgumentModifiers ArgumentModifiers { get; } - /// /// Gets the type expression that represents the data of this argument (i.e. the '[SomeType!]' /// declaration used in schema definition language.) diff --git a/src/graphql-aspnet/Interfaces/Schema/IGraphArgumentCollection.cs b/src/graphql-aspnet/Interfaces/Schema/IGraphArgumentCollection.cs index ab3206900..05c90057a 100644 --- a/src/graphql-aspnet/Interfaces/Schema/IGraphArgumentCollection.cs +++ b/src/graphql-aspnet/Interfaces/Schema/IGraphArgumentCollection.cs @@ -56,6 +56,12 @@ IGraphArgument AddArgument( Type concreteType, object defaultValue); + /// + /// Removes the specified argument instance from this collection. + /// + /// The argument to remove. + void Remove(IGraphArgument arg); + /// /// Determines whether this collection contains a . Argument /// names are case sensitive and should match the public name as its defined on the target schema @@ -73,6 +79,14 @@ IGraphArgument AddArgument( /// IGraphArgument. IGraphArgument FindArgument(string argumentName); + /// + /// Finds the name of the argument by internally declared name. e.g. the name of the parameter + /// on a C# method. + /// + /// The internal name of the argument. + /// IGraphArgument. + IGraphArgument FindArgumentByParameterName(string internalName); + /// /// Gets the with the specified name. Argument /// names are case sensitive and should match the public name as its defined on the target schema. @@ -94,11 +108,5 @@ IGraphArgument AddArgument( /// /// The count. int Count { get; } - - /// - /// Gets the singular argument that is to recieve source data for the field resolution. - /// - /// The source data argument. - IGraphArgument SourceDataArgument { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Schema/IGraphField.cs b/src/graphql-aspnet/Interfaces/Schema/IGraphField.cs index 735ded683..58f960426 100644 --- a/src/graphql-aspnet/Interfaces/Schema/IGraphField.cs +++ b/src/graphql-aspnet/Interfaces/Schema/IGraphField.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Interfaces.Schema { using GraphQL.AspNet.Execution; using GraphQL.AspNet.Interfaces.Execution; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; /// /// Describes a single field in the type system. This describes how a given field is to be represented with its @@ -42,13 +42,6 @@ public interface IGraphField : IDeprecatable, IGraphArgumentContainer, ISecurabl /// IGraphField. IGraphField Clone(IGraphType parent); - /// - /// Gets a value indicating whether this instance is a leaf field; one capable of generating - /// a real data item vs. generating data to be used in down stream projections. - /// - /// true if this instance is a leaf field; otherwise, false. - bool IsLeaf { get; } - /// /// Gets an object that will perform some operation against an execution /// context to fulfill the requirements of this resolvable entity. diff --git a/src/graphql-aspnet/Interfaces/Schema/IGraphFieldBase.cs b/src/graphql-aspnet/Interfaces/Schema/IGraphFieldBase.cs index 2c8953d25..a223a67e6 100644 --- a/src/graphql-aspnet/Interfaces/Schema/IGraphFieldBase.cs +++ b/src/graphql-aspnet/Interfaces/Schema/IGraphFieldBase.cs @@ -15,7 +15,7 @@ namespace GraphQL.AspNet.Interfaces.Schema /// /// A base set of items common between all field types (input, interface or object based fields). /// - public interface IGraphFieldBase : ISchemaItem + public interface IGraphFieldBase : ITypedSchemaItem, ISchemaItem { /// /// Updates the known graph type this field belongs to. @@ -31,9 +31,10 @@ public interface IGraphFieldBase : ISchemaItem GraphTypeExpression TypeExpression { get; } /// - /// Gets .NET type of the method or property that generated this field as it was declared in code. + /// Gets .NET return type of the method or property that generated this field as it was declared in code. This + /// type may include task wrappers etc. /// - /// The type of the declared return. + /// The .NET declared type returned from this field. public Type DeclaredReturnType { get; } /// @@ -48,13 +49,5 @@ public interface IGraphFieldBase : ISchemaItem /// /// true if publish; otherwise, false. bool Publish { get; set; } - - /// - /// Gets the core type of the object (or objects) returned by this field. If this field - /// is meant to return a list of items, this property represents the type of item in - /// that list. - /// - /// The type of the object. - public Type ObjectType { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Schema/IInputGraphField.cs b/src/graphql-aspnet/Interfaces/Schema/IInputGraphField.cs index 514fdefb1..e886c2408 100644 --- a/src/graphql-aspnet/Interfaces/Schema/IInputGraphField.cs +++ b/src/graphql-aspnet/Interfaces/Schema/IInputGraphField.cs @@ -14,5 +14,10 @@ namespace GraphQL.AspNet.Interfaces.Schema /// public interface IInputGraphField : IGraphFieldBase, IDefaultValueSchemaItem, ITypedSchemaItem { + /// + /// Gets the unaltered name of the property that defines this input field in source code. + /// + /// The property name that generated this data field. + public string DeclaredName { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Schema/INamedItem.cs b/src/graphql-aspnet/Interfaces/Schema/INamedItem.cs index a90cebfa5..cccb96ec5 100644 --- a/src/graphql-aspnet/Interfaces/Schema/INamedItem.cs +++ b/src/graphql-aspnet/Interfaces/Schema/INamedItem.cs @@ -20,6 +20,24 @@ public interface INamedItem /// The publically referenced name of this entity in the graph. string Name { get; } + /// + /// Gets the assigned internal name of this schema item as it exists on the server. This name + /// is used in many exceptions and internal error messages. + /// + /// + /// + /// + /// Examples:
+ /// Scalar: System.Int
+ /// Controller Resolver Method: MyProject.MyController.RetrieveWidgets
+ /// Object Property: MyProject.Widget.Name
+ ///
+ /// This value is customizable by the developer to assign a more specific name if desired. + ///
+ ///
+ /// The assigned internal name of this schema item. + string InternalName { get; } + /// /// Gets or sets the human-readable description distributed with this item /// when requested. The description should accurately describe the contents of this entity diff --git a/src/graphql-aspnet/Interfaces/Schema/IScalarGraphType.cs b/src/graphql-aspnet/Interfaces/Schema/IScalarGraphType.cs index 617f75516..6b984d864 100644 --- a/src/graphql-aspnet/Interfaces/Schema/IScalarGraphType.cs +++ b/src/graphql-aspnet/Interfaces/Schema/IScalarGraphType.cs @@ -19,15 +19,6 @@ namespace GraphQL.AspNet.Interfaces.Schema /// public interface IScalarGraphType : IGraphType, ITypedSchemaItem { - /// - /// Gets a collection of other types that this scalar may be declared as. Scalars maybe - /// represented in C# in multiple formats (e.g. int and int?) but these - /// formats are still the same datatype from the perspective of graphql. This field captures the other known - /// types of this scalar so they can be grouped and processed in a similar manner. - /// - /// The other known types. - TypeCollection OtherKnownTypes { get; } - /// /// Gets the type of the value as it should be supplied on an input argument. Scalar values, from a standpoint of "raw data" can be submitted as /// strings, numbers or a boolean value. A source value resolver would then convert this raw value into its formal scalar representation. @@ -49,6 +40,17 @@ public interface IScalarGraphType : IGraphType, ITypedSchemaItem /// The specified by URL. string SpecifiedByUrl { get; set; } + /// + /// Clones this scalar instance to an exact copy with a new type name. This new type name. + /// + /// The new name to assign to the scalar instance. + /// + /// This method is used during schema generation to generate a graph type named accoridng to the + /// schema's naming rules. + /// + /// IScalarGraphType. + IScalarGraphType Clone(string newName); + /// /// Serializes the scalar from its object representation to a /// value that can be used in JSON serialziation. For most scalars this is diff --git a/src/graphql-aspnet/Interfaces/Schema/ISchemaItem.cs b/src/graphql-aspnet/Interfaces/Schema/ISchemaItem.cs index a8a472ccd..c196081e3 100644 --- a/src/graphql-aspnet/Interfaces/Schema/ISchemaItem.cs +++ b/src/graphql-aspnet/Interfaces/Schema/ISchemaItem.cs @@ -9,6 +9,7 @@ namespace GraphQL.AspNet.Interfaces.Schema { + using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Schemas.Structural; /// diff --git a/src/graphql-aspnet/Interfaces/Schema/ISchemaItemValidator.cs b/src/graphql-aspnet/Interfaces/Schema/ISchemaItemValidator.cs new file mode 100644 index 000000000..4f47ee452 --- /dev/null +++ b/src/graphql-aspnet/Interfaces/Schema/ISchemaItemValidator.cs @@ -0,0 +1,27 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Interfaces.Schema +{ + /// + /// A runtime validator to check that an instance and ensure that it is + /// usable at runtime. + /// + internal interface ISchemaItemValidator + { + /// + /// Validates that the given is valid and internally consistant + /// with the provided schema instance. If the is invalid in anyway an + /// exception must be thrown. + /// + /// The schema item to validate. + /// The schema instance that owns . + void ValidateOrThrow(ISchemaItem schemaItem, ISchema schema); + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Schema/ISchemaTypeCollection.cs b/src/graphql-aspnet/Interfaces/Schema/ISchemaTypeCollection.cs index eb4d476a0..fdf2e8f4c 100644 --- a/src/graphql-aspnet/Interfaces/Schema/ISchemaTypeCollection.cs +++ b/src/graphql-aspnet/Interfaces/Schema/ISchemaTypeCollection.cs @@ -57,7 +57,8 @@ public interface ISchemaTypeCollection : IEnumerable /// if no is found. /// /// The concrete type to search for. - /// The graph type to search for an association of. + /// An optional kind of graph type to search for. Only used in a tie breaker scenario + /// such as if a concrete type is registered as both an OBJECT and INPUT_OBJECT. /// IGraphType. IGraphType FindGraphType(Type concreteType, TypeKind kind); diff --git a/src/graphql-aspnet/Interfaces/Schema/ITypedSchemaItem.cs b/src/graphql-aspnet/Interfaces/Schema/ITypedSchemaItem.cs index f6e1e5d2c..542192f3d 100644 --- a/src/graphql-aspnet/Interfaces/Schema/ITypedSchemaItem.cs +++ b/src/graphql-aspnet/Interfaces/Schema/ITypedSchemaItem.cs @@ -21,19 +21,5 @@ public interface ITypedSchemaItem : ISchemaItem /// /// The type of the object. Type ObjectType { get; } - - /// - /// Gets a fully-qualified, internal name of schema item as it exists on the server. This name - /// is used in many exceptions and internal error messages. - /// - /// - /// - /// Examples:
- /// Scalar: System.Int
- /// Controller Resolver Method: MyProject.MyController.RetrieveWidgets
- /// Object Property: MyProject.Widget.Name
. - ///
- /// The fully qualiified, internal name of this schema item. - string InternalName { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLResolvableSchemaItemDefinition.cs b/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLResolvableSchemaItemDefinition.cs new file mode 100644 index 000000000..a6e487254 --- /dev/null +++ b/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLResolvableSchemaItemDefinition.cs @@ -0,0 +1,41 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions +{ + using System; + + /// + /// A template for a runtime created schema item that has an attached resolver. Usually + /// a field or a directive. + /// + public interface IGraphQLResolvableSchemaItemDefinition : IGraphQLRuntimeSchemaItemDefinition + { + /// + /// Gets or sets the resolver function that has been assigned to execute when this + /// schema item is requested or processed. + /// + /// The field's assigned resolver. + Delegate Resolver { get; set; } + + /// + /// Gets or sets the explicitly declared return type of this schema item. Can be + /// null if the returns a valid concrete type. May also be a + /// type that implements for items that return a union of values. + /// + /// The data type this schema item will return. + Type ReturnType { get; set; } + + /// + /// Gets or sets the internal name that will be applied this item in the schema. + /// + /// The internal name to apply to this schema item when its created. + string InternalName { get; set; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLRuntimeDirectiveDefinition.cs b/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLRuntimeDirectiveDefinition.cs new file mode 100644 index 000000000..5b6439e87 --- /dev/null +++ b/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLRuntimeDirectiveDefinition.cs @@ -0,0 +1,19 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions +{ + /// + /// An intermediate template that utilizies a key/value pair system to build up a set of component parts + /// that the templating engine will use to generate a full fledged field in a schema. + /// + public interface IGraphQLRuntimeDirectiveDefinition : IGraphQLRuntimeSchemaItemDefinition, IGraphQLResolvableSchemaItemDefinition + { + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLRuntimeFieldGroupDefinition.cs b/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLRuntimeFieldGroupDefinition.cs new file mode 100644 index 000000000..f6ec533bf --- /dev/null +++ b/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLRuntimeFieldGroupDefinition.cs @@ -0,0 +1,33 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions +{ + /// + /// An intermediate template that utilizies a key/value pair system to build up a set of component parts + /// that the templating engine will use to generate a full fledged field in a schema. + /// + public interface IGraphQLRuntimeFieldGroupDefinition : IGraphQLRuntimeSchemaItemDefinition + { + /// + /// Creates a new, resolvable field as a child of this group instance. + /// + /// The path template to incorporate on the field. + /// IGraphQLRuntimeResolvedFieldDefinition. + IGraphQLRuntimeResolvedFieldDefinition MapField(string pathTemplate); + + /// + /// Creates an intermediate child group, nested under this group instance with the given + /// path template. + /// + /// The path template to incorpate on the child group. + /// IGraphQLRuntimeFieldGroupDefinition. + IGraphQLRuntimeFieldGroupDefinition MapChildGroup(string pathTemplate); + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLRuntimeResolvedFieldDefinition.cs b/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLRuntimeResolvedFieldDefinition.cs new file mode 100644 index 000000000..8b0d001d2 --- /dev/null +++ b/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLRuntimeResolvedFieldDefinition.cs @@ -0,0 +1,22 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions +{ + /// + /// An intermediate template that utilizies a key/value pair system to build up a set of component parts + /// that the templating engine will use to generate a full fledged field in a schema. + /// A field generated via this builder is identicial to a field parsed from a controller action + /// in that it has an explicit resolver applied. The runtime will not attempt to + /// autoresolve this field. + /// + public interface IGraphQLRuntimeResolvedFieldDefinition : IGraphQLRuntimeSchemaItemDefinition, IGraphQLResolvableSchemaItemDefinition + { + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLRuntimeSchemaItemDefinition.cs b/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLRuntimeSchemaItemDefinition.cs new file mode 100644 index 000000000..3d1a08356 --- /dev/null +++ b/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLRuntimeSchemaItemDefinition.cs @@ -0,0 +1,55 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions +{ + using System; + using System.Collections.Generic; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Schemas.Structural; + + /// + /// A marker templtae for any runtime-built schema item (field, directive etc.) + /// being added to the schema. + /// + public interface IGraphQLRuntimeSchemaItemDefinition + { + /// + /// Adds the given attribute to the collection for this schema item. + /// + /// The attribute to append. + void AddAttribute(Attribute attrib); + + /// + /// Removes the specified attribute from the collection. + /// + /// The attribute to remove. + void RemoveAttribute(Attribute attrib); + + /// + /// Gets the templated name that will be given to the item on the target schema. + /// + /// The fully qualified template for this item. + /// (e.g. '[directive]/@myDirective', '[query]/path1/path2'). + SchemaItemPath Route { get; } + + /// + /// Gets the set of schema options under which this directive is being defined. + /// + /// The schema options on which this directive is being defined. + SchemaOptions Options { get; } + + /// + /// Gets a set of attributes that have been applied to this directive. This mimics + /// the collection of applied attributes to a controller method. + /// + /// The collection of applied attributes. + IEnumerable Attributes { get; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLRuntimeTypeExtensionDefinition.cs b/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLRuntimeTypeExtensionDefinition.cs new file mode 100644 index 000000000..fb3446726 --- /dev/null +++ b/src/graphql-aspnet/Interfaces/Schema/RuntimeDefinitions/IGraphQLRuntimeTypeExtensionDefinition.cs @@ -0,0 +1,34 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions +{ + using System; + using GraphQL.AspNet.Execution; + + /// + /// An intermediate template that utilizies a key/value pair system to build up a set of component parts + /// that the templating engine will use to generate a full fledged type extension in a schema. + /// + public interface IGraphQLRuntimeTypeExtensionDefinition : IGraphQLRuntimeSchemaItemDefinition, IGraphQLResolvableSchemaItemDefinition + { + /// + /// Gets the concrcete type of the OBJECT or INTERFACE that will be extended. + /// + /// The class, interface or struct that will be extended with this new field. + Type TargetType { get; } + + /// + /// Gets or sets the expected processing mode of data when this field is invoked + /// by the runtime. + /// + /// The execution mode of this type extension. + FieldResolutionMode ExecutionMode { get; set; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodExceptionLogEntryBase.cs b/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodExceptionLogEntryBase.cs index 03091c6b7..b943f1530 100644 --- a/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodExceptionLogEntryBase.cs +++ b/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodExceptionLogEntryBase.cs @@ -30,14 +30,14 @@ public abstract class ActionMethodExceptionLogEntryBase : GraphLogEntry /// The exception that was thrown. protected ActionMethodExceptionLogEntryBase( EventId eventId, - IGraphFieldResolverMethod method, + IGraphFieldResolverMetaData method, IDataRequest request, Exception exception) : base(eventId) { this.PipelineRequestId = request?.Id.ToString(); - this.ControllerTypeName = method?.Parent?.InternalFullName; - this.ActionName = method?.Name; + this.ControllerTypeName = method?.ParentInternalName; + this.ActionName = method?.InternalName; this.Exception = new ExceptionLogItem(exception); } diff --git a/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodInvocationCompletedLogEntry.cs b/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodInvocationCompletedLogEntry.cs index d9a8a3347..b45f2d9b2 100644 --- a/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodInvocationCompletedLogEntry.cs +++ b/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodInvocationCompletedLogEntry.cs @@ -27,15 +27,14 @@ public class ActionMethodInvocationCompletedLogEntry : GraphLogEntry /// The method being invoked. /// The request being executed on the method. /// The result that was generated. - public ActionMethodInvocationCompletedLogEntry(IGraphFieldResolverMethod method, IDataRequest request, object result) + public ActionMethodInvocationCompletedLogEntry(IGraphFieldResolverMetaData method, IDataRequest request, object result) : base(LogEventIds.ControllerInvocationCompleted) { this.PipelineRequestId = request?.Id.ToString(); - this.ControllerName = method?.Parent?.InternalFullName; + this.ControllerName = method?.ParentInternalName; this.ActionName = method?.InternalName; - this.FieldPath = method?.Route?.Path; this.ResultTypeName = result?.GetType().FriendlyName(true); - _shortControllerName = method?.Parent?.InternalName; + _shortControllerName = method?.ParentInternalName; } /// @@ -69,16 +68,6 @@ public string ActionName private set => this.SetProperty(LogPropertyNames.ACTION_NAME, value); } - /// - /// Gets the path, in the target schema, of the action. - /// - /// The action name. - public string FieldPath - { - get => this.GetProperty(LogPropertyNames.SCHEMA_ITEM_PATH); - private set => this.SetProperty(LogPropertyNames.SCHEMA_ITEM_PATH, value); - } - /// /// Gets the name of the data that was returned. If the method /// returned a the type of the action result is recorded. diff --git a/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodInvocationExceptionLogEntry.cs b/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodInvocationExceptionLogEntry.cs index de92549a7..168e3c08a 100644 --- a/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodInvocationExceptionLogEntry.cs +++ b/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodInvocationExceptionLogEntry.cs @@ -25,7 +25,7 @@ public class ActionMethodInvocationExceptionLogEntry : ActionMethodExceptionLogE /// The request being executed on the method. /// The exception that was thrown. public ActionMethodInvocationExceptionLogEntry( - IGraphFieldResolverMethod method, + IGraphFieldResolverMetaData method, IDataRequest request, Exception exception) : base( diff --git a/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodInvocationStartedLogEntry.cs b/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodInvocationStartedLogEntry.cs index 0f9c37865..1535eb609 100644 --- a/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodInvocationStartedLogEntry.cs +++ b/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodInvocationStartedLogEntry.cs @@ -10,6 +10,7 @@ namespace GraphQL.AspNet.Logging.GeneralEvents { using System; + using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Interfaces.Execution; /// @@ -25,16 +26,18 @@ public class ActionMethodInvocationStartedLogEntry : GraphLogEntry /// /// The method being invoked. /// The request being executed on the method. - public ActionMethodInvocationStartedLogEntry(IGraphFieldResolverMethod method, IDataRequest request) + public ActionMethodInvocationStartedLogEntry(IGraphFieldResolverMetaData method, IDataRequest request) : base(LogEventIds.ControllerInvocationStarted) { this.PipelineRequestId = request?.Id.ToString(); - this.ControllerName = method?.Parent?.InternalFullName; - this.ActionName = method?.Name; - this.FieldPath = method?.Route?.Path; - this.SourceObjectType = method?.ObjectType?.ToString(); + this.ControllerName = method?.ParentInternalName; + this.ActionName = method?.InternalName; + + if (request is IGraphFieldRequest gfr) + this.SourceObjectType = gfr.InvocationContext?.ExpectedSourceType?.FriendlyName(true); + this.IsAsync = method?.IsAsyncField; - _shortControllerName = method?.Parent?.InternalName; + _shortControllerName = method?.ParentInternalName; } /// @@ -68,16 +71,6 @@ public string ActionName private set => this.SetProperty(LogPropertyNames.ACTION_NAME, value); } - /// - /// Gets the path, in the target schema, of the action. - /// - /// The action name. - public string FieldPath - { - get => this.GetProperty(LogPropertyNames.SCHEMA_ITEM_PATH); - private set => this.SetProperty(LogPropertyNames.SCHEMA_ITEM_PATH, value); - } - /// /// Gets the name of the expected source data object to this /// action. diff --git a/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodModelStateValidatedLogEntry.cs b/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodModelStateValidatedLogEntry.cs index 54ff93d7c..6c6747cd2 100644 --- a/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodModelStateValidatedLogEntry.cs +++ b/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodModelStateValidatedLogEntry.cs @@ -32,17 +32,17 @@ public class ActionMethodModelStateValidatedLogEntry : GraphLogEntry /// The request being executed on the method. /// the model dictionary created by the controller. public ActionMethodModelStateValidatedLogEntry( - IGraphFieldResolverMethod method, + IGraphFieldResolverMetaData method, IDataRequest request, InputModelStateDictionary modelState) : base(LogEventIds.ControllerModelValidated) { this.PipelineRequestId = request?.Id.ToString(); - this.ControllerName = method?.Parent?.ObjectType?.FriendlyName(true) ?? method?.Parent?.Name; - this.ActionName = method?.Name; - this.FieldPath = method?.Route?.Path; + this.ControllerName = method?.ParentInternalName; + this.ActionName = method?.InternalName; this.ModelDataIsValid = modelState?.IsValid; - _shortControllerName = method?.Parent?.ObjectType?.FriendlyName() ?? method?.Parent?.Name; + + _shortControllerName = method?.ParentInternalName; this.ModelItems = null; if (modelState?.Values != null && modelState.Values.Any()) { @@ -90,16 +90,6 @@ public string ActionName private set => this.SetProperty(LogPropertyNames.ACTION_NAME, value); } - /// - /// Gets the path, in the target schema, of the action. - /// - /// The action name. - public string FieldPath - { - get => this.GetProperty(LogPropertyNames.SCHEMA_ITEM_PATH); - private set => this.SetProperty(LogPropertyNames.SCHEMA_ITEM_PATH, value); - } - /// /// Gets a value indicating whether the collective sum of model data is in a valid state /// when its passed to the method for execution. diff --git a/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodUnhandledExceptionLogEntry.cs b/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodUnhandledExceptionLogEntry.cs index 2d2b4645f..177bceec1 100644 --- a/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodUnhandledExceptionLogEntry.cs +++ b/src/graphql-aspnet/Logging/GeneralEvents/ActionMethodUnhandledExceptionLogEntry.cs @@ -25,7 +25,7 @@ public class ActionMethodUnhandledExceptionLogEntry : ActionMethodExceptionLogEn /// The request being executed on the method. /// The exception that was thrown. public ActionMethodUnhandledExceptionLogEntry( - IGraphFieldResolverMethod method, + IGraphFieldResolverMetaData method, IDataRequest request, Exception exception) : base( diff --git a/src/graphql-aspnet/Middleware/DirectiveExecution/Components/InvokeDirectiveResolverMiddleware.cs b/src/graphql-aspnet/Middleware/DirectiveExecution/Components/InvokeDirectiveResolverMiddleware.cs index 066a4a3e5..fa31c4a65 100644 --- a/src/graphql-aspnet/Middleware/DirectiveExecution/Components/InvokeDirectiveResolverMiddleware.cs +++ b/src/graphql-aspnet/Middleware/DirectiveExecution/Components/InvokeDirectiveResolverMiddleware.cs @@ -44,12 +44,19 @@ public async Task InvokeAsync(GraphDirectiveExecutionContext context, GraphMiddl } else { + // resolution context messages + // are seperate by design var resolutionContext = new DirectiveResolutionContext( + context.ServiceProvider, + context.Session, context.Schema, - context, + context.QueryRequest, context.Request, executionArguments, - context.User); + new GraphMessageCollection(), + context.Logger, + context.User, + context.CancellationToken); // execute the directive await context diff --git a/src/graphql-aspnet/Middleware/FieldExecution/Components/InvokeFieldResolverMiddleware.cs b/src/graphql-aspnet/Middleware/FieldExecution/Components/InvokeFieldResolverMiddleware.cs index ec400e931..0c2b25283 100644 --- a/src/graphql-aspnet/Middleware/FieldExecution/Components/InvokeFieldResolverMiddleware.cs +++ b/src/graphql-aspnet/Middleware/FieldExecution/Components/InvokeFieldResolverMiddleware.cs @@ -107,14 +107,18 @@ private async Task ExecuteContextAsync(GraphFieldExecutionContext context, return false; } - executionArguments = executionArguments.ForContext(context); - + // resolution context messages are independent var resolutionContext = new FieldResolutionContext( + context.ServiceProvider, + context.Session, _schema, - context, + context.QueryRequest, context.Request, executionArguments, - context.User); + new GraphMessageCollection(), + context.Logger, + context.User, + context.CancellationToken); // Step 2: Resolve the field context.Logger?.FieldResolutionStarted(resolutionContext); diff --git a/src/graphql-aspnet/Middleware/FieldExecution/Components/ProcessChildFieldsMiddleware.cs b/src/graphql-aspnet/Middleware/FieldExecution/Components/ProcessChildFieldsMiddleware.cs index ac2127eac..c7338ccd7 100644 --- a/src/graphql-aspnet/Middleware/FieldExecution/Components/ProcessChildFieldsMiddleware.cs +++ b/src/graphql-aspnet/Middleware/FieldExecution/Components/ProcessChildFieldsMiddleware.cs @@ -109,8 +109,6 @@ private async Task ProcessDownStreamFieldContextsAsync(GraphFieldExecutionContex return; } - var allChildPipelines = new List(); - // Step 0 // ----------------------------------------------------------------------- // create a lookup of source items by concrete type known to the schema, for easy seperation to the individual @@ -173,6 +171,8 @@ private async Task ProcessDownStreamFieldContextsAsync(GraphFieldExecutionContex executableChildContexts.AddRange(orderedContexts); } + var allChildPipelines = new List(executableChildContexts.Count); + // Step 4 // --------------------------------- // Execute all the child contexts. Order doesn't matter @@ -337,7 +337,9 @@ private IEnumerable CreateChildExecutionContexts( // create a list to house the raw source data being passed for the batch // this is the IEnumerable required as an input to any batch resolver - var sourceArgumentType = childInvocationContext.Field.Arguments.SourceDataArgument?.ObjectType ?? typeof(object); + var coreSourceParamType = childInvocationContext.Field.Resolver.MetaData.Parameters.SourceParameter?.UnwrappedExpectedParameterType; + + var sourceArgumentType = coreSourceParamType ?? typeof(object); var sourceListType = typeof(List<>).MakeGenericType(sourceArgumentType); var sourceDataList = InstanceFactory.CreateInstance(sourceListType, sourceItemsToInclude.Count) as IList; diff --git a/src/graphql-aspnet/Middleware/FieldExecution/Components/ValidateFieldExecutionMiddleware.cs b/src/graphql-aspnet/Middleware/FieldExecution/Components/ValidateFieldExecutionMiddleware.cs index b72efd68d..4b08e46bb 100644 --- a/src/graphql-aspnet/Middleware/FieldExecution/Components/ValidateFieldExecutionMiddleware.cs +++ b/src/graphql-aspnet/Middleware/FieldExecution/Components/ValidateFieldExecutionMiddleware.cs @@ -20,7 +20,7 @@ namespace GraphQL.AspNet.Middleware.FieldExecution.Components using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Middleware; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; + using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.TypeSystem; /// diff --git a/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeControllerActionDefinitionBase.cs b/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeControllerActionDefinitionBase.cs new file mode 100644 index 000000000..4767bd75a --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeControllerActionDefinitionBase.cs @@ -0,0 +1,168 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions +{ + using System; + using System.Collections.Generic; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas.Structural; + + /// + /// An abstract class containing all the common elements across minimal field builders and + /// their supporting classes. + /// + public abstract class RuntimeControllerActionDefinitionBase : IGraphQLRuntimeSchemaItemDefinition + { + private readonly IGraphQLRuntimeFieldGroupDefinition _parentField; + + /// + /// Prevents a default instance of the class from being created. + /// + private RuntimeControllerActionDefinitionBase() + { + this.AppendedAttributes = new List(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The schema options where this field item + /// is being defined. + /// The full route to use for this schema item. + protected RuntimeControllerActionDefinitionBase( + SchemaOptions options, + SchemaItemPath route) + : this() + { + this.Options = Validation.ThrowIfNullOrReturn(options, nameof(options)); + this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); + } + + /// + /// Initializes a new instance of the class. + /// + /// The schema options where this field item + /// is being defined. + /// The schema collection this item will belong to. + /// The path template identifying this item. + protected RuntimeControllerActionDefinitionBase( + SchemaOptions options, + SchemaItemCollections collection, + string pathTemplate) + : this() + { + this.Options = Validation.ThrowIfNullOrReturn(options, nameof(options)); + pathTemplate = pathTemplate?.Trim() ?? string.Empty; + + this.Route = new SchemaItemPath(collection, pathTemplate); + } + + /// + /// Initializes a new instance of the class. + /// + /// The field from which this entity is being added. + /// The partial path template defined for this + /// individual entity. + protected RuntimeControllerActionDefinitionBase( + IGraphQLRuntimeFieldGroupDefinition parentField, + string partialPathTemplate) + : this() + { + _parentField = Validation.ThrowIfNullOrReturn(parentField, nameof(parentField)); + this.Options = Validation.ThrowIfNullOrReturn(parentField?.Options, nameof(parentField.Options)); + + partialPathTemplate = Validation.ThrowIfNullWhiteSpaceOrReturn(partialPathTemplate, nameof(partialPathTemplate)); + + this.Route = _parentField.Route.CreateChild(partialPathTemplate); + } + + /// + public virtual void AddAttribute(Attribute attrib) + { + Validation.ThrowIfNull(attrib, nameof(attrib)); + this.AppendedAttributes.Add(attrib); + } + + /// + /// Creates the primary attribute that would identify this instance if it was defined on + /// a controller. + /// + /// The primary attribute. + protected abstract Attribute CreatePrimaryAttribute(); + + /// + public void RemoveAttribute(Attribute attrib) + { + Validation.ThrowIfNull(attrib, nameof(attrib)); + this.AppendedAttributes.Remove(attrib); + } + + /// + public virtual IEnumerable Attributes + { + get + { + var combinedAttribs = new List(1 + this.AppendedAttributes.Count); + var definedTypes = new HashSet(); + + // apply the attributes defined on the parent (and parent's parents) + // FIRST to mimic controller level attribs being encountered before action method params. + if (_parentField != null) + { + foreach (var attrib in _parentField.Attributes) + { + if (!definedTypes.Contains(attrib.GetType()) || attrib.CanBeAppliedMultipleTimes()) + { + combinedAttribs.Add(attrib); + definedTypes.Add(attrib.GetType()); + } + } + } + + // apply the primary attribute first as its defined by this + // exact instance + var topAttrib = this.CreatePrimaryAttribute(); + if (topAttrib != null) + { + combinedAttribs.Add(topAttrib); + definedTypes.Add(topAttrib.GetType()); + } + + // apply all the secondary attributes defined directly on this instance + foreach (var attrib in this.AppendedAttributes) + { + if (!definedTypes.Contains(attrib.GetType()) || attrib.CanBeAppliedMultipleTimes()) + { + combinedAttribs.Add(attrib); + definedTypes.Add(attrib.GetType()); + } + } + + return combinedAttribs; + } + } + + /// + /// Gets a list of attributes that were directly appended to this instance. + /// + /// The appended attributes. + protected List AppendedAttributes { get; } + + /// + public virtual SchemaOptions Options { get; protected set; } + + /// + public SchemaItemPath Route { get; protected set; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeDirectiveActionDefinition.cs b/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeDirectiveActionDefinition.cs new file mode 100644 index 000000000..5d068c709 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeDirectiveActionDefinition.cs @@ -0,0 +1,62 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions +{ + using System; + using System.Diagnostics; + using System.Linq; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// An internal implementation of the + /// used to generate new graphql directives via a minimal api style of coding. + /// + [DebuggerDisplay("{Route.Path}")] + public class RuntimeDirectiveActionDefinition : RuntimeControllerActionDefinitionBase, IGraphQLRuntimeDirectiveDefinition + { + /// + /// Initializes a new instance of the class. + /// + /// The schema options where this directive will be created. + /// Name of the directive to use in the schema. + public RuntimeDirectiveActionDefinition( + SchemaOptions schemaOptions, + string directiveName) + : base(schemaOptions, SchemaItemCollections.Directives, directiveName) + { + } + + /// + protected override Attribute CreatePrimaryAttribute() + { + // if the user never declared any restrictions + // supply location data for ALL locations + if (this.AppendedAttributes.OfType().Any()) + return null; + + return new DirectiveLocationsAttribute(DirectiveLocation.AllExecutionLocations | DirectiveLocation.AllTypeSystemLocations) + { + }; + } + + /// + public Delegate Resolver { get; set; } + + /// + public Type ReturnType { get; set; } + + /// + public string InternalName { get; set; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeFieldGroupTemplate.cs b/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeFieldGroupTemplate.cs new file mode 100644 index 000000000..8e6c7d1a6 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeFieldGroupTemplate.cs @@ -0,0 +1,66 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions +{ + using System; + using System.Diagnostics; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + + /// + /// An internal implementation of the + /// used to generate new graphql fields via a minimal api style of coding. + /// + [DebuggerDisplay("{Route.Path}")] + public sealed class RuntimeFieldGroupTemplate : RuntimeFieldGroupTemplateBase, IGraphQLRuntimeFieldGroupDefinition + { + /// + /// Initializes a new instance of the class. + /// + /// The schema options that will own the fields created from + /// this builder. + /// The schema collection this item will belong to. + /// The path template identifying this item. + public RuntimeFieldGroupTemplate( + SchemaOptions options, + SchemaItemCollections collection, + string pathTemplate) + : base(options, collection, pathTemplate) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The field from which this entity is being added. + /// The partial path template to be appended to + /// the parent's already defined template. + public RuntimeFieldGroupTemplate( + IGraphQLRuntimeFieldGroupDefinition parentField, string fieldSubTemplate) + : base(parentField, fieldSubTemplate) + { + } + + /// + public override IGraphQLRuntimeResolvedFieldDefinition MapField(string pathTemplate) + { + var field = new RuntimeResolvedFieldDefinition(this, pathTemplate); + this.Options.AddRuntimeSchemaItem(field); + return field; + } + + /// + public override IGraphQLRuntimeFieldGroupDefinition MapChildGroup(string pathTemplate) + { + return new RuntimeFieldGroupTemplate(this, pathTemplate); + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeFieldGroupTemplateBase.cs b/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeFieldGroupTemplateBase.cs new file mode 100644 index 000000000..39cf02bc4 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeFieldGroupTemplateBase.cs @@ -0,0 +1,64 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions +{ + using System; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + + /// + /// An internal implementation of the + /// used to generate new graphql fields via a minimal api style of coding. + /// + public abstract class RuntimeFieldGroupTemplateBase : RuntimeControllerActionDefinitionBase, IGraphQLRuntimeFieldGroupDefinition + { + /// + /// Initializes a new instance of the class. + /// + /// The schema options that will own the fields created from + /// this builder. + /// The schema collection this item will belong to. + /// The path template identifying this item. + protected RuntimeFieldGroupTemplateBase( + SchemaOptions options, + SchemaItemCollections collection, + string pathTemplate) + : base(options, collection, pathTemplate) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The field from which this entity is being added. + /// The partial path template defined for this + /// individual entity. + protected RuntimeFieldGroupTemplateBase( + IGraphQLRuntimeFieldGroupDefinition parentField, + string partialPathTemplate) + : base(parentField, partialPathTemplate) + { + } + + /// + protected override Attribute CreatePrimaryAttribute() + { + return null; + } + + /// + public abstract IGraphQLRuntimeResolvedFieldDefinition MapField(string pathTemplate); + + /// + public abstract IGraphQLRuntimeFieldGroupDefinition MapChildGroup(string pathTemplate); + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeResolvedFieldDefinition.cs b/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeResolvedFieldDefinition.cs new file mode 100644 index 000000000..bf7118bbd --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeResolvedFieldDefinition.cs @@ -0,0 +1,117 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions +{ + using System; + using System.Diagnostics; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas.Structural; + + /// + /// An internal implementation of the + /// used to generate new graphql fields via a minimal api style of coding. + /// + [DebuggerDisplay("{Route.Path}")] + public class RuntimeResolvedFieldDefinition : RuntimeControllerActionDefinitionBase, IGraphQLRuntimeResolvedFieldDefinition + { + /// + /// Converts the unresolved field into a resolved field. The newly generated field + /// will NOT be attached to any schema and will not have an assigned resolver. + /// + /// The field template. + /// IGraphQLResolvedFieldTemplate. + internal static IGraphQLRuntimeResolvedFieldDefinition FromFieldTemplate(IGraphQLRuntimeFieldGroupDefinition fieldTemplate) + { + Validation.ThrowIfNull(fieldTemplate, nameof(fieldTemplate)); + var field = new RuntimeResolvedFieldDefinition( + fieldTemplate.Options, + fieldTemplate.Route); + + foreach (var attrib in fieldTemplate.Attributes) + field.AddAttribute(attrib); + + return field; + } + + /// + /// Initializes a new instance of the class. + /// + /// The schema options to which this field is being added. + /// The full route to use for this item. + protected RuntimeResolvedFieldDefinition( + SchemaOptions schemaOptions, + SchemaItemPath route) + : base(schemaOptions, route) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The schema options to which this field is being added. + /// The schema collection this item will belong to. + /// The path template identifying this item. + public RuntimeResolvedFieldDefinition( + SchemaOptions schemaOptions, + SchemaItemCollections collection, + string pathTemplate) + : base(schemaOptions, collection, pathTemplate) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The parent field builder to which this new, resolved field + /// will be appended. + /// The template part to append to the parent field's template. + public RuntimeResolvedFieldDefinition( + IGraphQLRuntimeFieldGroupDefinition parentFieldBuilder, + string fieldSubTemplate) + : base(parentFieldBuilder, fieldSubTemplate) + { + } + + /// + protected override Attribute CreatePrimaryAttribute() + { + var (collection, path) = this.Route; + switch (collection) + { + case SchemaItemCollections.Query: + return new QueryRootAttribute(path, this.ReturnType) + { + InternalName = this.InternalName, + }; + + case SchemaItemCollections.Mutation: + return new MutationRootAttribute(path, this.ReturnType) + { + InternalName = this.InternalName, + }; + } + + return null; + } + + /// + public Delegate Resolver { get; set; } + + /// + public Type ReturnType { get; set; } + + /// + public string InternalName { get; set; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeTypeExtensionDefinition.cs b/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeTypeExtensionDefinition.cs new file mode 100644 index 000000000..f12a05b2e --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeTypeExtensionDefinition.cs @@ -0,0 +1,86 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions +{ + using System; + using System.Diagnostics; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// An internal implementation of the + /// used to generate new type extensions via a minimal api style of coding. + /// + [DebuggerDisplay("{Route.Path}")] + public class RuntimeTypeExtensionDefinition : RuntimeResolvedFieldDefinition, IGraphQLRuntimeTypeExtensionDefinition + { + /// + /// Initializes a new instance of the class. + /// + /// The schema options where this type extension is being declared. + /// The target OBJECT or INTERFACE type to extend. + /// Name of the field to add to the . + /// The resolution mode for the resolver implemented by this + /// type extension. + public RuntimeTypeExtensionDefinition( + SchemaOptions schemaOptions, + Type typeToExtend, + string fieldName, + FieldResolutionMode resolutionMode) + : base( + schemaOptions, + SchemaItemCollections.Types, + SchemaItemPath.Join( + GraphTypeNames.ParseName(typeToExtend, TypeKind.OBJECT), + fieldName)) + { + _fieldName = fieldName; + this.ExecutionMode = resolutionMode; + this.TargetType = typeToExtend; + } + + /// + protected override Attribute CreatePrimaryAttribute() + { + switch (this.ExecutionMode) + { + case FieldResolutionMode.PerSourceItem: + return new TypeExtensionAttribute(this.TargetType, _fieldName, this.ReturnType) + { + InternalName = this.InternalName, + }; + + case FieldResolutionMode.Batch: + return new BatchTypeExtensionAttribute(this.TargetType, _fieldName, this.ReturnType) + { + InternalName = this.InternalName, + }; + + default: + throw new NotSupportedException( + $"Unknown {nameof(FieldResolutionMode)}. cannot render " + + $"primary type extension attribute."); + } + } + + private readonly string _fieldName; + + /// + public FieldResolutionMode ExecutionMode { get; set; } + + /// + public Type TargetType { get; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/SchemaItemValidators/BaseSchemaItemValidator.cs b/src/graphql-aspnet/Schemas/Generation/SchemaItemValidators/BaseSchemaItemValidator.cs new file mode 100644 index 000000000..8f4877656 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/SchemaItemValidators/BaseSchemaItemValidator.cs @@ -0,0 +1,22 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.SchemaItemValidators +{ + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// A base class with common functionality used by many internal schema item validators. + /// + internal abstract class BaseSchemaItemValidator : ISchemaItemValidator + { + /// + public abstract void ValidateOrThrow(ISchemaItem schemaItem, ISchema schema); + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/SchemaItemValidators/GraphArgumentValidator.cs b/src/graphql-aspnet/Schemas/Generation/SchemaItemValidators/GraphArgumentValidator.cs new file mode 100644 index 000000000..0ab19bd29 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/SchemaItemValidators/GraphArgumentValidator.cs @@ -0,0 +1,68 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.SchemaItemValidators +{ + using System; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// A validator of a completed and schema-attached graph argument that ensures it can function as + /// expected in the target schema. + /// + internal class GraphArgumentValidator : BaseSchemaItemValidator + { + /// + /// Gets the singular instnace of this validator. + /// + /// The instance. + public static ISchemaItemValidator Instance { get; } = new GraphArgumentValidator(); + + /// + /// Prevents a default instance of the class from being created. + /// + private GraphArgumentValidator() + { + } + + /// + public override void ValidateOrThrow(ISchemaItem schemaItem, ISchema schema) + { + Validation.ThrowIfNull(schemaItem, nameof(schemaItem)); + Validation.ThrowIfNull(schema, nameof(schema)); + + var argument = schemaItem as IGraphArgument; + if (argument == null) + { + throw new InvalidCastException( + $"Unable to validate argument. Expected type " + + $"'{typeof(IGraphArgument).FriendlyName()}' but got '{schema?.GetType().FriendlyName() ?? "-none-"}'"); + } + + if (argument.ObjectType == null || argument.ObjectType.IsInterface) + { + throw new GraphTypeDeclarationException( + $"The argument '{argument.Name}' on '{argument.Parent?.Route.Path ?? "~unknown~"}' is of type '{argument.ObjectType?.FriendlyName() ?? "~null~"}', " + + $"which is an interface. Interfaces cannot be used as input arguments to any graph type."); + } + + // the type MUST be in the schema + var foundItem = schema.KnownTypes.FindGraphType(argument.ObjectType, TypeSystem.TypeKind.INPUT_OBJECT); + if (foundItem == null) + { + throw new GraphTypeDeclarationException( + $"The argument '{argument.Name}' on '{argument.Parent?.Route.Path ?? "~unknown~"}' is declared as a {argument.ObjectType.FriendlyName()}, " + + $"which is not included in the schema as an acceptable input type (e.g. Scalar, Enum or Input Object). Ensure your type is included in the schema."); + } + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/SchemaItemValidators/GraphFieldValidator.cs b/src/graphql-aspnet/Schemas/Generation/SchemaItemValidators/GraphFieldValidator.cs new file mode 100644 index 000000000..290f0029b --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/SchemaItemValidators/GraphFieldValidator.cs @@ -0,0 +1,39 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.SchemaItemValidators +{ + using System; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// A runtime validator that will inspect and ensure the integrity of a + /// before a schema is placed online. + /// + internal class GraphFieldValidator : BaseSchemaItemValidator + { + /// + /// Gets the singular instnace of this validator. + /// + /// The instance. + public static ISchemaItemValidator Instance { get; } = new GraphFieldValidator(); + + /// + /// Prevents a default instance of the class from being created. + /// + private GraphFieldValidator() + { + } + + /// + public override void ValidateOrThrow(ISchemaItem schemaItem, ISchema schema) + { + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/SchemaItemValidators/NoValidationSchemaItemValidator.cs b/src/graphql-aspnet/Schemas/Generation/SchemaItemValidators/NoValidationSchemaItemValidator.cs new file mode 100644 index 000000000..28371e1ca --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/SchemaItemValidators/NoValidationSchemaItemValidator.cs @@ -0,0 +1,38 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.SchemaItemValidators +{ + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// A runtime schema item validator that performs no validation. + /// + internal class NoValidationSchemaItemValidator : BaseSchemaItemValidator + { + /// + /// Gets the singular instnace of this validator. + /// + /// The instance. + public static ISchemaItemValidator Instance { get; } = new NoValidationSchemaItemValidator(); + + /// + /// Prevents a default instance of the class from being created. + /// + private NoValidationSchemaItemValidator() + { + } + + /// + public override void ValidateOrThrow(ISchemaItem schemaItem, ISchema schema) + { + // do nothing + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/SchemaItemValidators/SchemaItemValidationFactory.cs b/src/graphql-aspnet/Schemas/Generation/SchemaItemValidators/SchemaItemValidationFactory.cs new file mode 100644 index 000000000..22673a134 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/SchemaItemValidators/SchemaItemValidationFactory.cs @@ -0,0 +1,36 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.SchemaItemValidators; + +using GraphQL.AspNet.Interfaces.Schema; + +/// +/// A factory for producing validator instances that can validate a given +/// consistancy against a target schema. +/// +internal static class SchemaItemValidationFactory +{ + /// + /// Creates a validator instance for the given schema item. + /// + /// The schema item. + /// ISchemaItemValidator. + public static ISchemaItemValidator CreateValidator(ISchemaItem schemaItem) + { + switch (schemaItem) + { + case IGraphArgument _: + return GraphArgumentValidator.Instance; + + default: + return NoValidationSchemaItemValidator.Instance; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/TypeMakers/DependentTypeCollection.cs b/src/graphql-aspnet/Schemas/Generation/TypeMakers/DependentTypeCollection.cs similarity index 97% rename from src/graphql-aspnet/Engine/TypeMakers/DependentTypeCollection.cs rename to src/graphql-aspnet/Schemas/Generation/TypeMakers/DependentTypeCollection.cs index ad125aa77..646ea3c34 100644 --- a/src/graphql-aspnet/Engine/TypeMakers/DependentTypeCollection.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/DependentTypeCollection.cs @@ -7,13 +7,13 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Engine.TypeMakers +namespace GraphQL.AspNet.Schemas.Generation.TypeMakers { using System; using System.Collections.Generic; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.TypeSystem; /// diff --git a/src/graphql-aspnet/Engine/TypeMakers/DirectiveMaker.cs b/src/graphql-aspnet/Schemas/Generation/TypeMakers/DirectiveMaker.cs similarity index 58% rename from src/graphql-aspnet/Engine/TypeMakers/DirectiveMaker.cs rename to src/graphql-aspnet/Schemas/Generation/TypeMakers/DirectiveMaker.cs index a9e93d7a0..502e0192d 100644 --- a/src/graphql-aspnet/Engine/TypeMakers/DirectiveMaker.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/DirectiveMaker.cs @@ -7,16 +7,17 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Engine.TypeMakers +namespace GraphQL.AspNet.Schemas.Generation.TypeMakers { - using System; using System.Collections.Generic; using GraphQL.AspNet.Common; + using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Engine; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Security; + using Microsoft.AspNetCore.Mvc; /// /// A "maker" capable of producing a qualified from its related . @@ -24,33 +25,30 @@ namespace GraphQL.AspNet.Engine.TypeMakers public class DirectiveMaker : IGraphTypeMaker { private readonly ISchema _schema; + private readonly IGraphArgumentMaker _argMaker; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The schema. - public DirectiveMaker(ISchema schema) + /// The schema this maker will create directives for. + /// The maker used to generate new arguments on any created directives. + public DirectiveMaker(ISchema schema, IGraphArgumentMaker argumentMaker) { _schema = Validation.ThrowIfNullOrReturn(schema, nameof(schema)); + _argMaker = Validation.ThrowIfNullOrReturn(argumentMaker, nameof(argumentMaker)); } - /// - /// Inspects the given type and, in accordance with the rules of this maker, will - /// generate a complete set of necessary graph types required to support it. - /// - /// The concrete type to incorporate into the schema. - /// GraphTypeCreationResult. - public GraphTypeCreationResult CreateGraphType(Type concreteType) + /// + public virtual GraphTypeCreationResult CreateGraphType(IGraphTypeTemplate typeTemplate) { - Validation.ThrowIfNull(concreteType, nameof(concreteType)); - - var formatter = _schema.Configuration.DeclarationOptions.GraphNamingFormatter; - var template = GraphQLProviders.TemplateProvider.ParseType(concreteType) as IGraphDirectiveTemplate; - if (template == null) + if (!(typeTemplate is IGraphDirectiveTemplate template)) return null; + template.Parse(); template.ValidateOrThrow(false); + var formatter = _schema.Configuration.DeclarationOptions.GraphNamingFormatter; + var securityGroups = new List(); if (template.SecurityPolicies?.Count > 0) @@ -60,6 +58,7 @@ public GraphTypeCreationResult CreateGraphType(Type concreteType) var directive = new Directive( formatter.FormatFieldName(template.Name), + template.InternalName, template.Locations, template.ObjectType, template.Route, @@ -73,17 +72,27 @@ public GraphTypeCreationResult CreateGraphType(Type concreteType) // all arguments are required to have the same signature via validation // can use any method to fill the arg field list - var argMaker = new GraphArgumentMaker(_schema); foreach (var argTemplate in template.Arguments) { - var argumentResult = argMaker.CreateArgument(directive, argTemplate); - directive.Arguments.AddArgument(argumentResult.Argument); + if (GraphArgumentMaker.IsArgumentPartOfSchema(argTemplate, _schema)) + { + var argumentResult = _argMaker.CreateArgument(directive, argTemplate); + directive.Arguments.AddArgument(argumentResult.Argument); - result.MergeDependents(argumentResult); + result.MergeDependents(argumentResult); + } } result.GraphType = directive; - result.ConcreteType = concreteType; + + // only assign a concrete type if one was declared + if (template.ObjectType != typeof(RuntimeExecutionDirective)) + result.ConcreteType = template.ObjectType; + + // account for any potential type dependencies (input objects etc) + var requiredTypes = template.RetrieveRequiredTypes(); + result.AddDependentRange(requiredTypes); + return result; } } diff --git a/src/graphql-aspnet/Engine/TypeMakers/EnumGraphTypeMaker.cs b/src/graphql-aspnet/Schemas/Generation/TypeMakers/EnumGraphTypeMaker.cs similarity index 69% rename from src/graphql-aspnet/Engine/TypeMakers/EnumGraphTypeMaker.cs rename to src/graphql-aspnet/Schemas/Generation/TypeMakers/EnumGraphTypeMaker.cs index 5fc1d9c83..cf9bf63ae 100644 --- a/src/graphql-aspnet/Engine/TypeMakers/EnumGraphTypeMaker.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/EnumGraphTypeMaker.cs @@ -7,12 +7,14 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Engine.TypeMakers +namespace GraphQL.AspNet.Schemas.Generation.TypeMakers { using System; using System.Linq; using GraphQL.AspNet.Common; using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution.RulesEngine.RuleSets.DirectiveExecution.DirectiveValidation; + using GraphQL.AspNet.Interfaces.Configuration; using GraphQL.AspNet.Interfaces.Engine; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Schema; @@ -24,36 +26,35 @@ namespace GraphQL.AspNet.Engine.TypeMakers /// public class EnumGraphTypeMaker : IGraphTypeMaker { - private readonly ISchema _schema; + private readonly ISchemaConfiguration _config; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The schema this generator should follow. - public EnumGraphTypeMaker(ISchema schema) + /// The schema configuration to use when building the graph type. + public EnumGraphTypeMaker(ISchemaConfiguration config) { - _schema = Validation.ThrowIfNullOrReturn(schema, nameof(schema)); + _config = Validation.ThrowIfNullOrReturn(config, nameof(config)); } /// - public GraphTypeCreationResult CreateGraphType(Type concreteType) + public virtual GraphTypeCreationResult CreateGraphType(IGraphTypeTemplate typeTemplate) { - Validation.ThrowIfNull(concreteType, nameof(concreteType)); - - var template = GraphQLProviders.TemplateProvider.ParseType(concreteType, TypeKind.ENUM) as IEnumGraphTypeTemplate; - if (template == null) + if (!(typeTemplate is IEnumGraphTypeTemplate template)) return null; + template.Parse(); template.ValidateOrThrow(false); - var requirements = template.DeclarationRequirements ?? _schema.Configuration.DeclarationOptions.FieldDeclarationRequirements; + var requirements = template.DeclarationRequirements ?? _config.DeclarationOptions.FieldDeclarationRequirements; // enum level directives var enumDirectives = template.CreateAppliedDirectives(); var graphType = new EnumGraphType( - _schema.Configuration.DeclarationOptions.GraphNamingFormatter.FormatGraphTypeName(template.Name), - concreteType, + _config.DeclarationOptions.GraphNamingFormatter.FormatGraphTypeName(template.Name), + template.InternalName, + template.ObjectType, template.Route, enumDirectives) { @@ -64,7 +65,7 @@ public GraphTypeCreationResult CreateGraphType(Type concreteType) var result = new GraphTypeCreationResult() { GraphType = graphType, - ConcreteType = concreteType, + ConcreteType = template.ObjectType, }; // account for any potential type system directives @@ -74,6 +75,7 @@ public GraphTypeCreationResult CreateGraphType(Type concreteType) var enumValuesToInclude = template.Values.Where(value => requirements.AllowImplicitEnumValues() || value.IsExplicitDeclaration); foreach (var value in enumValuesToInclude) { + value.Parse(); value.ValidateOrThrow(false); // enum option directives @@ -81,11 +83,12 @@ public GraphTypeCreationResult CreateGraphType(Type concreteType) var valueOption = new EnumValue( graphType, - _schema.Configuration.DeclarationOptions.GraphNamingFormatter.FormatEnumValueName(value.Name), + _config.DeclarationOptions.GraphNamingFormatter.FormatEnumValueName(value.Name), + value.InternalName, value.Description, value.Route, value.Value, - value.InternalName, + value.DeclaredLabel, valueDirectives); graphType.AddOption(valueOption); diff --git a/src/graphql-aspnet/Schemas/Generation/TypeMakers/FieldContainerGraphTypeMakerBase.cs b/src/graphql-aspnet/Schemas/Generation/TypeMakers/FieldContainerGraphTypeMakerBase.cs new file mode 100644 index 000000000..a0067ec6e --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/FieldContainerGraphTypeMakerBase.cs @@ -0,0 +1,65 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.TypeMakers +{ + using System.Collections.Generic; + using System.Linq; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + + /// + /// A base type maker for those types that generate fields. Used to centralize common code. + /// + public abstract class FieldContainerGraphTypeMakerBase + { + private readonly ISchemaConfiguration _config; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration data to use when generating + /// field items. + public FieldContainerGraphTypeMakerBase(ISchemaConfiguration config) + { + _config = Validation.ThrowIfNullOrReturn(config, nameof(config)); + } + + /// + /// Creates the collection of graph fields that belong to the template. + /// + /// The template to generate fields for. + /// IEnumerable<IGraphField>. + protected virtual IEnumerable GatherFieldTemplates(IGraphTypeFieldTemplateContainer template) + { + // gather the fields to include in the graph type + var requiredDeclarations = template.DeclarationRequirements ?? _config.DeclarationOptions.FieldDeclarationRequirements; + + return template.FieldTemplates.Where(x => + { + if (x.IsExplicitDeclaration) + return true; + + switch (x.FieldSource) + { + case GraphFieldSource.Method: + return requiredDeclarations.AllowImplicitMethods(); + + case GraphFieldSource.Property: + return requiredDeclarations.AllowImplicitProperties(); + } + + return false; + }); + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/TypeMakers/GraphArgumentCreationResult.cs b/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphArgumentCreationResult.cs similarity index 94% rename from src/graphql-aspnet/Engine/TypeMakers/GraphArgumentCreationResult.cs rename to src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphArgumentCreationResult.cs index 8c7abe466..3c2302781 100644 --- a/src/graphql-aspnet/Engine/TypeMakers/GraphArgumentCreationResult.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphArgumentCreationResult.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Engine.TypeMakers +namespace GraphQL.AspNet.Schemas.Generation.TypeMakers { using GraphQL.AspNet.Interfaces.Schema; diff --git a/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphArgumentMaker.cs b/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphArgumentMaker.cs new file mode 100644 index 000000000..4e70a2fd9 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphArgumentMaker.cs @@ -0,0 +1,142 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.TypeMakers +{ + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Configuration.Formatting; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Engine; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + + /// + /// A maker capable of turning a into a usable on a graph field. + /// + public class GraphArgumentMaker : IGraphArgumentMaker + { + private readonly ISchema _schema; + private readonly ISchemaConfiguration _config; + + /// + /// Initializes a new instance of the class. + /// + /// The schema being built. + public GraphArgumentMaker(ISchema schema) + { + _schema = Validation.ThrowIfNullOrReturn(schema, nameof(schema)); + _config = _schema.Configuration; + } + + /// + public GraphArgumentCreationResult CreateArgument(ISchemaItem owner, IGraphArgumentTemplate template) + { + Validation.ThrowIfNull(owner, nameof(owner)); + Validation.ThrowIfNull(template, nameof(template)); + + template.Parse(); + template.ValidateOrThrow(false); + + var formatter = _config.DeclarationOptions.GraphNamingFormatter; + + var directives = template.CreateAppliedDirectives(); + + // all arguments are either leafs or input objects + var existingGraphType = _schema.KnownTypes.FindGraphType(template.ObjectType, TypeKind.INPUT_OBJECT); + + string schemaTypeName; + if (existingGraphType != null && existingGraphType.Kind.IsValidInputKind()) + { + // when the type already exists on the target schema + // and is usable as an input type then just use the name + schemaTypeName = existingGraphType.Name; + } + else + { + // guess on what the name of the schema item will be + // this is guaranteed correct for all but scalars and scalars should be + // added first + schemaTypeName = GraphTypeNames.ParseName(template.ObjectType, TypeKind.INPUT_OBJECT); + } + + // enforce non-renaming standards in the maker since the + // directly controls the formatter + if (GlobalTypes.CanBeRenamed(schemaTypeName)) + schemaTypeName = formatter.FormatGraphTypeName(schemaTypeName); + + var typeExpression = template.TypeExpression.CloneTo(schemaTypeName); + + var argument = new GraphFieldArgument( + owner, + formatter.FormatFieldName(template.Name), + template.InternalName, + template.ParameterName, + typeExpression, + template.Route, + template.ObjectType, + template.HasDefaultValue, + template.DefaultValue, + template.Description, + directives); + + var result = new GraphArgumentCreationResult(); + result.Argument = argument; + + result.AddDependentRange(template.RetrieveRequiredTypes()); + + return result; + } + + /// + /// Determines whether the provided argument template should be included as part of the schema. + /// + /// The argument template to evaluate. + /// The schema to evaluate against. + /// true if the template should be rendered into the schema; otherwise, false. + public static bool IsArgumentPartOfSchema(IGraphArgumentTemplate argTemplate, ISchema schema) + { + Validation.ThrowIfNull(argTemplate, nameof(argTemplate)); + Validation.ThrowIfNull(schema, nameof(schema)); + + if (argTemplate.ArgumentModifier.IsExplicitlyPartOfTheSchema()) + return true; + + if (!argTemplate.ArgumentModifier.CouldBePartOfTheSchema()) + return false; + + // teh argument contains no explicit inclusion or exclusion modifiers + // what do we do with it? + switch (schema.Configuration.DeclarationOptions.ArgumentBindingRule) + { + case Configuration.SchemaArgumentBindingRules.ParametersRequireFromGraphQLDeclaration: + + // arg didn't explicitly have [FromGraphQL] so it should NOT be part of the schema + return false; + + case Configuration.SchemaArgumentBindingRules.ParametersRequireFromServicesDeclaration: + + // arg didn't explicitly have [FromServices] so it should be part of the schema + return true; + + case Configuration.SchemaArgumentBindingRules.ParametersPreferQueryResolution: + default: + + // only exclude types that could never be correct as an input argument + // --- + // interfaces can never be valid input object types + if (argTemplate.ObjectType.IsInterface) + return false; + + return true; + } + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/TypeMakers/GraphFieldCreationResult.cs b/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphFieldCreationResult.cs similarity index 95% rename from src/graphql-aspnet/Engine/TypeMakers/GraphFieldCreationResult.cs rename to src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphFieldCreationResult.cs index 606b2118d..84cd58bec 100644 --- a/src/graphql-aspnet/Engine/TypeMakers/GraphFieldCreationResult.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphFieldCreationResult.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Engine.TypeMakers +namespace GraphQL.AspNet.Schemas.Generation.TypeMakers { using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Schema; diff --git a/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphFieldMaker.cs b/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphFieldMaker.cs new file mode 100644 index 000000000..9096d10b4 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphFieldMaker.cs @@ -0,0 +1,263 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.TypeMakers +{ + using System; + using System.Collections.Generic; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Common.Generics; + using GraphQL.AspNet.Configuration.Formatting; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Engine; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Security; + + /// + /// A maker capable of turning a into a usable field in an object graph. + /// + public class GraphFieldMaker : IGraphFieldMaker + { + private readonly ISchema _schema; + private readonly ISchemaConfiguration _config; + private readonly IGraphArgumentMaker _argMaker; + + /// + /// Initializes a new instance of the class. + /// + /// The schema instance to reference when creating fields. + /// A maker that can make arguments declared on this field. + public GraphFieldMaker(ISchema schema, IGraphArgumentMaker argMaker) + { + _schema = Validation.ThrowIfNullOrReturn(schema, nameof(schema)); + _argMaker = Validation.ThrowIfNullOrReturn(argMaker, nameof(argMaker)); + _config = _schema.Configuration; + } + + /// + public virtual GraphFieldCreationResult CreateField(IGraphFieldTemplate template) + { + Validation.ThrowIfNull(template, nameof(template)); + + template.Parse(); + template.ValidateOrThrow(false); + + var formatter = _config.DeclarationOptions.GraphNamingFormatter; + var result = new GraphFieldCreationResult(); + + // if the owner of this field declared top level objects append them to the + // field for evaluation + var securityGroups = new List(); + + if (template.Parent?.SecurityPolicies?.Count > 0) + securityGroups.Add(template.Parent.SecurityPolicies); + + if (template.SecurityPolicies?.Count > 0) + securityGroups.Add(template.SecurityPolicies); + + MethodGraphField field = this.CreateFieldInstance(formatter, template, securityGroups); + + field.Description = template.Description; + field.Complexity = template.Complexity; + field.FieldSource = template.FieldSource; + + if (template.Arguments != null && template.Arguments.Count > 0) + { + foreach (var argTemplate in template.Arguments) + { + if (GraphArgumentMaker.IsArgumentPartOfSchema(argTemplate, _schema)) + { + var argumentResult = _argMaker.CreateArgument(field, argTemplate); + field.Arguments.AddArgument(argumentResult.Argument); + + result.MergeDependents(argumentResult); + } + } + } + + result.AddDependentRange(template.RetrieveRequiredTypes()); + if (template.UnionProxy != null) + { + var dependentUnion = new DependentType(template.UnionProxy, TypeKind.UNION); + result.AddDependent(dependentUnion); + } + + result.Field = field; + return result; + } + + /// + public GraphFieldCreationResult CreateField(IInputGraphFieldTemplate template) + { + Validation.ThrowIfNull(template, nameof(template)); + + var formatter = _config.DeclarationOptions.GraphNamingFormatter; + + var defaultInputObject = InstanceFactory.CreateInstance(template.Parent.ObjectType); + var propGetters = InstanceFactory.CreatePropertyGetterInvokerCollection(template.Parent.ObjectType); + + object defaultValue = null; + + if (!template.IsRequired && propGetters.ContainsKey(template.DeclaredName)) + { + defaultValue = propGetters[template.DeclaredName](ref defaultInputObject); + } + + var result = new GraphFieldCreationResult(); + + var directives = template.CreateAppliedDirectives(); + var schemaTypeName = this.PrepareTypeName(template); + + var field = new InputGraphField( + formatter.FormatFieldName(template.Name), + template.InternalName, + template.TypeExpression.CloneTo(schemaTypeName), + template.Route, + template.ObjectType, + template.DeclaredName, + template.DeclaredReturnType, + template.IsRequired, + defaultValue, + directives); + + field.Description = template.Description; + + result.AddDependentRange(template.RetrieveRequiredTypes()); + + result.Field = field; + return result; + } + + /// + /// Finds and applies proper casing to the graph type name returned by this field. + /// + /// The template to inspect. + /// System.String. + protected virtual string PrepareTypeName(IGraphFieldTemplate template) + { + // all fields return either an object, interface, union, scalar or enum + IGraphType existingGraphType; + string fallbackTypeName; + if (template.UnionProxy != null) + { + existingGraphType = _schema.KnownTypes.FindGraphType(template.UnionProxy.Name); + fallbackTypeName = template.UnionProxy.Name; + } + else + { + existingGraphType = _schema.KnownTypes.FindGraphType(template.ObjectType, template.OwnerTypeKind); + fallbackTypeName = null; + } + + string schemaTypeName; + + // when the type already exists on the target schema + // and is usable as a type name then just use the name + if (existingGraphType != null) + { + schemaTypeName = existingGraphType.Name; + } + else if (fallbackTypeName != null) + { + schemaTypeName = fallbackTypeName; + } + else + { + // guess on what the name of the schema item will be + // this is guaranteed correct for all but scalars + schemaTypeName = GraphTypeNames.ParseName(template.ObjectType, template.OwnerTypeKind); + } + + // enforce non-renaming standards in the maker since the + // directly controls the formatter + if (GlobalTypes.CanBeRenamed(schemaTypeName)) + schemaTypeName = _schema.Configuration.DeclarationOptions.GraphNamingFormatter.FormatGraphTypeName(schemaTypeName); + + return schemaTypeName; + } + + /// + /// Finds and applies proper casing to the graph type name returned by this input field. + /// + /// The template to inspect. + /// System.String. + protected virtual string PrepareTypeName(IInputGraphFieldTemplate template) + { + // all input fields return either an object, scalar or enum (never a union) + string schemaTypeName; + var existingGraphType = _schema.KnownTypes.FindGraphType(template.ObjectType, template.OwnerTypeKind); + + // when the type already exists on the target schema + // and is usable as a type for an input field then just use the name + // an OBJECT type may be registered for the target `template.ObjectType` which might get found + // but the coorisponding INPUT_OBJECT may not yet be discovered + if (existingGraphType != null && existingGraphType.Kind.IsValidInputKind()) + { + schemaTypeName = existingGraphType.Name; + } + else + { + // guess on what the unformatted name of the schema item will be + // this is guaranteed correct for all but scalars and scalars should be registered by the time + // input objects are registered + schemaTypeName = GraphTypeNames.ParseName(template.ObjectType, template.OwnerTypeKind); + } + + // enforce non-renaming standards in the maker since the + // directly controls the formatter + if (GlobalTypes.CanBeRenamed(schemaTypeName)) + schemaTypeName = _schema.Configuration.DeclarationOptions.GraphNamingFormatter.FormatGraphTypeName(schemaTypeName); + + return schemaTypeName; + } + + /// + /// Instantiates the graph field according to the data provided. + /// + /// The formatter. + /// The template. + /// The security groups. + /// MethodGraphField. + protected virtual MethodGraphField CreateFieldInstance( + GraphNameFormatter formatter, + IGraphFieldTemplate template, + List securityGroups) + { + var directives = template.CreateAppliedDirectives(); + + var schemaTypeName = this.PrepareTypeName(template); + var typeExpression = template.TypeExpression.CloneTo(schemaTypeName); + + switch (template.FieldSource) + { + case GraphFieldSource.Method: + case GraphFieldSource.Property: + case GraphFieldSource.Action: + return new MethodGraphField( + formatter.FormatFieldName(template.Name), + template.InternalName, + typeExpression, + template.Route, + template.DeclaredReturnType, + template.ObjectType, + template.Mode, + template.CreateResolver(), + securityGroups, + directives); + + default: + throw new ArgumentOutOfRangeException($"Template field source of {template.FieldSource.ToString()} is not supported by {this.GetType().FriendlyName()}."); + } + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/TypeMakers/GraphMakerExtensions.cs b/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphMakerExtensions.cs similarity index 97% rename from src/graphql-aspnet/Engine/TypeMakers/GraphMakerExtensions.cs rename to src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphMakerExtensions.cs index 7c4defc6a..2134c8688 100644 --- a/src/graphql-aspnet/Engine/TypeMakers/GraphMakerExtensions.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphMakerExtensions.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Engine.TypeMakers +namespace GraphQL.AspNet.Schemas.Generation.TypeMakers { using System.Collections.Generic; using GraphQL.AspNet.Common; diff --git a/src/graphql-aspnet/Engine/TypeMakers/GraphTypeCreationResult.cs b/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphTypeCreationResult.cs similarity index 95% rename from src/graphql-aspnet/Engine/TypeMakers/GraphTypeCreationResult.cs rename to src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphTypeCreationResult.cs index fdf4219ce..a203e4d45 100644 --- a/src/graphql-aspnet/Engine/TypeMakers/GraphTypeCreationResult.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphTypeCreationResult.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Engine.TypeMakers +namespace GraphQL.AspNet.Schemas.Generation.TypeMakers { using System; using System.Diagnostics; diff --git a/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphTypeMakerFactory.cs b/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphTypeMakerFactory.cs new file mode 100644 index 000000000..63934debf --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphTypeMakerFactory.cs @@ -0,0 +1,149 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.TypeMakers +{ + using System; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Directives; + using GraphQL.AspNet.Interfaces.Engine; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// An object used during schema generation to organize and expose the various + /// template objects to the schema. + /// + public class GraphTypeMakerFactory + { + private ISchema _schemaInstance; + + /// + /// Initializes a new instance of the class. + /// + /// The schema instance to reference when making + /// types. + public GraphTypeMakerFactory(ISchema schemaInstance) + { + Validation.ThrowIfNull(schemaInstance, nameof(schemaInstance)); + + _schemaInstance = schemaInstance; + } + + /// + /// Creates a new template from the given type. This template is consumed during type generation. + /// + /// Type of the object to templatize. + /// The typekind of template to be created. If the kind can be + /// determined from the alone, this value is ignored. Largely used to seperate + /// an OBJECT template from an INPUT_OBJECT template for the same .NET type. + /// IGraphTypeTemplate. + public virtual IGraphTypeTemplate MakeTemplate(Type objectType, TypeKind? kind = null) + { + return GraphTypeTemplates.CreateTemplate(objectType, kind); + } + + /// + /// Parses the provided type, extracting the metadata used in type generation for the object graph. + /// + /// The type of the object to inspect and determine a valid type maker. + /// The graph to create a template for. If not supplied the template provider + /// will attempt to assign the best graph type possible for the supplied . + /// IGraphTypeTemplate. + public virtual IGraphTypeMaker CreateTypeMaker(Type objectType = null, TypeKind? kind = null) + { + if (objectType == null) + { + if (!kind.HasValue) + return null; + } + else + { + objectType = GlobalTypes.FindBuiltInScalarType(objectType) ?? objectType; + + if (Validation.IsCastable(objectType)) + kind = TypeKind.SCALAR; + else if (objectType.IsEnum) + kind = TypeKind.ENUM; + else if (objectType.IsInterface) + kind = TypeKind.INTERFACE; + else if (Validation.IsCastable(objectType)) + kind = TypeKind.DIRECTIVE; + else if (Validation.IsCastable(objectType)) + kind = TypeKind.CONTROLLER; + else if (Validation.IsCastable(objectType)) + kind = TypeKind.UNION; + else if (!kind.HasValue || kind.Value != TypeKind.INPUT_OBJECT) + kind = TypeKind.OBJECT; + } + + switch (kind.Value) + { + case TypeKind.SCALAR: + return new ScalarGraphTypeMaker(_schemaInstance.Configuration); + + case TypeKind.INTERFACE: + return new InterfaceGraphTypeMaker(_schemaInstance.Configuration, this.CreateFieldMaker()); + + case TypeKind.UNION: + return new UnionGraphTypeMaker(_schemaInstance.Configuration); + + case TypeKind.ENUM: + return new EnumGraphTypeMaker(_schemaInstance.Configuration); + + case TypeKind.INPUT_OBJECT: + return new InputObjectGraphTypeMaker( + _schemaInstance.Configuration, + this.CreateFieldMaker()); + + case TypeKind.OBJECT: + return new ObjectGraphTypeMaker( + _schemaInstance.Configuration, + this.CreateFieldMaker()); + + case TypeKind.DIRECTIVE: + return new DirectiveMaker(_schemaInstance, this.CreateArgumentMaker()); + + case TypeKind.CONTROLLER: + default: + return null; + } + } + + /// + /// Creates a maker that can generate valid graph fields + /// + /// IGraphFieldMaker. + public virtual IGraphFieldMaker CreateFieldMaker() + { + return new GraphFieldMaker(_schemaInstance, this.CreateArgumentMaker()); + } + + /// + /// Creates a maker that can generate arguments for fields or directives. + /// + /// IGraphArgumentMaker. + public virtual IGraphArgumentMaker CreateArgumentMaker() + { + return new GraphArgumentMaker(_schemaInstance); + } + + /// + /// Creates a maker that can generate special union graph types. + /// + /// IUnionGraphTypeMaker. + public virtual IUnionGraphTypeMaker CreateUnionMaker() + { + return new UnionGraphTypeMaker(_schemaInstance.Configuration); + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/TypeMakers/InputObjectGraphTypeMaker.cs b/src/graphql-aspnet/Schemas/Generation/TypeMakers/InputObjectGraphTypeMaker.cs similarity index 68% rename from src/graphql-aspnet/Engine/TypeMakers/InputObjectGraphTypeMaker.cs rename to src/graphql-aspnet/Schemas/Generation/TypeMakers/InputObjectGraphTypeMaker.cs index 81ef2ab1b..dc46d3aee 100644 --- a/src/graphql-aspnet/Engine/TypeMakers/InputObjectGraphTypeMaker.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/InputObjectGraphTypeMaker.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Engine.TypeMakers +namespace GraphQL.AspNet.Schemas.Generation.TypeMakers { using System; using System.Linq; @@ -15,6 +15,7 @@ namespace GraphQL.AspNet.Engine.TypeMakers using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Configuration; using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Configuration; using GraphQL.AspNet.Interfaces.Engine; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Schema; @@ -25,27 +26,32 @@ namespace GraphQL.AspNet.Engine.TypeMakers /// public class InputObjectGraphTypeMaker : IGraphTypeMaker { - private readonly ISchema _schema; + private readonly ISchemaConfiguration _config; + private readonly IGraphFieldMaker _fieldMaker; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The schema defining the graph type creation rules this generator should follow. - public InputObjectGraphTypeMaker(ISchema schema) + /// The configuration to use when constructing the input graph type. + /// The field maker used to create input field instances. + public InputObjectGraphTypeMaker( + ISchemaConfiguration config, + IGraphFieldMaker fieldMaker) { - _schema = Validation.ThrowIfNullOrReturn(schema, nameof(schema)); + _config = Validation.ThrowIfNullOrReturn(config, nameof(config)); + _fieldMaker = Validation.ThrowIfNullOrReturn(fieldMaker, nameof(fieldMaker)); } /// - public virtual GraphTypeCreationResult CreateGraphType(Type concreteType) + public virtual GraphTypeCreationResult CreateGraphType(IGraphTypeTemplate typeTemplate) { - var template = GraphQLProviders.TemplateProvider.ParseType(concreteType, TypeKind.INPUT_OBJECT) as IInputObjectGraphTypeTemplate; - if (template == null) + if (!(typeTemplate is IInputObjectGraphTypeTemplate template)) return null; + template.Parse(); template.ValidateOrThrow(false); - var formatter = _schema.Configuration.DeclarationOptions.GraphNamingFormatter; + var formatter = _config.DeclarationOptions.GraphNamingFormatter; var result = new GraphTypeCreationResult(); // gather directives @@ -53,7 +59,8 @@ public virtual GraphTypeCreationResult CreateGraphType(Type concreteType) var inputObjectType = new InputObjectGraphType( formatter.FormatGraphTypeName(template.Name), - concreteType, + template.InternalName, + template.ObjectType, template.Route, directives) { @@ -66,15 +73,14 @@ public virtual GraphTypeCreationResult CreateGraphType(Type concreteType) // gather the fields to include in the graph type var requiredDeclarations = template.DeclarationRequirements - ?? _schema.Configuration.DeclarationOptions.FieldDeclarationRequirements; + ?? _config.DeclarationOptions.FieldDeclarationRequirements; var fieldTemplates = template.FieldTemplates.Values.Where(x => x.IsExplicitDeclaration || requiredDeclarations.AllowImplicitProperties()); // create the fields for the graph type - var fieldMaker = GraphQLProviders.GraphTypeMakerProvider.CreateFieldMaker(_schema); foreach (var fieldTemplate in fieldTemplates) { - var fieldResult = fieldMaker.CreateField(fieldTemplate); + var fieldResult = _fieldMaker.CreateField(fieldTemplate); inputObjectType.AddField(fieldResult.Field); result.MergeDependents(fieldResult); @@ -90,7 +96,7 @@ public virtual GraphTypeCreationResult CreateGraphType(Type concreteType) } result.GraphType = inputObjectType; - result.ConcreteType = concreteType; + result.ConcreteType = template.ObjectType; return result; } } diff --git a/src/graphql-aspnet/Engine/TypeMakers/InterfaceGraphTypeMaker.cs b/src/graphql-aspnet/Schemas/Generation/TypeMakers/InterfaceGraphTypeMaker.cs similarity index 64% rename from src/graphql-aspnet/Engine/TypeMakers/InterfaceGraphTypeMaker.cs rename to src/graphql-aspnet/Schemas/Generation/TypeMakers/InterfaceGraphTypeMaker.cs index b2f56b7e5..6275f2d99 100644 --- a/src/graphql-aspnet/Engine/TypeMakers/InterfaceGraphTypeMaker.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/InterfaceGraphTypeMaker.cs @@ -7,53 +7,58 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Engine.TypeMakers +namespace GraphQL.AspNet.Schemas.Generation.TypeMakers { using System; using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Configuration; using GraphQL.AspNet.Interfaces.Engine; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; using GraphQL.AspNet.Schemas.TypeSystem; /// /// A "maker" capable of producing a qualified from its related template. /// - public class InterfaceGraphTypeMaker : IGraphTypeMaker + public class InterfaceGraphTypeMaker : FieldContainerGraphTypeMakerBase, IGraphTypeMaker { - private readonly ISchema _schema; + private readonly ISchemaConfiguration _config; + private readonly IGraphFieldMaker _fieldMaker; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The schema. - public InterfaceGraphTypeMaker(ISchema schema) + /// The configuration data to use when generating any graph types. + /// The field maker to use when generating fields on any + /// created interface types. + public InterfaceGraphTypeMaker( + ISchemaConfiguration config, + IGraphFieldMaker fieldMaker) + : base(config) { - _schema = Validation.ThrowIfNullOrReturn(schema, nameof(schema)); + _config = Validation.ThrowIfNullOrReturn(config, nameof(config)); + _fieldMaker = Validation.ThrowIfNullOrReturn(fieldMaker, nameof(fieldMaker)); } /// - public GraphTypeCreationResult CreateGraphType(Type concreteType) + public virtual GraphTypeCreationResult CreateGraphType(IGraphTypeTemplate typeTemplate) { - if (concreteType == null) - return null; - - var template = GraphQLProviders.TemplateProvider.ParseType(concreteType, TypeKind.INTERFACE) as IInterfaceGraphTypeTemplate; - if (template == null) + if (!(typeTemplate is IInterfaceGraphTypeTemplate template)) return null; + template.Parse(); template.ValidateOrThrow(false); var result = new GraphTypeCreationResult(); - var formatter = _schema.Configuration.DeclarationOptions.GraphNamingFormatter; + var formatter = _config.DeclarationOptions.GraphNamingFormatter; var directives = template.CreateAppliedDirectives(); var interfaceType = new InterfaceGraphType( formatter.FormatGraphTypeName(template.Name), + template.InternalName, template.ObjectType, template.Route, directives) @@ -65,10 +70,9 @@ public GraphTypeCreationResult CreateGraphType(Type concreteType) // account for any potential type system directives result.AddDependentRange(template.RetrieveRequiredTypes()); - var fieldMaker = GraphQLProviders.GraphTypeMakerProvider.CreateFieldMaker(_schema); - foreach (var fieldTemplate in ObjectGraphTypeMaker.GatherFieldTemplates(template, _schema)) + foreach (var fieldTemplate in this.GatherFieldTemplates(template)) { - var fieldResult = fieldMaker.CreateField(fieldTemplate); + var fieldResult = _fieldMaker.CreateField(fieldTemplate); interfaceType.Extend(fieldResult.Field); result.MergeDependents(fieldResult); @@ -91,7 +95,7 @@ public GraphTypeCreationResult CreateGraphType(Type concreteType) } result.GraphType = interfaceType; - result.ConcreteType = concreteType; + result.ConcreteType = template.ObjectType; return result; } } diff --git a/src/graphql-aspnet/Schemas/Generation/TypeMakers/ObjectGraphTypeMaker.cs b/src/graphql-aspnet/Schemas/Generation/TypeMakers/ObjectGraphTypeMaker.cs new file mode 100644 index 000000000..e4af3b668 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/ObjectGraphTypeMaker.cs @@ -0,0 +1,107 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.TypeMakers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Engine; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// A "maker" capable of producing a qualified from its related template. + /// + public class ObjectGraphTypeMaker : FieldContainerGraphTypeMakerBase, IGraphTypeMaker + { + private readonly ISchemaConfiguration _config; + private readonly IGraphFieldMaker _fieldMaker; + + /// + /// Initializes a new instance of the class. + /// + /// The schema configuration and setup options to use + /// when building out the graph type. + /// The field maker instnace used to create fields + /// on any created graph types. + public ObjectGraphTypeMaker( + ISchemaConfiguration config, + IGraphFieldMaker fieldMaker) + : base(config) + { + _config = Validation.ThrowIfNullOrReturn(config, nameof(config)); + _fieldMaker = Validation.ThrowIfNullOrReturn(fieldMaker, nameof(fieldMaker)); + } + + /// + public virtual GraphTypeCreationResult CreateGraphType(IGraphTypeTemplate typeTemplate) + { + if (!(typeTemplate is IObjectGraphTypeTemplate template)) + return null; + + template.Parse(); + template.ValidateOrThrow(false); + + var result = new GraphTypeCreationResult(); + + var formatter = _config.DeclarationOptions.GraphNamingFormatter; + var directives = template.CreateAppliedDirectives(); + + var objectType = new ObjectGraphType( + formatter.FormatGraphTypeName(template.Name), + template.InternalName, + template.ObjectType, + template.Route, + directives) + { + Description = template.Description, + Publish = template.Publish, + }; + + result.GraphType = objectType; + result.ConcreteType = template.ObjectType; + + // account for any potential type system directives + result.AddDependentRange(template.RetrieveRequiredTypes()); + + var templatesToRender = this.GatherFieldTemplates(template); + foreach (var fieldTemplate in templatesToRender) + { + var fieldResult = _fieldMaker.CreateField(fieldTemplate); + objectType.Extend(fieldResult.Field); + result.MergeDependents(fieldResult); + } + + // at least one field should have been rendered + // the type is invalid if there are no fields othe than __typename + if (objectType.Fields.Count == 1) + { + throw new GraphTypeDeclarationException( + $"The object graph type '{template.ObjectType.FriendlyName()}' defines 0 fields. " + + $"All object types must define at least one field.", + template.ObjectType); + } + + // add in declared interfaces by name + foreach (var iface in template.DeclaredInterfaces) + { + objectType.InterfaceNames.Add(formatter.FormatGraphTypeName(GraphTypeNames.ParseName(iface, TypeKind.INTERFACE))); + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/TypeMakers/ScalarGraphTypeMaker.cs b/src/graphql-aspnet/Schemas/Generation/TypeMakers/ScalarGraphTypeMaker.cs new file mode 100644 index 000000000..9a1313628 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/ScalarGraphTypeMaker.cs @@ -0,0 +1,67 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.TypeMakers +{ + using System; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Engine; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + + /// + /// A "maker" capable of producing a qualified from its related template. + /// + public class ScalarGraphTypeMaker : IGraphTypeMaker + { + private readonly ISchemaConfiguration _config; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration object used for configuring scalar types. + public ScalarGraphTypeMaker(ISchemaConfiguration config) + { + _config = Validation.ThrowIfNullOrReturn(config, nameof(config)); + } + + /// + public GraphTypeCreationResult CreateGraphType(IGraphTypeTemplate typeTemplate) + { + if (!(typeTemplate is IScalarGraphTypeTemplate template)) + return null; + + template.Parse(); + template.ValidateOrThrow(false); + + var scalarType = GlobalTypes.CreateScalarInstanceOrThrow(template.ScalarType); + scalarType = scalarType.Clone(_config.DeclarationOptions.GraphNamingFormatter.FormatGraphTypeName(scalarType.Name)); + + var result = new GraphTypeCreationResult() + { + GraphType = scalarType, + ConcreteType = scalarType.ObjectType, + }; + + // add any known directives as dependents + // to be added to the schema + foreach (var directiveToApply in scalarType.AppliedDirectives) + { + if (directiveToApply.DirectiveType != null) + result.AddDependent(directiveToApply.DirectiveType, TypeKind.DIRECTIVE); + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Engine/TypeMakers/UnionGraphTypeMaker.cs b/src/graphql-aspnet/Schemas/Generation/TypeMakers/UnionGraphTypeMaker.cs similarity index 61% rename from src/graphql-aspnet/Engine/TypeMakers/UnionGraphTypeMaker.cs rename to src/graphql-aspnet/Schemas/Generation/TypeMakers/UnionGraphTypeMaker.cs index 27ad7df53..4f2219d92 100644 --- a/src/graphql-aspnet/Engine/TypeMakers/UnionGraphTypeMaker.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/UnionGraphTypeMaker.cs @@ -7,32 +7,53 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Engine.TypeMakers +namespace GraphQL.AspNet.Schemas.Generation.TypeMakers { + using System; using System.Linq; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Common.Generics; using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Configuration; using GraphQL.AspNet.Interfaces.Engine; using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; /// /// An object responsible for generating a union graph type from a proxy. /// - public sealed class UnionGraphTypeMaker : IUnionGraphTypeMaker + public sealed class UnionGraphTypeMaker : IGraphTypeMaker, IUnionGraphTypeMaker { - private readonly ISchema _schema; + private readonly ISchemaConfiguration _config; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The schema. - public UnionGraphTypeMaker(ISchema schema) + /// The schema configuration used to determine what to incude + /// and how to build the union. + public UnionGraphTypeMaker(ISchemaConfiguration config) { - _schema = schema; + _config = Validation.ThrowIfNullOrReturn(config, nameof(config)); + } + + /// + public GraphTypeCreationResult CreateGraphType(IGraphTypeTemplate typeTemplate) + { + if (!(typeTemplate is IUnionGraphTypeTemplate template)) + return null; + + template.Parse(); + template.ValidateOrThrow(false); + + var proxy = GlobalTypes.CreateUnionProxyFromType(template.ProxyType); + return this.CreateUnionFromProxy(proxy); } /// @@ -49,10 +70,11 @@ public GraphTypeCreationResult CreateUnionFromProxy(IGraphUnionProxy proxy) var directives = directiveTemplates.CreateAppliedDirectives(); - var formatter = _schema.Configuration.DeclarationOptions.GraphNamingFormatter; + var formatter = _config.DeclarationOptions.GraphNamingFormatter; var name = formatter.FormatGraphTypeName(proxy.Name); var union = new UnionGraphType( name, + proxy.InternalName, (IUnionGraphTypeMapper)proxy, new SchemaItemPath(SchemaItemCollections.Types, name), directives) diff --git a/src/graphql-aspnet/Internal/TypeTemplates/AppliedDirectiveTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/AppliedDirectiveTemplate.cs similarity index 96% rename from src/graphql-aspnet/Internal/TypeTemplates/AppliedDirectiveTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/AppliedDirectiveTemplate.cs index 25b679714..2e9347e22 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/AppliedDirectiveTemplate.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/AppliedDirectiveTemplate.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using GraphQL.AspNet.Attributes; @@ -107,12 +107,12 @@ public virtual IAppliedDirective CreateAppliedDirective() } /// - public Type DirectiveType { get; } + public Type DirectiveType { get; init; } /// public string DirectiveName { get; private set; } /// - public object[] Arguments { get; } + public object[] Arguments { get; init; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/TypeTemplates/ApplyDirectiveAttributeExtensions.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/ApplyDirectiveAttributeExtensions.cs similarity index 98% rename from src/graphql-aspnet/Internal/TypeTemplates/ApplyDirectiveAttributeExtensions.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/ApplyDirectiveAttributeExtensions.cs index 07955e75e..ea8522c4c 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/ApplyDirectiveAttributeExtensions.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/ApplyDirectiveAttributeExtensions.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System.Collections.Generic; using System.Reflection; diff --git a/src/graphql-aspnet/Internal/TypeTemplates/ControllerActionGraphFieldTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/ControllerActionGraphFieldTemplate.cs similarity index 80% rename from src/graphql-aspnet/Internal/TypeTemplates/ControllerActionGraphFieldTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/ControllerActionGraphFieldTemplate.cs index 98ab22331..8bd70726f 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/ControllerActionGraphFieldTemplate.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/ControllerActionGraphFieldTemplate.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System.Diagnostics; using System.Reflection; @@ -16,9 +16,9 @@ namespace GraphQL.AspNet.Internal.TypeTemplates using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal.Resolvers; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; @@ -32,13 +32,25 @@ public class ControllerActionGraphFieldTemplate : MethodGraphFieldTemplateBase /// /// Initializes a new instance of the class. /// - /// The parent. - /// The method information. + /// The controller that owns this action. + /// The method information to be templatized. public ControllerActionGraphFieldTemplate(IGraphControllerTemplate parent, MethodInfo methodInfo) : base(parent, methodInfo) { } + /// + /// Initializes a new instance of the class. + /// + /// The controller that owns this action. + /// The method information to be templatized. + /// A custom, external attribute provider to use instead for extracting + /// configuration attributes instead of the provider on . + public ControllerActionGraphFieldTemplate(IGraphControllerTemplate parent, MethodInfo methodInfo, ICustomAttributeProvider attributeProvider) + : base(parent, methodInfo, attributeProvider) + { + } + /// /// When overridden in a child class, this metyhod builds the route that will be assigned to this method /// using the implementation rules of the concrete type. @@ -72,7 +84,7 @@ public override void ValidateOrThrow(bool validateChildren = true) if (declaration != null && declaration == typeof(GraphFieldAttribute)) { throw new GraphTypeDeclarationException( - $"Invalid action declaration. The controller action method '{this.InternalFullName}' declares " + + $"Invalid action declaration. The controller action method '{this.InternalName}' declares " + $"a '{nameof(GraphFieldAttribute)}'. This attribute is reserved for model classes. Controller " + $"actions must declare an operation specific attribute such as '{nameof(QueryAttribute)}', '{nameof(MutationAttribute)}' etc."); } @@ -84,7 +96,7 @@ public override void ValidateOrThrow(bool validateChildren = true) /// IGraphFieldResolver. public override IGraphFieldResolver CreateResolver() { - return new GraphControllerActionResolver(this); + return new GraphControllerActionResolver(this.CreateResolverMetaData()); } /// diff --git a/src/graphql-aspnet/Internal/TypeTemplates/DependentType.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/DependentType.cs similarity index 71% rename from src/graphql-aspnet/Internal/TypeTemplates/DependentType.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/DependentType.cs index c2bc4c0fd..b50e9867d 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/DependentType.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/DependentType.cs @@ -7,12 +7,13 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Diagnostics; using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas.TypeSystem; /// @@ -39,12 +40,29 @@ public DependentType(Type type, TypeKind expectedKind) this.ExpectedKind = expectedKind; } + /// + /// Initializes a new instance of the class. + /// + /// The union instance that needs to be turned into a graph type. + /// The expected kind. + public DependentType(IGraphUnionProxy union, TypeKind expectedKind) + { + this.UnionDeclaration = Validation.ThrowIfNullOrReturn(union, nameof(union)); + this.ExpectedKind = expectedKind; + } + /// /// Gets the dependent type this instance points to. /// /// The type. public Type Type { get; } + /// + /// Gets a union proxy that must be added to the schema. + /// + /// The union declaration. + public IGraphUnionProxy UnionDeclaration { get; } + /// /// Gets the expected type kind that the target should be /// instantiated as. diff --git a/src/graphql-aspnet/Internal/TypeTemplates/EnumGraphTypeTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/EnumGraphTypeTemplate.cs similarity index 95% rename from src/graphql-aspnet/Internal/TypeTemplates/EnumGraphTypeTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/EnumGraphTypeTemplate.cs index 7709950a7..a4a8b8fcc 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/EnumGraphTypeTemplate.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/EnumGraphTypeTemplate.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; @@ -149,12 +149,6 @@ public override void ValidateOrThrow(bool validateChildren = true) /// public IReadOnlyList Values => _values; - /// - public override string InternalFullName => this.ObjectType?.FriendlyName(true); - - /// - public override string InternalName => this.ObjectType?.FriendlyName(); - /// public override AppliedSecurityPolicyGroup SecurityPolicies => AppliedSecurityPolicyGroup.Empty; diff --git a/src/graphql-aspnet/Internal/TypeTemplates/EnumValueTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/EnumValueTemplate.cs similarity index 82% rename from src/graphql-aspnet/Internal/TypeTemplates/EnumValueTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/EnumValueTemplate.cs index 79edf9202..ea3313079 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/EnumValueTemplate.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/EnumValueTemplate.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.ComponentModel; @@ -14,6 +14,7 @@ namespace GraphQL.AspNet.Internal.TypeTemplates using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Schemas.Structural; @@ -51,18 +52,28 @@ protected override void ParseTemplateDefinition() this.Description = this.FieldInfo.SingleAttributeOrDefault()?.Description ?? null; var enumAttrib = this.FieldInfo.SingleAttributeOrDefault(); + this.InternalName = enumAttrib?.InternalName; var valueName = enumAttrib?.Name?.Trim() ?? Constants.Routing.ENUM_VALUE_META_NAME; if (valueName.Length == 0) valueName = Constants.Routing.ENUM_VALUE_META_NAME; valueName = valueName.Replace(Constants.Routing.ENUM_VALUE_META_NAME, this.FieldInfo.Name); this.Route = new SchemaItemPath(SchemaItemPath.Join(this.Parent.Route.Path, valueName)); + + if (string.IsNullOrWhiteSpace(this.InternalName)) + this.InternalName = $"{this.Parent.InternalName}.{this.FieldInfo.Name}"; } /// public override void ValidateOrThrow(bool validateChildren = true) { - GraphValidation.EnsureGraphNameOrThrow($"{this.InternalFullName}", this.Name); + if (string.IsNullOrWhiteSpace(this.InternalName)) + { + throw new GraphTypeDeclarationException( + $"The enum value template of `{this.Value}`, owned by enum {this.Parent.InternalName}, does not declare a valid internal name"); + } + + GraphValidation.EnsureGraphNameOrThrow($"{this.InternalName}", this.Name); } /// @@ -84,9 +95,6 @@ public override void ValidateOrThrow(bool validateChildren = true) public string NumericValueAsString { get; private set; } /// - public override string InternalFullName => $"{this.Parent.InternalFullName}.{this.InternalName}"; - - /// - public override string InternalName => this.FieldInfo.Name; + public string DeclaredLabel => this.FieldInfo.Name; } } \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/TypeTemplates/GraphArgumentTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphArgumentTemplate.cs similarity index 51% rename from src/graphql-aspnet/Internal/TypeTemplates/GraphArgumentTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphArgumentTemplate.cs index 8946df5ad..fad9d4591 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/GraphArgumentTemplate.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphArgumentTemplate.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; @@ -19,11 +19,16 @@ namespace GraphQL.AspNet.Internal.TypeTemplates using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Execution.Contexts; using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Execution.Resolvers; + using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; /// /// A template describing an argument declared a field. @@ -33,6 +38,10 @@ public class GraphArgumentTemplate : IGraphArgumentTemplate { private FromGraphQLAttribute _argDeclaration; private bool _invalidTypeExpression; + private HashSet _foundModifiers; + private GraphSkipAttribute _argSkipDeclaration; + private GraphSkipAttribute _argTypeSkipDeclaration; + private bool _isParsed = false; /// /// Initializes a new instance of the class. @@ -45,6 +54,8 @@ public GraphArgumentTemplate(IGraphFieldTemplateBase parent, ParameterInfo param Validation.ThrowIfNull(parent, nameof(parent)); Validation.ThrowIfNull(parameter, nameof(parameter)); + _foundModifiers = new HashSet(); + this.Parent = parent; this.Parameter = parameter; } @@ -52,15 +63,26 @@ public GraphArgumentTemplate(IGraphFieldTemplateBase parent, ParameterInfo param /// public virtual void Parse() { + if (_isParsed) + return; + + _isParsed = true; this.DeclaredArgumentType = this.Parameter.ParameterType; this.ObjectType = GraphValidation.EliminateWrappersFromCoreType(this.Parameter.ParameterType); this.AppliedDirectives = this.ExtractAppliedDirectiveTemplates(); // set the name - _argDeclaration = this.Parameter.SingleAttributeOrDefault(); + _argDeclaration = this.AttributeProvider.SingleAttributeOrDefault(); string name = null; if (_argDeclaration != null) + { name = _argDeclaration?.ArgumentName?.Trim(); + _foundModifiers.Add(GraphArgumentModifiers.ExplicitSchemaItem); + this.InternalName = _argDeclaration.InternalName; + } + + if (string.IsNullOrWhiteSpace(this.InternalName)) + this.InternalName = $"{this.Parent?.InternalName}.{this.Parameter.Name}"; if (string.IsNullOrWhiteSpace(name)) name = Constants.Routing.PARAMETER_META_NAME; @@ -68,7 +90,7 @@ public virtual void Parse() name = name.Replace(Constants.Routing.PARAMETER_META_NAME, this.Parameter.Name); this.Route = new GraphArgumentFieldPath(this.Parent.Route, name); - this.Description = this.Parameter.SingleAttributeOrDefault()?.Description?.Trim(); + this.Description = this.AttributeProvider.SingleAttributeOrDefault()?.Description?.Trim(); if (_argDeclaration?.TypeExpression == null) { @@ -109,24 +131,60 @@ public virtual void Parse() // set appropriate meta data about this parameter for inclusion in the type system this.TypeExpression = GraphTypeExpression.FromType(this.DeclaredArgumentType, this.DeclaredTypeWrappers); - this.TypeExpression = this.TypeExpression.CloneTo(GraphTypeNames.ParseName(this.ObjectType, TypeKind.INPUT_OBJECT)); + this.TypeExpression = this.TypeExpression.CloneTo(Constants.Other.DEFAULT_TYPE_EXPRESSION_TYPE_NAME); + + // perform any inspections and logic to determine + // how this argument performs within the application. + var fromServicesAttrib = this.Parameter.SingleAttributeOfTypeOrDefault(); + if (fromServicesAttrib != null) + _foundModifiers.Add(GraphArgumentModifiers.ExplicitInjected); - // when this argument accepts the same data type as the data returned by its owners target source type - // i.e. if the source data supplied to the field for resolution is the same as this argument - // then assume this argument is to contain the source data - // since the source data will be an OBJECT type (not INPUT_OBJECT) there is no way the user could have supplied it if (this.IsSourceDataArgument()) - { - this.ArgumentModifiers = this.ArgumentModifiers - | GraphArgumentModifiers.Internal - | GraphArgumentModifiers.ParentFieldResult; - } - else if (this.IsCancellationTokenArgument()) - { - this.ArgumentModifiers = this.ArgumentModifiers - | GraphArgumentModifiers.Internal - | GraphArgumentModifiers.CancellationToken; - } + _foundModifiers.Add(GraphArgumentModifiers.ParentFieldResult); + + if (this.IsCancellationTokenArgument()) + _foundModifiers.Add(GraphArgumentModifiers.CancellationToken); + + if (this.IsResolutionContext()) + _foundModifiers.Add(GraphArgumentModifiers.ResolutionContext); + + if (this.IsHttpContext()) + _foundModifiers.Add(GraphArgumentModifiers.HttpContext); + + if (this.MustBeInjected() && _foundModifiers.Count == 0) + _foundModifiers.Add(GraphArgumentModifiers.ImplicitInjected); + + if (_foundModifiers.Count > 0) + this.ArgumentModifier = _foundModifiers.First(); + + _argSkipDeclaration = this.AttributeProvider.FirstAttributeOfTypeOrDefault(); + _argTypeSkipDeclaration = this.Parameter.ParameterType.FirstAttributeOfTypeOrDefault(); + } + + /// + /// Determines whether this instance represents a parameter that should be marked as a "resolution context" + /// and filled with the active context when possible. + /// + /// true if the ; otherwise, false. + public virtual bool IsResolutionContext() + { + if (Validation.IsCastable(this.ObjectType, typeof(SchemaItemResolutionContext))) + return true; + + return false; + } + + /// + /// Determines whether this instance represents a parameter that should be marked as a "resolution context" + /// and filled with the active context when possible. + /// + /// true if the ; otherwise, false. + public virtual bool IsHttpContext() + { + if (Validation.IsCastable(this.ObjectType, typeof(HttpContext))) + return true; + + return false; } /// @@ -136,6 +194,18 @@ public virtual void Parse() /// System.Boolean. protected virtual bool IsSourceDataArgument() { + // there can only ever be one source argument + if (this.Parent.Arguments.Any(x => x.ArgumentModifier.IsSourceParameter())) + return false; + + if (_foundModifiers.Count > 0) + return false; + + // when this argument accepts the same data type as the data returned by its owner's resolver + // i.e. if the source data supplied to the field for resolution is the same as this parameter + // then assume this argument is to contain the source data + // since the source data will be an OBJECT type (not INPUT_OBJECT) + // there is no way the user could have supplied it if (this.ObjectType == this.Parent.SourceObjectType) { var sourceType = this.ObjectType; @@ -144,9 +214,6 @@ protected virtual bool IsSourceDataArgument() sourceType = typeof(IEnumerable<>).MakeGenericType(sourceType); } - if (this.Parent.Arguments.Any(x => x.ArgumentModifiers.HasFlag(GraphArgumentModifiers.ParentFieldResult))) - return false; - return sourceType == this.DeclaredArgumentType; } @@ -162,20 +229,40 @@ protected virtual bool IsCancellationTokenArgument() { if (this.ObjectType == typeof(CancellationToken)) { - if (this.Parent.Arguments.All(x => !x.ArgumentModifiers.IsCancellationToken())) + // only one captured cancel token allowed + if (this.Parent.Arguments.All(x => !x.ArgumentModifier.IsCancellationToken())) return true; } return false; } + /// + /// Determines if this argument represents a value that MUST + /// be injected because it doesn't conform to the specification rules + /// for graphql arguments. + /// + /// true if this argument must be injected, false otherwise. + protected virtual bool MustBeInjected() + { + // interfaces are not allowed as arguments to a field + // therefore they must be injected + if (this.ObjectType.IsInterface) + return true; + + if (!GraphValidation.IsValidGraphType(this.ObjectType)) + return true; + + return false; + } + /// /// Retrieves the concrete types that this instance may return or make use of in a graph query. /// /// IEnumerable<Type>. public IEnumerable RetrieveRequiredTypes() { - if (this.ArgumentModifiers.IsInternalParameter()) + if (!this.ArgumentModifier.CouldBePartOfTheSchema()) { // internal parameters should not be injected into the object graph // so they have no dependents @@ -201,12 +288,19 @@ public IEnumerable RetrieveRequiredTypes() /// public void ValidateOrThrow(bool validateChildren = true) { - GraphValidation.EnsureGraphNameOrThrow(this.InternalFullName, this.Name); + if (string.IsNullOrWhiteSpace(this.InternalName)) + { + throw new GraphTypeDeclarationException( + $"The item '{this.Parent.InternalName}' declares a parameter '{this.Parameter.Name}' " + + "that did not not declare a valid internal name. Templating cannot continue."); + } + + GraphValidation.EnsureGraphNameOrThrow(this.InternalName, this.Name); if (_invalidTypeExpression) { throw new GraphTypeDeclarationException( - $"The item '{this.Parent.InternalFullName}' declares an argument '{this.Name}' that " + + $"The item '{this.Parent.InternalName}' declares a parameter '{this.Name}' that " + $"defines an invalid {nameof(FromGraphQLAttribute.TypeExpression)} (Value = '{_argDeclaration.TypeExpression}'). " + $"The provided type expression must be a valid query language type expression or null."); } @@ -217,36 +311,104 @@ public void ValidateOrThrow(bool validateChildren = true) .FromType(this.DeclaredArgumentType) .CloneTo(GraphTypeNames.ParseName(this.ObjectType, TypeKind.INPUT_OBJECT)); - if (!GraphTypeExpression.AreTypesCompatiable(actualTypeExpression, this.TypeExpression)) + if (!GraphTypeExpression.AreTypesCompatiable(actualTypeExpression, this.TypeExpression, false)) { throw new GraphTypeDeclarationException( - $"The item '{this.Parent.InternalFullName}' declares an argument '{this.Name}' that " + + $"The item '{this.Parent.InternalName}' declares a parameter '{this.Name}' that " + $"defines a {nameof(FromGraphQLAttribute.TypeExpression)} that is incompatiable with the " + $".NET parameter. (Declared '{this.TypeExpression}' is incompatiable with '{actualTypeExpression}') "); } - if (!this.ArgumentModifiers.IsInternalParameter()) + if (_foundModifiers.Contains(GraphArgumentModifiers.ExplicitSchemaItem)) { if (this.ObjectType.IsInterface) { // special error message for trying to use an interface in an argument throw new GraphTypeDeclarationException( - $"The item '{this.Parent.InternalFullName}' declares an argument '{this.Name}' of type '{this.ObjectType.FriendlyName()}' " + - $"which is an interface. Interfaces cannot be used as input arguments to any type."); + $"The item '{this.Parent.InternalName}' declares an explicit argument '{this.Name}' of type '{this.ObjectType.FriendlyName()}' " + + $"which is an interface. Interfaces cannot be used as input arguments to any graph type or directive."); } if (!GraphValidation.IsValidGraphType(this.ObjectType)) { throw new GraphTypeDeclarationException( - $"The item '{this.Parent.InternalFullName}' declares an argument '{this.Name}' of type '{this.ObjectType.FriendlyName()}' " + + $"The item '{this.Parent.InternalName}' declares an argument '{this.Name}' of type '{this.ObjectType.FriendlyName()}' " + $"which is not a valid graph type."); } } + // the most common scenario for multiple arg modifiers, + // throw an exception with explicit text on how to fix it + if (_foundModifiers.Contains(GraphArgumentModifiers.ExplicitInjected) + && _foundModifiers.Contains(GraphArgumentModifiers.ExplicitSchemaItem)) + { + throw new GraphTypeDeclarationException( + $"The item '{this.Parent.InternalName}' declares a parameter '{this.Name}' that " + + $"is defined to be supplied from a graphql query AND from a DI services container. " + + $"An argument can not be supplied from a graphql query and from a DI container. If declaring argument attributes, supply " + + $"{nameof(FromGraphQLAttribute)} or {nameof(FromServicesAttribute)}, but not both."); + } + + if (_foundModifiers.Contains(GraphArgumentModifiers.ImplicitInjected) + && _foundModifiers.Contains(GraphArgumentModifiers.ExplicitSchemaItem)) + { + throw new GraphTypeDeclarationException( + $"The item '{this.Parent.InternalName}' declares a parameter '{this.Name}' that " + + $"is defined to be supplied from a graphql query. However, the parameter definition " + + $"inidcates that it could never be part of a schema and must be resolved from a DI services container. " + + $"Remove the explicit {nameof(FromGraphQLAttribute)} declaration or change the parameter type."); + } + + if (_foundModifiers.Count > 1) + { + var flags = string.Join(", ", _foundModifiers); + throw new GraphTypeDeclarationException( + $"The item '{this.Parent.InternalName}' declares a parameter '{this.Name}' that " + + $"declares more than one behavior modification flag. Each parameter must declare only one " + + $"behavioral role within a given resolver method. Flags Declared: {flags}"); + } + + if (_argSkipDeclaration != null && this.ArgumentModifier.CouldBePartOfTheSchema()) + { + throw new GraphTypeDeclarationException( + $"The item '{this.Parent.InternalName}' contains a parameter '{this.Name}' that " + + $"declares the {nameof(GraphSkipAttribute)}. However, this argument may be included in the schema in some scenarios. " + + $"If this argument is intended to be served from a service provider try adding {typeof(FromServicesAttribute)} to its declaration."); + } + + if (_argTypeSkipDeclaration != null && this.ArgumentModifier.CouldBePartOfTheSchema()) + { + throw new GraphTypeDeclarationException( + $"The item '{this.Parent.InternalName}' contains a parameter '{this.Name}' that " + + $"is of type {this.Parameter.ParameterType.FriendlyName()} . This type declares the {nameof(GraphSkipAttribute)} and is " + + $"not allowed to appear in any schema but is currently being interpreted as an INPUT_OBJECT. If the parameter value is intended to be served " + + $"from a service provider try adding {typeof(FromServicesAttribute)} to its declaration."); + } + foreach (var directive in this.AppliedDirectives) directive.ValidateOrThrow(); } + /// + public IGraphFieldResolverParameterMetaData CreateResolverMetaData() + { + var isValidList = this.TypeExpression.IsListOfItems; + if (!isValidList && this.ArgumentModifier.IsSourceParameter()) + { + if (this.Parent is IGraphFieldTemplate gft) + isValidList = gft.Mode == FieldResolutionMode.Batch; + } + + return new FieldResolverParameterMetaData( + this.Parameter, + this.InternalName, + this.Parent.InternalName, + this.ArgumentModifier, + isValidList, + this.HasDefaultValue, + this.DefaultValue); + } + /// public string Name => this.Route.Name; @@ -260,10 +422,7 @@ public void ValidateOrThrow(bool validateChildren = true) public Type DeclaredArgumentType { get; private set; } /// - public string InternalFullName => $"{this.Parent?.InternalFullName}.{this.Parameter.Name}"; - - /// - public string InternalName => this.Parameter.Name; + public string InternalName { get; private set; } /// public bool IsExplicitDeclaration => true; @@ -287,10 +446,10 @@ public void ValidateOrThrow(bool validateChildren = true) public Type ObjectType { get; private set; } /// - public GraphArgumentModifiers ArgumentModifiers { get; protected set; } + public GraphArgumentModifiers ArgumentModifier { get; protected set; } /// - public string DeclaredArgumentName => this.Parameter.Name; + public string ParameterName => this.Parameter.Name; /// public MetaGraphTypes[] DeclaredTypeWrappers { get; private set; } diff --git a/src/graphql-aspnet/Internal/TypeTemplates/GraphControllerTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphControllerTemplate.cs similarity index 85% rename from src/graphql-aspnet/Internal/TypeTemplates/GraphControllerTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphControllerTemplate.cs index 76a6420ac..a44784424 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/GraphControllerTemplate.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphControllerTemplate.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; @@ -47,17 +47,18 @@ public GraphControllerTemplate(Type controllerType) } /// - protected override IEnumerable GatherPossibleTemplateMembers() + protected override IEnumerable GatherPossibleFieldTemplates() { return this.ObjectType.GetMethods(BindingFlags.Public | BindingFlags.Instance) - .Where(x => + .Where(x => !x.IsAbstract && !x.IsGenericMethod && !x.IsSpecialName && x.DeclaringType != typeof(object) && x.DeclaringType != typeof(ValueType)) - .Cast() - .Concat(this.ObjectType.GetProperties(BindingFlags.Public | BindingFlags.Instance)); + .Cast() + .Concat(this.ObjectType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + .Select(x => new MemberInfoProvider(x)); } /// @@ -113,35 +114,29 @@ public override void ValidateOrThrow(bool validateChildren = true) } /// - protected override bool CouldBeGraphField(MemberInfo memberInfo) + protected override bool CouldBeGraphField(IMemberInfoProvider fieldProvider) { - if (memberInfo == null || memberInfo is PropertyInfo) + if (fieldProvider?.MemberInfo == null || !(fieldProvider.MemberInfo is MethodInfo methodInfo)) return false; - if (!base.CouldBeGraphField(memberInfo)) + if (!base.CouldBeGraphField(fieldProvider)) return false; // always require explicit attribution from controller action methods - return memberInfo.SingleAttributeOfTypeOrDefault() != null; + return fieldProvider.AttributeProvider.SingleAttributeOfTypeOrDefault() != null; } /// - protected override IGraphFieldTemplate CreateMethodFieldTemplate(MethodInfo methodInfo) + protected override IGraphFieldTemplate CreateFieldTemplate(IMemberInfoProvider member) { - if (methodInfo == null) + // safety check to ensure properites on controllers can never be parsed as fields + if (member?.MemberInfo == null || !(member.MemberInfo is MethodInfo)) return null; - if (methodInfo.HasAttribute()) - return new GraphTypeExtensionFieldTemplate(this, methodInfo); + if (member.AttributeProvider.HasAttribute()) + return new GraphTypeExtensionFieldTemplate(this, (MethodInfo)member.MemberInfo, member.AttributeProvider); else - return new ControllerActionGraphFieldTemplate(this, methodInfo); - } - - /// - protected override IGraphFieldTemplate CreatePropertyFieldTemplate(PropertyInfo prop) - { - // safety check to ensure properites on controllers can never be parsed as fields - return null; + return new ControllerActionGraphFieldTemplate(this, (MethodInfo)member.MemberInfo, member.AttributeProvider); } /// diff --git a/src/graphql-aspnet/Internal/TypeTemplates/GraphDirectiveMethodTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphDirectiveMethodTemplate.cs similarity index 70% rename from src/graphql-aspnet/Internal/TypeTemplates/GraphDirectiveMethodTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphDirectiveMethodTemplate.cs index d2176a467..54e5add74 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/GraphDirectiveMethodTemplate.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphDirectiveMethodTemplate.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; @@ -21,10 +21,11 @@ namespace GraphQL.AspNet.Internal.TypeTemplates using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Execution.Resolvers; + using GraphQL.AspNet.Execution.RulesEngine.RuleSets.DocumentValidation.QueryOperationSteps; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; @@ -32,7 +33,7 @@ namespace GraphQL.AspNet.Internal.TypeTemplates /// A template describing an action method declared on a directive. /// [DebuggerDisplay("{InternalName} (Type: {Parent.InternalName})")] - public class GraphDirectiveMethodTemplate : IGraphFieldTemplateBase, IGraphFieldResolverMethod + public class GraphDirectiveMethodTemplate : IGraphFieldTemplateBase { private readonly List _arguments; @@ -41,12 +42,26 @@ public class GraphDirectiveMethodTemplate : IGraphFieldTemplateBase, IGraphField /// /// The owner of this method. /// The method information. - internal GraphDirectiveMethodTemplate(IGraphTypeTemplate parent, MethodInfo method) + public GraphDirectiveMethodTemplate(IGraphTypeTemplate parent, MethodInfo method) { this.Parent = Validation.ThrowIfNullOrReturn(parent, nameof(parent)); this.Method = Validation.ThrowIfNullOrReturn(method, nameof(method)); this.Parameters = this.Method.GetParameters().ToList(); _arguments = new List(); + this.AttributeProvider = this.Method; + } + + /// + /// Initializes a new instance of the class. + /// + /// The owner of this method. + /// The method information. + /// A custom attribute provider this template will use + /// to perform its configuration. + public GraphDirectiveMethodTemplate(IGraphTypeTemplate parent, MethodInfo method, ICustomAttributeProvider attributeProvider) + : this(parent, method) + { + this.AttributeProvider = Validation.ThrowIfNullOrReturn(attributeProvider, nameof(attributeProvider)); } /// @@ -54,17 +69,22 @@ internal GraphDirectiveMethodTemplate(IGraphTypeTemplate parent, MethodInfo meth /// public virtual void Parse() { + if (_isParsed) + return; + + _isParsed = true; this.DeclaredType = this.Method.ReturnType; this.ObjectType = GraphValidation.EliminateWrappersFromCoreType(this.DeclaredType); this.TypeExpression = new GraphTypeExpression(this.ObjectType.FriendlyName()); - this.Description = this.Method.SingleAttributeOrDefault()?.Description; + this.Description = this.AttributeProvider.SingleAttributeOrDefault()?.Description; this.IsAsyncField = Validation.IsCastable(this.Method.ReturnType); this.AppliedDirectives = this.ExtractAppliedDirectiveTemplates(); + this.InternalName = $"{this.Parent?.InternalName ?? "UnknownDirective"}.{this.Method.Name}"; // deteremine all the directive locations where this method should be invoked var locations = DirectiveLocation.NONE; - foreach (var attrib in this.Method.AttributesOfType()) + foreach (var attrib in this.AttributeProvider.AttributesOfType()) { locations = locations | attrib.Locations; } @@ -129,14 +149,14 @@ public virtual void ValidateOrThrow(bool validateChildren = true) if (this.Method.SingleAttributeOrDefault() != null) { throw new GraphTypeDeclarationException( - $"The directive method {this.InternalFullName} defines a {nameof(GraphSkipAttribute)}. It cannot be parsed or added " + + $"The directive resolver {this.InternalName} defines a {nameof(GraphSkipAttribute)}. It cannot be parsed or added " + "to the object graph."); } if (this.AppliedDirectives.Any()) { throw new GraphTypeDeclarationException( - $"The directive method {this.InternalFullName} defines an {nameof(ApplyDirectiveAttribute)}. " + + $"The directive resolver {this.InternalName} defines an {nameof(ApplyDirectiveAttribute)}. " + $"Directive methods cannot have applied directives."); } @@ -149,9 +169,9 @@ public virtual void ValidateOrThrow(bool validateChildren = true) if (genericArgs.Length != 1) { throw new GraphTypeDeclarationException( - $"The directive method '{this.InternalFullName}' defines a return type of'{typeof(Task).Name}' but " + + $"The directive resolver '{this.InternalName}' defines a return type of'{typeof(Task).Name}' but " + "defines no contained return type for the resultant model object yielding a void return after " + - "completion of the task. All graph methods must return a single model object. Consider using " + + "completion of the task. All resolvers must return a single model object. Consider using " + $"'{typeof(Task<>).Name}' instead for asyncronous methods"); } } @@ -159,8 +179,14 @@ public virtual void ValidateOrThrow(bool validateChildren = true) if (this.ExpectedReturnType != typeof(IGraphActionResult)) { throw new GraphTypeDeclarationException( - $"The directive method '{this.InternalFullName}' does not return a {nameof(IGraphActionResult)}. " + - $"All directive methods must return a {nameof(IGraphActionResult)} or {typeof(Task).FriendlyName()}"); + $"The directive resolver '{this.InternalName}' does not return a {nameof(IGraphActionResult)}. " + + $"All directive resolvers must return a {nameof(IGraphActionResult)} or {typeof(Task).FriendlyName()}"); + } + + if (string.IsNullOrWhiteSpace(this.InternalName)) + { + throw new GraphTypeDeclarationException( + $"The directive resolver template identified by `{this.Method.Name}` does not declare a valid internal name."); } if (validateChildren) @@ -177,6 +203,7 @@ public virtual void ValidateOrThrow(bool validateChildren = true) public IEnumerable RetrieveRequiredTypes() { var list = new List(); + foreach (var argument in this.Arguments) list.AddRange(argument.RetrieveRequiredTypes()); @@ -191,6 +218,24 @@ public IEnumerable RetrieveRequiredTypes() return list; } + /// + public IGraphFieldResolverMetaData CreateResolverMetaData() + { + var paramSet = new FieldResolverParameterMetaDataCollection( + this.Arguments.Select(x => x.CreateResolverMetaData())); + + return new FieldResolverMetaData( + this.Method, + paramSet, + this.ExpectedReturnType, + this.IsAsyncField, + this.InternalName, + this.Method.Name, + this.Parent.ObjectType, + this.Parent.InternalName, + this.Parent.TemplateSource); + } + /// /// Gets the bitwise flags of locations that this method is defined /// to handle. @@ -201,6 +246,8 @@ public IEnumerable RetrieveRequiredTypes() /// public MetaGraphTypes[] DeclaredTypeWrappers => null; // not used by directives + private bool _isParsed; + /// /// Gets declared return type of the method minus any asyncronous wrappers (i.e. the T in Task{T}). /// @@ -225,10 +272,10 @@ public IEnumerable RetrieveRequiredTypes() /// public IReadOnlyList Arguments => _arguments; - /// + /// public Type ExpectedReturnType { get; protected set; } - /// + /// public bool IsAsyncField { get; protected set; } /// @@ -243,26 +290,26 @@ public IEnumerable RetrieveRequiredTypes() /// public Type SourceObjectType => this.Parent?.ObjectType; - /// + /// public IGraphTypeTemplate Parent { get; } - /// + /// public MethodInfo Method { get; } - /// + /// + /// Gets the set of parameters declared on the . + /// + /// The parameters. public IReadOnlyList Parameters { get; } /// public GraphFieldSource FieldSource => GraphFieldSource.Method; /// - public string InternalFullName => $"{this.Parent?.InternalFullName}.{this.Method.Name}"; - - /// - public string InternalName => this.Method.Name; + public string InternalName { get; protected set; } /// - public ICustomAttributeProvider AttributeProvider => this.Method; + public ICustomAttributeProvider AttributeProvider { get; } /// public IEnumerable AppliedDirectives { get; private set; } diff --git a/src/graphql-aspnet/Internal/TypeTemplates/GraphDirectiveMethodTemplateContainer.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphDirectiveMethodTemplateContainer.cs similarity index 82% rename from src/graphql-aspnet/Internal/TypeTemplates/GraphDirectiveMethodTemplateContainer.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphDirectiveMethodTemplateContainer.cs index c70645574..c53d88373 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/GraphDirectiveMethodTemplateContainer.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphDirectiveMethodTemplateContainer.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System.Collections.Generic; using System.Linq; @@ -43,7 +43,6 @@ public IEnumerable RetrieveRequiredTypes() { // all methods are required to be the same signatured // we can just pull the dependnent types on the first - var list = new List(); if (_templateMap.Count > 0) return _templateMap.Values.First().RetrieveRequiredTypes(); @@ -77,14 +76,15 @@ public void RegisterMethod(GraphDirectiveMethodTemplate methodTemplate) } /// - /// Retrieves the method template mapped to the given lifecycle and location. Returns null if nothing is registered. + /// Retrieves the resolver data mapped to the given location usage. Returns null if nothing is registered + /// for the provided location. /// /// A valid graphql directive location. /// IGraphMethod. - public IGraphFieldResolverMethod FindMethod(DirectiveLocation location) + public IGraphFieldResolverMetaData FindMetaData(DirectiveLocation location) { if (_templateMap.ContainsKey(location)) - return _templateMap[location]; + return _templateMap[location].CreateResolverMetaData(); return null; } @@ -117,19 +117,18 @@ private bool DoMethodSignaturesMatch(GraphDirectiveMethodTemplate left, GraphDir } /// - /// When overridden in a child class, allows the template to perform some final validation checks - /// on the integrity of itself. An exception should be thrown to stop the template from being - /// persisted if the object is unusable or otherwise invalid in the manner its been built. + /// Validates that this template is item and correct according to its own rules + /// or throws an exception. /// - /// if set to true any child items (e.g. fields on an interface, arguments on a field) - /// are also validated. - public void ValidateOrThrow(bool validateChildren = true) + /// if set to true + /// any child objects this instance manages will also be checked for validity. + public virtual void ValidateOrThrow(bool validateChildren = true) { if (_duplicateDirectiveLocations != null && _duplicateDirectiveLocations.Count > 0) { var duplicatedDecs = string.Join(",", _duplicateDirectiveLocations.Select(x => $"'{x.ToString()}'")); throw new GraphTypeDeclarationException( - $"The directive '{_parent.InternalFullName}' attempted to register more than one method to handle " + + $"The directive '{_parent.InternalName}' attempted to register more than one method to handle " + $"a single {nameof(DirectiveLocation)}. Each directive can only define, at most, one method per {nameof(DirectiveLocation)}. " + $"Duplicated Locations: {duplicatedDecs}"); } @@ -157,7 +156,7 @@ public void ValidateOrThrow(bool validateChildren = true) if (!this.DoMethodSignaturesMatch(baseExecutionMethod, kvp.Value)) { throw new GraphTypeDeclarationException( - $"The method '{kvp.Value.InternalFullName}' (Target Location: {kvp.Value}) declares a signature of '{kvp.Value.MethodSignature}'. " + + $"The method '{kvp.Value.InternalName}' (Target Location: {kvp.Value}) declares a signature of '{kvp.Value.MethodSignature}'. " + $"However, which is different than the method '{baseExecutionMethod.InternalName}'. " + $"All location targeting methods on a directive must have the same method signature " + $"including parameter types, names and declaration order."); @@ -165,6 +164,22 @@ public void ValidateOrThrow(bool validateChildren = true) } } + /// + /// Creates the a dictionary of metadata items, keyed by the location where each resolver + /// should execute.. + /// + /// IReadOnlyDictionary<DirectiveLocation, IGraphFieldResolverMetaData>. + public IReadOnlyDictionary CreateMetadataCollection() + { + var dicOut = new Dictionary(); + foreach (var kvp in _templateMap) + { + dicOut.Add(kvp.Key, kvp.Value.CreateResolverMetaData()); + } + + return dicOut; + } + /// /// Gets the total number of registrations tracked by this instance. /// diff --git a/src/graphql-aspnet/Internal/TypeTemplates/GraphDirectiveTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphDirectiveTemplate.cs similarity index 61% rename from src/graphql-aspnet/Internal/TypeTemplates/GraphDirectiveTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphDirectiveTemplate.cs index 929d2d681..b032fab78 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/GraphDirectiveTemplate.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphDirectiveTemplate.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; @@ -21,9 +21,9 @@ namespace GraphQL.AspNet.Internal.TypeTemplates using GraphQL.AspNet.Directives; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal.Resolvers; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Security; @@ -37,11 +37,14 @@ public class GraphDirectiveTemplate : GraphTypeTemplateBase, IGraphDirectiveTemp private AppliedSecurityPolicyGroup _securityPolicies; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Type of the graph directive being described. - public GraphDirectiveTemplate(Type graphDirectiveType) - : base(graphDirectiveType) + /// The attribute provider that will supply the attributes needed to parse + /// and configure this template. is used if this parameter + /// is not supplied. + public GraphDirectiveTemplate(Type graphDirectiveType, ICustomAttributeProvider attributeProvider = null) + : base(attributeProvider ?? graphDirectiveType) { Validation.ThrowIfNotCastable(graphDirectiveType, nameof(graphDirectiveType)); @@ -67,14 +70,14 @@ protected override void ParseTemplateDefinition() this.Description = this.AttributeProvider.SingleAttributeOrDefault()?.Description; this.IsRepeatable = this.AttributeProvider.SingleAttributeOrDefault() != null; - var routeName = GraphTypeNames.ParseName(this.ObjectType, TypeKind.DIRECTIVE); + var routeName = this.DetermineDirectiveName(); this.Route = new SchemaItemPath(SchemaItemPath.Join(SchemaItemCollections.Directives, routeName)); - - foreach (var methodInfo in this.ObjectType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + var potentialMethods = this.GatherPossibleDirectiveExecutionMethods(); + foreach (var methodData in potentialMethods) { - if (methodInfo.FirstAttributeOfTypeOrDefault() != null) + if (this.CouldBeDirectiveExecutionMethod(methodData)) { - var methodTemplate = new GraphDirectiveMethodTemplate(this, methodInfo); + var methodTemplate = new GraphDirectiveMethodTemplate(this, methodData.MemberInfo as MethodInfo, methodData.AttributeProvider); methodTemplate.Parse(); this.Methods.RegisterMethod(methodTemplate); } @@ -83,10 +86,45 @@ protected override void ParseTemplateDefinition() _securityPolicies = AppliedSecurityPolicyGroup.FromAttributeCollection(this.AttributeProvider); } + /// + /// Determines the name of the directive. (e.g. @name). + /// + /// System.String. + protected virtual string DetermineDirectiveName() + { + return GraphTypeNames.ParseName(this.ObjectType, TypeKind.DIRECTIVE); + } + + /// + /// Inspects the templated object and gathers all the methods with the right attribute declarations such that + /// they should be come execution methods on the directive instance. + /// + /// IEnumerable<IMemberInfoProvider>. + protected virtual IEnumerable GatherPossibleDirectiveExecutionMethods() + { + return this.ObjectType + .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Select(x => new MemberInfoProvider(x)); + } + + /// + /// Inspects the member data and determines if it can be successfully parsed into a directive action method + /// for thi template. + /// + /// The member information to inspect. + /// true if the member data can be parsed, false otherwise. + protected virtual bool CouldBeDirectiveExecutionMethod(IMemberInfoProvider memberInfo) + { + return memberInfo?.MemberInfo is MethodInfo && + memberInfo + .AttributeProvider + .FirstAttributeOfTypeOrDefault() != null; + } + /// - public IGraphFieldResolverMethod FindMethod(DirectiveLocation location) + public IGraphFieldResolverMetaData FindMetaData(DirectiveLocation location) { - return this.Methods.FindMethod(location); + return this.Methods.FindMetaData(location); } /// @@ -97,14 +135,14 @@ public override void ValidateOrThrow(bool validateChildren = true) if (this.Locations == DirectiveLocation.NONE) { throw new GraphTypeDeclarationException( - $"The directive '{this.InternalFullName}' defines no locations to which it can be applied. You must specify at least " + + $"The directive '{this.InternalName}' defines no locations to which it can be applied. You must specify at least " + $"one '{typeof(DirectiveLocation)}' via the {typeof(DirectiveLocationsAttribute).FriendlyName()}."); } if (this.AppliedDirectives.Any()) { throw new GraphTypeDeclarationException( - $"The directive {this.InternalFullName} defines an {nameof(ApplyDirectiveAttribute)}. " + + $"The directive {this.InternalName} defines an {nameof(ApplyDirectiveAttribute)}. " + $"Directives cannot have applied directives."); } @@ -115,7 +153,8 @@ public override void ValidateOrThrow(bool validateChildren = true) /// public IGraphDirectiveResolver CreateResolver() { - return new GraphDirectiveActionResolver(this); + var allMetadata = this.Methods.CreateMetadataCollection(); + return new GraphDirectiveActionResolver(allMetadata); } /// @@ -133,12 +172,6 @@ public IGraphDirectiveResolver CreateResolver() /// The type of the declared. public Type DeclaredType => this.ObjectType; - /// - public override string InternalFullName => this.ObjectType?.FriendlyName(true); - - /// - public override string InternalName => this.ObjectType?.FriendlyName(); - /// public override bool IsExplicitDeclaration => true; diff --git a/src/graphql-aspnet/Internal/TypeTemplates/GraphFieldTemplateBase.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphFieldTemplateBase.cs similarity index 55% rename from src/graphql-aspnet/Internal/TypeTemplates/GraphFieldTemplateBase.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphFieldTemplateBase.cs index 76011b2fd..b489abaf1 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/GraphFieldTemplateBase.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphFieldTemplateBase.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; @@ -24,7 +24,6 @@ namespace GraphQL.AspNet.Internal.TypeTemplates using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Security; @@ -37,6 +36,8 @@ public abstract class GraphFieldTemplateBase : SchemaItemTemplateBase, IGraphFie private AppliedSecurityPolicyGroup _securityPolicies; private GraphFieldAttribute _fieldDeclaration; private bool _invalidTypeExpression; + private bool _returnsActionResult; + private bool _duplicateUnionDeclarationDetected; /// /// Initializes a new instance of the class. @@ -49,6 +50,7 @@ protected GraphFieldTemplateBase(IGraphTypeTemplate parent, ICustomAttributeProv { this.Parent = Validation.ThrowIfNullOrReturn(parent, nameof(parent)); _securityPolicies = AppliedSecurityPolicyGroup.Empty; + this.PossibleObjectTypes = new List(); } /// @@ -56,16 +58,44 @@ protected override void ParseTemplateDefinition() { base.ParseTemplateDefinition(); + Type StripTasks(Type type) + { + return GraphValidation.EliminateWrappersFromCoreType( + type, + eliminateEnumerables: false, + eliminateTask: true, + eliminateNullableT: false); + } + _fieldDeclaration = this.AttributeProvider.SingleAttributeOfTypeOrDefault(); // ------------------------------------ - // Common Metadata + // Build up a list of possible return types and, if applicable, + // position the declared return type of the resolver to be at the 0th index. + // ------------------------------------ + var potentialReturnTypes = this.GatherAllPossibleReturnedDataTypes() + .Select(x => StripTasks(x)) + .ToList(); + + // extract type info from the return type of the field + // ensure its listed first if it needs to be + var rootDeclaredType = StripTasks(this.DeclaredReturnType); + if (!Validation.IsCastable(rootDeclaredType)) + potentialReturnTypes.Insert(0, rootDeclaredType); + + // remove duplicates and trim to only valid types + potentialReturnTypes = potentialReturnTypes + .Distinct() + .Where(x => GraphValidation.IsValidGraphType(x)) + .ToList(); + + // ------------------------------------ + // Extract Common Metadata // ------------------------------------ this.Route = this.GenerateFieldPath(); this.Mode = _fieldDeclaration?.ExecutionMode ?? FieldResolutionMode.PerSourceItem; this.Complexity = _fieldDeclaration?.Complexity; this.Description = this.AttributeProvider.SingleAttributeOfTypeOrDefault()?.Description; - if (_fieldDeclaration?.TypeExpression == null) { this.DeclaredTypeWrappers = null; @@ -74,98 +104,71 @@ protected override void ParseTemplateDefinition() { var expression = GraphTypeExpression.FromDeclaration(_fieldDeclaration.TypeExpression); if (!expression.IsValid) - { _invalidTypeExpression = true; - } else - { this.DeclaredTypeWrappers = expression.Wrappers; - } } - var objectType = GraphValidation.EliminateWrappersFromCoreType(this.DeclaredReturnType); - var typeExpression = GraphTypeExpression.FromType(this.DeclaredReturnType, this.DeclaredTypeWrappers); - typeExpression = typeExpression.CloneTo(GraphTypeNames.ParseName(objectType, this.Parent.Kind)); - // ------------------------------------ - // Gather Possible Types and/or union definition + // Build out the union definition if one was supplied // ------------------------------------ - this.BuildUnionProxyInstance(); - if (this.UnionProxy != null) - { - this.PossibleTypes = new List(this.UnionProxy.Types); - } - else - { - this.PossibleTypes = new List(); - - // the possible types attribte is optional but expects that the concrete types are added - // to the schema else where lest a runtime exception occurs of a missing graph type. - var typesAttrib = this.AttributeProvider.SingleAttributeOfTypeOrDefault(); - if (typesAttrib != null) - { - foreach (var type in typesAttrib.PossibleTypes) - this.PossibleTypes.Add(type); - } - - // add any types declared on the primary field declaration - if (_fieldDeclaration != null) - { - foreach (var type in _fieldDeclaration.Types) - { - var strippedType = GraphValidation.EliminateWrappersFromCoreType(type); - if (strippedType != null) - { - this.PossibleTypes.Add(strippedType); - } - } - } - - if (objectType != null && !Validation.IsCastable(objectType) && GraphValidation.IsValidGraphType(objectType)) - { - this.PossibleTypes.Insert(0, objectType); - } - } - - this.PossibleTypes = this.PossibleTypes.Distinct().ToList(); + this.UnionProxy = this.BuildUnionProxyInstance(potentialReturnTypes); // ------------------------------------ - // Adjust the action result to the actual return type this field returns + // Calculate the correct type expression and expected return type // ------------------------------------ + // + // first calculate the initial expression from what is returned by the resolver + // based on the resolver's declarations + var objectType = GraphValidation.EliminateWrappersFromCoreType(this.DeclaredReturnType); + var typeExpression = GraphTypeExpression + .FromType(this.DeclaredReturnType, this.DeclaredTypeWrappers) + .CloneTo(Constants.Other.DEFAULT_TYPE_EXPRESSION_TYPE_NAME); + + // adjust the object type and type expression + // if this field returns an action result if (Validation.IsCastable(objectType)) { + _returnsActionResult = true; if (this.UnionProxy != null) { - // if a union was decalred preserve whatever modifer elements - // were decalred but alter the return type to object (a known common element among all members of the union) + // if a union was declared preserve whatever modifer elements + // were declared but alter the return type to "object" + // (a known common element among all members of the union) objectType = typeof(object); + + // clear out the "possible types" that could be returned and + // limit this field to just those of the union (they are already part of the union anyways) + potentialReturnTypes.Clear(); + potentialReturnTypes.AddRange(this.UnionProxy.Types); } - else if (_fieldDeclaration != null && _fieldDeclaration.Types.Count > 0) - { - objectType = _fieldDeclaration.Types[0]; - typeExpression = GraphTypeExpression.FromType(objectType, this.DeclaredTypeWrappers) - .CloneTo(GraphTypeNames.ParseName(objectType, this.Parent.Kind)); - objectType = GraphValidation.EliminateWrappersFromCoreType(objectType); - } - else if (this.PossibleTypes.Count > 0) + else if (potentialReturnTypes.Count > 0) { - objectType = this.PossibleTypes[0]; - typeExpression = GraphTypeExpression.FromType(objectType, this.DeclaredTypeWrappers) - .CloneTo(GraphTypeNames.ParseName(objectType, this.Parent.Kind)); + // the first type in the list will be the primary return type + // when this field is not returning a union + // extract its type expression AND object type to be the primary for the field + objectType = potentialReturnTypes[0]; + typeExpression = GraphTypeExpression + .FromType(objectType, this.DeclaredTypeWrappers) + .CloneTo(Constants.Other.DEFAULT_TYPE_EXPRESSION_TYPE_NAME); + objectType = GraphValidation.EliminateWrappersFromCoreType(objectType); } else { - objectType = typeof(object); + // ths is an error state that will be picked up during validation + objectType = null; } } this.ObjectType = objectType; + this.TypeExpression = typeExpression; - if (this.UnionProxy != null) - this.TypeExpression = typeExpression.CloneTo(this.UnionProxy.Name); - else - this.TypeExpression = typeExpression; + // done with type expression, set the final list of potential object types + // to the core types of each return type. + this.PossibleObjectTypes = potentialReturnTypes + .Select(x => GraphValidation.EliminateWrappersFromCoreType(x)) + .ToList(); // ------------------------------------ // Async Requirements @@ -186,13 +189,13 @@ public override void ValidateOrThrow(bool validateChildren = true) if (_invalidTypeExpression) { throw new GraphTypeDeclarationException( - $"The field '{this.InternalFullName}' defines an invalid {nameof(GraphFieldAttribute.TypeExpression)} (Value = '{_fieldDeclaration.TypeExpression}'). " + + $"The field '{this.InternalName}' defines an invalid {nameof(GraphFieldAttribute.TypeExpression)} (Value = '{_fieldDeclaration.TypeExpression}'). " + $"The provided type expression must be a valid query language type expression or null."); } if (this.DeclaredReturnType == typeof(void)) { - throw new GraphTypeDeclarationException($"The graph field '{this.InternalFullName}' has a void return. All graph fields must return something."); + throw new GraphTypeDeclarationException($"The graph field '{this.InternalName}' has a void return. All graph fields must return something."); } if (this.IsAsyncField) @@ -202,23 +205,49 @@ public override void ValidateOrThrow(bool validateChildren = true) if (genericArgs.Length != 1) { throw new GraphTypeDeclarationException( - $"The field '{this.InternalFullName}' defines a return type of'{typeof(Task).Name}' but " + + $"The field '{this.InternalName}' defines a return type of'{typeof(Task).Name}' but " + "defines no contained return type for the resultant model object yielding a void return after " + "completion of the task. All graph methods must return a single model object. Consider using " + $"'{typeof(Task<>).Name}' instead for asyncronous methods"); } } + if (_duplicateUnionDeclarationDetected) + { + throw new GraphTypeDeclarationException( + $"The field '{this.InternalName}' attempted to define a union multiple times. A union can only be " + + $"defined once per field. Double check the field's applied attributes."); + } + if (this.UnionProxy != null) { - GraphValidation.EnsureGraphNameOrThrow($"{this.InternalFullName}[{nameof(GraphFieldAttribute)}][{nameof(IGraphUnionProxy)}]", this.UnionProxy.Name); + GraphValidation.EnsureGraphNameOrThrow($"{this.InternalName}[{nameof(GraphFieldAttribute)}][{nameof(IGraphUnionProxy)}]", this.UnionProxy.Name); if (this.UnionProxy.Types.Count < 1) { throw new GraphTypeDeclarationException( - $"The field '{this.InternalFullName}' declares union type of '{this.UnionProxy.Name}' " + + $"The field '{this.InternalName}' declares union type of '{this.UnionProxy.Name}' " + "but that type includes 0 possible types in the union. Unions require 1 or more possible types. Add additional types" + "or remove the union."); } + + // union methods MUST return a graph action result + var unwrappedReturnType = GraphValidation.EliminateWrappersFromCoreType(this.DeclaredReturnType); + if (!Validation.IsCastable(unwrappedReturnType)) + { + throw new GraphTypeDeclarationException( + $"The field '{this.InternalName}' declares union type of '{this.UnionProxy.Name}'. " + + $"A fields returning a union must return a {nameof(IGraphActionResult)} from the method or property resolver."); + } + } + else if (this.ObjectType == null || this.ObjectType == typeof(object)) + { + // this field is not a union but it also has not declared a proper return type. + // this can happen if the field returns a graph action result and does not declare a return type. + throw new GraphTypeDeclarationException( + $"The field '{this.InternalName}' declared no possible return types either as part of its specification or as the " + + "declared return type for the field. GraphQL requires the type information be known " + + $"to setup the schema and client tooling properly. If this field returns a '{nameof(IGraphActionResult)}' you must " + + "provide a graph field declaration attribute and add at least one type; be that a concrete type, an interface or a union."); } else { @@ -226,67 +255,53 @@ public override void ValidateOrThrow(bool validateChildren = true) if (!GraphValidation.IsValidGraphType(this.ObjectType)) { throw new GraphTypeDeclarationException( - $"The field '{this.InternalFullName}' defines a a return type of {this.ObjectType.FriendlyName()}, which is cannot a be used as a graph type. " + - $"Change the return type or skip the field and try again."); + $"The field '{this.InternalName}' defines a a return type of {this.ObjectType.FriendlyName()}, which is cannot a be used as a graph type. " + + $"Change the return type and try again."); } } - // ensure the object type returned by the graph field is set correctly - bool returnsActionResult = Validation.IsCastable(this.ObjectType); - var enforceUnionRules = this.UnionProxy != null; - - if (this.PossibleTypes.Count == 0) + // regardless of being a union or not there must always at least one possible return type + if (this.PossibleObjectTypes.Count == 0) { throw new GraphTypeDeclarationException( - $"The field '{this.InternalFullName}' declared no possible return types either on its attribute declarations or as the " + + $"The field '{this.InternalName}' declared no possible return types either as part of its specification or as the " + "declared return type for the field. GraphQL requires the type information be known " + $"to setup the schema and client tooling properly. If this field returns a '{nameof(IGraphActionResult)}' you must " + "provide a graph field declaration attribute and add at least one type; be that a concrete type, an interface or a union."); } // validate each type in the list for "correctness" - // Possible Types must conform to the rules of those required by sub type declarations of unions and interfaces + // Possible Types must conform to the rules of those required by sub-type declarations of unions and interfaces // interfaces: https://graphql.github.io/graphql-spec/October2021/#sec-Interfaces // unions: https://graphql.github.io/graphql-spec/October2021/#sec-Unions - foreach (var type in this.PossibleTypes) + var enforceUnionRules = this.UnionProxy != null; + foreach (var type in this.PossibleObjectTypes) { if (enforceUnionRules) { - if (GraphQLProviders.ScalarProvider.IsScalar(type)) + if (GraphValidation.MustBeLeafType(type)) { throw new GraphTypeDeclarationException( - $"The field '{this.InternalFullName}' declares union with a possible type of '{type.FriendlyName()}' " + - "but that type is a scalar. Scalars cannot be included in a field's possible type collection, only object types can."); + $"The field '{this.InternalName}' declares union with a possible type of '{type.FriendlyName()}' " + + "but that type is a leaf value (i.e. a defined scalar or enum). Scalars and enums cannot be included in a field's possible type collection, only object types can."); } if (type.IsInterface) { throw new GraphTypeDeclarationException( - $"The field '{this.InternalFullName}' declares union with a possible type of '{type.FriendlyName()}' " + + $"The field '{this.InternalName}' declares union with a possible type of '{type.FriendlyName()}' " + "but that type is an interface. Interfaces cannot be included in a field's possible type collection, only object types can."); } - - if (type.IsEnum) - { - throw new GraphTypeDeclarationException( - $"The field '{this.InternalFullName}' declares a union with a possible type of '{type.FriendlyName()}' " + - "but that type is an enum. Only concrete, non-abstract classes may be used. Value types, such as structs or enumerations, are not allowed."); - } - - if (!type.IsClass) - { - throw new GraphTypeDeclarationException( - $"The field '{this.InternalFullName}' returns an interface named '{this.ObjectType.FriendlyName()}' and declares a possible type of '{type.FriendlyName()}' " + - "but that type is not a valid class. Only concrete, non-abstract classes may be used. Value types, such as structs or enumerations, are also not allowed."); - } } + // the possible types returned by this field must never include any of the pre-defined + // invalid types foreach (var invalidFieldType in Constants.InvalidFieldTemplateTypes) { if (Validation.IsCastable(type, invalidFieldType)) { throw new GraphTypeDeclarationException( - $"The field '{this.InternalFullName}' declares a possible return type of '{type.FriendlyName()}' " + + $"The field '{this.InternalName}' declares a possible return type of '{type.FriendlyName()}' " + $"but that type inherits from '{invalidFieldType.FriendlyName()}' which is a reserved type declared by the graphql-aspnet library. This type cannot cannot be returned by a graphql field."); } } @@ -294,16 +309,17 @@ public override void ValidateOrThrow(bool validateChildren = true) // to ensure an object isn't arbitrarly returned as null and lost // ensure that the any possible type returned from this field is returnable AS the type this field declares // as its return type. In doing this we know that, potentially, an object returned by this - // field "could" cast to the return type and allow field execution to continue. + // field "could" cast itself to the expected return type and allow field execution to continue. // // This is a helpful developer safety check, not a complete guarantee as concrete types for interface - // declarations are not required at this stage + // declarations are not required at this stage. // - // batch processed fields are not subject to this restriction - if (!returnsActionResult && this.Mode == FieldResolutionMode.PerSourceItem && !Validation.IsCastable(type, this.ObjectType)) + // batch processed fields and those that return a IGrahpActionResult + // are not subject to this restriction or check + if (!_returnsActionResult && this.Mode == FieldResolutionMode.PerSourceItem && !Validation.IsCastable(type, this.ObjectType)) { throw new GraphTypeDeclarationException( - $"The field '{this.InternalFullName}' returns '{this.ObjectType.FriendlyName()}' and declares a possible type of '{type.FriendlyName()}' " + + $"The field '{this.InternalName}' returns '{this.ObjectType.FriendlyName()}' and declares a possible type of '{type.FriendlyName()}' " + $"but that type is not castable to '{this.ObjectType.FriendlyName()}' and therefore not returnable by this field. Due to the strongly-typed nature of C# any possible type on a field " + "must be castable to the type of the field in order to ensure its not inadvertantly nulled out during processing. If this field returns a union " + $"of multiple, disperate types consider returning '{typeof(object).Name}' from the field to ensure each possible return type can be successfully processed."); @@ -320,7 +336,7 @@ public override void ValidateOrThrow(bool validateChildren = true) if (this.Complexity.HasValue && this.Complexity < 0) { throw new GraphTypeDeclarationException( - $"The field '{this.InternalFullName}' declares a complexity value of " + + $"The field '{this.InternalName}' declares a complexity value of " + $"`{this.Complexity.Value}`. The complexity factor must be greater than or equal to 0."); } @@ -350,13 +366,13 @@ private void ValidateBatchMethodSignatureOrThrow() if (this.Arguments.All(arg => arg.DeclaredArgumentType != requiredEnumerable)) { throw new GraphTypeDeclarationException( - $"Invalid batch method signature. The field '{this.InternalFullName}' declares itself as batch method but does not accept a batch " + + $"Invalid batch method signature. The field '{this.InternalName}' declares itself as batch method but does not accept a batch " + $"of data as an input parameter. This method must accept a parameter of type '{requiredEnumerable.FriendlyName()}' somewhere in its method signature to " + $"be used as a batch extension for the type '{this.SourceObjectType.FriendlyName()}'."); } var declaredType = GraphValidation.EliminateWrappersFromCoreType(this.DeclaredReturnType); - if (declaredType == typeof(IGraphActionResult)) + if (Validation.IsCastable(declaredType)) return; // when a batch method doesn't return an action result, indicating the developer @@ -368,7 +384,7 @@ private void ValidateBatchMethodSignatureOrThrow() if (!BatchResultProcessor.IsBatchDictionaryType(declaredType, this.SourceObjectType, this.ObjectType)) { throw new GraphTypeDeclarationException( - $"Invalid batch method signature. The field '{this.InternalFullName}' declares a return type of '{declaredType.FriendlyName()}', however; " + + $"Invalid batch method signature. The field '{this.InternalName}' declares a return type of '{declaredType.FriendlyName()}', however; " + $"batch methods must return either an '{typeof(IGraphActionResult).FriendlyName()}' or a dictionary keyed " + "on the provided source data (e.g. 'IDictionary')."); } @@ -378,12 +394,14 @@ private void ValidateBatchMethodSignatureOrThrow() // each member of the union must be castable to 'K' in order for the runtime to properly seperate // and process the batch results var dictionaryValue = GraphValidation.EliminateWrappersFromCoreType(declaredType.GetValueTypeOfDictionary()); - foreach (var type in this.PossibleTypes) + foreach (var type in this.PossibleObjectTypes) { + var s = dictionaryValue.FriendlyName(); + var t = type.FriendlyName(); if (!Validation.IsCastable(type, dictionaryValue)) { throw new GraphTypeDeclarationException( - $"The field '{this.InternalFullName}' returns '{this.ObjectType.FriendlyName()}' and declares a possible type of '{type.FriendlyName()}' " + + $"The field '{this.InternalName}' returns '{this.ObjectType.FriendlyName()}' and declares a possible type of '{type.FriendlyName()}' " + $"but that type is not castable to '{this.ObjectType.FriendlyName()}' and therefore not returnable by this field. Due to the strongly-typed nature of C# any possible type on a field " + "must be castable to the type of the field in order to ensure its not inadvertantly nulled out during processing. If this field returns a union " + $"of multiple, disperate types consider returning '{typeof(object).Name}' from the field to ensure each possible return type can be successfully processed."); @@ -394,15 +412,18 @@ private void ValidateBatchMethodSignatureOrThrow() /// public abstract IGraphFieldResolver CreateResolver(); + /// + public abstract IGraphFieldResolverMetaData CreateResolverMetaData(); + /// public override IEnumerable RetrieveRequiredTypes() { var list = new List(); list.AddRange(base.RetrieveRequiredTypes()); - if (this.PossibleTypes != null) + if (this.PossibleObjectTypes != null) { - var dependentTypes = this.PossibleTypes + var dependentTypes = this.PossibleObjectTypes .Select(x => new DependentType(x, GraphValidation.ResolveTypeKind(x, this.OwnerTypeKind))); list.AddRange(dependentTypes); } @@ -417,31 +438,101 @@ public override IEnumerable RetrieveRequiredTypes() } /// - /// Retrieves proxy instance defined on this attribute that is used to generate the metadata. + /// Gathers a list, of all possible data types returned by this field. This list should be unfiltered and + /// and contain any decorations as they are declared. Do NOT remove wrappers such as Task{T}, IEnumerable{T} or + /// Nullable{T}. /// - private void BuildUnionProxyInstance() + /// IEnumerable<Type>. + protected virtual IEnumerable GatherAllPossibleReturnedDataTypes() { + // extract types from [GraphField] var fieldAttribute = this.AttributeProvider.SingleAttributeOfTypeOrDefault(); - if (fieldAttribute == null) - return; + if (fieldAttribute?.Types != null) + { + foreach (var type in fieldAttribute.Types) + yield return type; + } - IGraphUnionProxy proxy = null; + // extract types from [Union] + var unionAttribute = this.AttributeProvider.SingleAttributeOfTypeOrDefault(); + if (unionAttribute != null) + { + if (unionAttribute.UnionMemberTypes != null) + { + foreach (var type in unionAttribute.UnionMemberTypes) + yield return type; + } + } + + // extract types from [PossibleTypes] + var possibleTypesAttribute = this.AttributeProvider.SingleAttributeOfTypeOrDefault(); + if (possibleTypesAttribute?.PossibleTypes != null) + { + foreach (var type in possibleTypesAttribute.PossibleTypes) + yield return type; + } + } + + /// + /// Attempts to create a union proxy instance from all the attribute declarations on this field. + /// This proxy will be used to create a union graph type for the field on a schema. If this field does + /// not return a union this method should return null. + /// + /// IGraphUnionProxy. + private IGraphUnionProxy BuildUnionProxyInstance(List potentialReturnTypes) + { + string unionTypeName = null; + var unionDeclaredOnFieldAttribute = false; + var unionAttributeDeclared = false; + + // extract union name from [GraphField] + var fieldAttribute = this.AttributeProvider.SingleAttributeOfTypeOrDefault(); + if (fieldAttribute != null) + { + unionTypeName = fieldAttribute.UnionTypeName; + unionDeclaredOnFieldAttribute = fieldAttribute.UnionTypeName != null; + } - if (fieldAttribute.Types.Count == 1) + // extract union name from [Union] + var unionAttribute = this.AttributeProvider.SingleAttributeOfTypeOrDefault(); + if (unionAttribute != null) { - var proxyType = fieldAttribute.Types.FirstOrDefault(); - if (proxyType != null) - proxy = GraphQLProviders.GraphTypeMakerProvider.CreateUnionProxyFromType(proxyType); + unionAttributeDeclared = true; + unionTypeName = unionAttribute.UnionName; } + // while there are multiple ways to declare a union, each field + // can only accept 1. + if (unionDeclaredOnFieldAttribute && unionAttributeDeclared) + { + _duplicateUnionDeclarationDetected = true; + return null; + } + + // if the only type declared is a reference to a union proxy instnatiate it and use its + // definition for the union + Type unionProxyType = null; + if (potentialReturnTypes.Count == 1 && Validation.IsCastable(potentialReturnTypes[0])) + unionProxyType = potentialReturnTypes[0]; + + IGraphUnionProxy proxy = null; + if (unionProxyType != null) + proxy = GlobalTypes.CreateUnionProxyFromType(unionProxyType); + // when no proxy type is declared attempt to construct the proxy from types supplied // if and only if a name was supplied for the union - if (proxy == null && !string.IsNullOrWhiteSpace(fieldAttribute.UnionTypeName)) + // + // if it happens that two or more union proxies were declared + // then validation will pick up the issue as a union proxy is not a valid return type + if (proxy == null && !string.IsNullOrWhiteSpace(unionTypeName)) { - proxy = new GraphUnionProxy(fieldAttribute.UnionTypeName, fieldAttribute.Types); + proxy = new GraphUnionProxy( + unionTypeName, + unionTypeName, + potentialReturnTypes); } - this.UnionProxy = proxy; + return proxy; } /// @@ -483,16 +574,18 @@ private void BuildUnionProxyInstance() /// public virtual IGraphUnionProxy UnionProxy { get; protected set; } - /// - /// Gets the possible types that can be returned by this field. - /// - /// The possible types. - protected List PossibleTypes { get; private set; } - /// public MetaGraphTypes[] DeclaredTypeWrappers { get; private set; } /// public float? Complexity { get; set; } + + /// + /// Gets the possible concrete data types that can be returned by this field. + /// This list represents the core .NET types that will represent the various graph types. It should not include + /// decorators such as IEnumerable{t} or Nullable{T}. + /// + /// The potential types of data returnable by this instance. + protected List PossibleObjectTypes { get; private set; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/TypeTemplates/GraphFieldTemplateSource.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphFieldTemplateSource.cs similarity index 95% rename from src/graphql-aspnet/Internal/TypeTemplates/GraphFieldTemplateSource.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphFieldTemplateSource.cs index 2f8f9c84c..10ceb701c 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/GraphFieldTemplateSource.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphFieldTemplateSource.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; diff --git a/src/graphql-aspnet/Internal/TypeTemplates/GraphTypeExtensionFieldTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphTypeExtensionFieldTemplate.cs similarity index 59% rename from src/graphql-aspnet/Internal/TypeTemplates/GraphTypeExtensionFieldTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphTypeExtensionFieldTemplate.cs index 96544e01a..00a402dd0 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/GraphTypeExtensionFieldTemplate.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphTypeExtensionFieldTemplate.cs @@ -7,19 +7,21 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; + using System.Collections.Generic; + using System.Linq; using System.Reflection; using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal.Resolvers; - using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; @@ -42,6 +44,18 @@ public GraphTypeExtensionFieldTemplate(IGraphTypeTemplate parent, MethodInfo met { } + /// + /// Initializes a new instance of the class. + /// + /// The parent object template that owns this method. + /// The method information. + /// A custom, external attribute provider to use instead for extracting + /// configuration attributes instead of the provider on . + public GraphTypeExtensionFieldTemplate(IGraphTypeTemplate parent, MethodInfo methodInfo, ICustomAttributeProvider attributeProvider) + : base(parent, methodInfo, attributeProvider) + { + } + /// /// Builds out this field template parsing out the required type declarations unions, nameing scheme etc. This method should be /// overridden in any child classes for additional decalration requirements. @@ -58,25 +72,29 @@ protected override void ParseTemplateDefinition() base.ParseTemplateDefinition(); + // rebuild the returned object type and the type expression of the field + // to account for the extra values required when dealing with an explictly + // declared batch extension var returnType = GraphValidation.EliminateWrappersFromCoreType(this.DeclaredReturnType); - if (returnType != typeof(IGraphActionResult)) + if (!Validation.IsCastable(returnType) && _typeAttrib.ExecutionMode == FieldResolutionMode.Batch) { // inspect the return type, if its a valid dictionary extract the return type from the value // and set the type modifiers and method type based on the value of each dictionary entry - if (_typeAttrib.ExecutionMode == FieldResolutionMode.Batch) - { - returnType = returnType.GetValueTypeOfDictionary(); - this.ObjectType = GraphValidation.EliminateWrappersFromCoreType(returnType); - this.TypeExpression = GraphTypeExpression.FromType(returnType, this.DeclaredTypeWrappers); - this.PossibleTypes.Insert(0, this.ObjectType); - } + returnType = returnType.GetValueTypeOfDictionary(); + var wrappers = GraphTypeExpression.FromType(returnType).Wrappers; + this.ObjectType = GraphValidation.EliminateWrappersFromCoreType(returnType); + this.TypeExpression = GraphTypeExpression + .FromType(returnType, this.DeclaredTypeWrappers ?? wrappers) + .CloneTo(Constants.Other.DEFAULT_TYPE_EXPRESSION_TYPE_NAME); + + this.PossibleObjectTypes.Insert(0, this.ObjectType); } } /// public override IGraphFieldResolver CreateResolver() { - return new GraphControllerActionResolver(this); + return new GraphControllerActionResolver(this.CreateResolverMetaData()); } /// @@ -86,15 +104,25 @@ public override void ValidateOrThrow(bool validateChildren = true) { // should be an impossible situation but just in case someone manually invokes this template bypassing global checks. throw new GraphTypeDeclarationException( - $"The type extension '{this.InternalFullName}' does not define a {typeof(TypeExtensionAttribute).FriendlyName()} or defines more than one instance. " + + $"The type extension '{this.InternalName}' does not define a {typeof(TypeExtensionAttribute).FriendlyName()} or defines more than one instance. " + "All methods wishing to be treated as type extensions must define one instance of this attribute to properly configure the runtime."); } - if (GraphQLProviders.ScalarProvider.IsLeaf(this.SourceObjectType)) + if (!this.SourceObjectType.IsClass && !this.SourceObjectType.IsStruct() && !this.SourceObjectType.IsInterface) + { + throw new GraphTypeDeclarationException( + $"The type extension '{this.InternalName}' is attempting to extend '{this.SourceObjectType.FriendlyName()}'. " + + "Only classes, structs and interfaces can be extended."); + } + + // a specialized redeclaration of this rule on the type extension to + // better contextualize the message to be just the template value + if (this.Route == null || !this.Route.IsValid) { throw new GraphTypeDeclarationException( - $"The type extension '{this.InternalFullName}' is attempting to extend '{this.SourceObjectType.FriendlyName()}' which is a leaf type ({nameof(TypeKind.SCALAR)}, {nameof(TypeKind.ENUM)}). " + - "Leaf types cannot be extended."); + $"The type extension '{this.InternalName}' declares an invalid field name of '{_typeAttrib.Template ?? ""}'. " + + $"Each segment of the route must conform to standard graphql naming rules. (Regex: {Constants.RegExPatterns.NameRegex} )", + this.ObjectType); } base.ValidateOrThrow(validateChildren); diff --git a/src/graphql-aspnet/Internal/TypeTemplates/GraphTypeTemplateBase.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphTypeTemplateBase.cs similarity index 77% rename from src/graphql-aspnet/Internal/TypeTemplates/GraphTypeTemplateBase.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphTypeTemplateBase.cs index 57b53a905..f50c8aef7 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/GraphTypeTemplateBase.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphTypeTemplateBase.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System.Reflection; using GraphQL.AspNet.Attributes; @@ -34,9 +34,7 @@ protected GraphTypeTemplateBase(ICustomAttributeProvider attributeProvider) this.Publish = true; } - /// - /// When overridden in a child class this method builds out the template according to its own individual requirements. - /// + /// protected override void ParseTemplateDefinition() { base.ParseTemplateDefinition(); @@ -46,11 +44,17 @@ protected override void ParseTemplateDefinition() if (graphTypeDeclaration != null) { this.Publish = graphTypeDeclaration.Publish; + + if (string.IsNullOrEmpty(this.InternalName)) + this.InternalName = graphTypeDeclaration.InternalName; if (graphTypeDeclaration.RequirementsWereDeclared) { _fieldDeclarationOverrides = graphTypeDeclaration.FieldDeclarationRequirements; } } + + if (string.IsNullOrWhiteSpace(this.InternalName)) + this.InternalName = this.ObjectType?.FriendlyName(); } /// @@ -60,9 +64,12 @@ protected override void ParseTemplateDefinition() public abstract TypeKind Kind { get; } /// - public TemplateDeclarationRequirements? DeclarationRequirements => _fieldDeclarationOverrides; + public virtual TemplateDeclarationRequirements? DeclarationRequirements => _fieldDeclarationOverrides; + + /// + public virtual bool Publish { get; private set; } /// - public bool Publish { get; private set; } + public virtual ItemSource TemplateSource => ItemSource.DesignTime; } } \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphTypeTemplates.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphTypeTemplates.cs new file mode 100644 index 000000000..4d1045700 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphTypeTemplates.cs @@ -0,0 +1,67 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates +{ + using System; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Directives; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + + /// + /// A helper class for creating graph type templates from info classes. + /// + public static class GraphTypeTemplates + { + /// + /// A default implementation that will create an appropriate + /// for the given and . + /// + /// + /// WARNING: This method does not parse or validate the template, it just instantiates an appropriate template instance + /// for the given . + /// + /// The type info representing the object. + /// The kind of template to make. Only used to differentiate INPUT_OBJECT from + /// OBJECT. Ignored otherwise. + /// IGraphTypeTemplate. + public static IGraphTypeTemplate CreateTemplate(Type objectType, TypeKind? kind = null) + { + if (objectType == null) + return null; + + // attempt to turn "int" into "IntScalarType" when necessary + objectType = GlobalTypes.FindBuiltInScalarType(objectType) ?? objectType; + + IGraphTypeTemplate template; + if (Validation.IsCastable(objectType)) + template = new ScalarGraphTypeTemplate(objectType); + else if (Validation.IsCastable(objectType)) + template = new UnionGraphTypeTemplate(objectType); + else if (objectType.IsEnum) + template = new EnumGraphTypeTemplate(objectType); + else if (objectType.IsInterface) + template = new InterfaceGraphTypeTemplate(objectType); + else if (Validation.IsCastable(objectType)) + template = new GraphDirectiveTemplate(objectType); + else if (Validation.IsCastable(objectType)) + template = new GraphControllerTemplate(objectType); + else if (kind.HasValue && kind.Value == TypeKind.INPUT_OBJECT) + template = new InputObjectGraphTypeTemplate(objectType); + else + template = new ObjectGraphTypeTemplate(objectType); + + return template; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/TypeTemplates/InputGraphFieldTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/InputGraphFieldTemplate.cs similarity index 83% rename from src/graphql-aspnet/Internal/TypeTemplates/InputGraphFieldTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/InputGraphFieldTemplate.cs index c96028930..eb9d01e8d 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/InputGraphFieldTemplate.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/InputGraphFieldTemplate.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; @@ -23,7 +23,6 @@ namespace GraphQL.AspNet.Internal.TypeTemplates using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; @@ -53,19 +52,24 @@ protected override void ParseTemplateDefinition() { base.ParseTemplateDefinition(); + var objectType = GraphValidation.EliminateWrappersFromCoreType(this.DeclaredReturnType); + this.ObjectType = objectType; + _fieldDeclaration = this.AttributeProvider.SingleAttributeOfTypeOrDefault(); this.Route = this.GenerateFieldPath(); this.Description = this.AttributeProvider.SingleAttributeOfTypeOrDefault()?.Description; - var objectType = GraphValidation.EliminateWrappersFromCoreType(this.DeclaredReturnType); - this.ObjectType = objectType; - var typeExpression = GraphTypeExpression.FromType(this.DeclaredReturnType, this.DeclaredTypeWrappers); - typeExpression = typeExpression.CloneTo(GraphTypeNames.ParseName(objectType, this.Parent.Kind)); + typeExpression = typeExpression.CloneTo(Constants.Other.DEFAULT_TYPE_EXPRESSION_TYPE_NAME); this.IsRequired = this.AttributeProvider.SingleAttributeOrDefault() != null; this.TypeExpression = typeExpression; + + if (_fieldDeclaration != null) + this.InternalName = _fieldDeclaration.InternalName; + if (string.IsNullOrEmpty(this.InternalName)) + this.InternalName = $"{this.Parent.InternalName}.{this.Property.Name}"; } /// @@ -80,7 +84,7 @@ public override IEnumerable RetrieveRequiredTypes() private SchemaItemPath GenerateFieldPath() { - var graphName = this.AttributeProvider.SingleAttributeOfTypeOrDefault()?.Template?.Trim() ?? Constants.Routing.ACTION_METHOD_META_NAME; + var graphName = _fieldDeclaration?.Template?.Trim() ?? Constants.Routing.ACTION_METHOD_META_NAME; graphName = graphName.Replace(Constants.Routing.ACTION_METHOD_META_NAME, this.Property.Name).Trim(); return new SchemaItemPath(SchemaItemPath.Join(this.Parent.Route.Path, graphName)); @@ -94,28 +98,28 @@ public override void ValidateOrThrow(bool validateChildren = true) if (Validation.IsCastable(this.DeclaredReturnType)) { throw new GraphTypeDeclarationException( - $"The input field '{this.InternalFullName}' defines a return type of'{nameof(Task)}'. " + + $"The input field '{this.InternalName}' defines a return type of'{nameof(Task)}'. " + $"Input fields must not be asyncronous."); } if (this.ObjectType.IsInterface) { throw new GraphTypeDeclarationException( - $"The input field '{this.InternalFullName}' returns '{this.ObjectType.FriendlyName()}' which is an interface. " + + $"The input field '{this.InternalName}' returns '{this.ObjectType.FriendlyName()}' which is an interface. " + $"Input fields must not return interface objects."); } if (Validation.IsCastable(this.ObjectType)) { throw new GraphTypeDeclarationException( - $"The input field '{this.InternalFullName}' returns '{this.ObjectType.FriendlyName()}' which implements {nameof(IGraphUnionProxy)}. " + + $"The input field '{this.InternalName}' returns '{this.ObjectType.FriendlyName()}' which implements {nameof(IGraphUnionProxy)}. " + $"Input fields must not implement {nameof(IGraphUnionProxy)}."); } if (Validation.IsCastable(this.ObjectType)) { throw new GraphTypeDeclarationException( - $"The input field '{this.InternalFullName}' returns '{this.ObjectType.FriendlyName()}' which implements {nameof(IGraphActionResult)}. " + + $"The input field '{this.InternalName}' returns '{this.ObjectType.FriendlyName()}' which implements {nameof(IGraphActionResult)}. " + $"Input fields must not implement {nameof(IGraphActionResult)}."); } } @@ -144,12 +148,6 @@ public override void ValidateOrThrow(bool validateChildren = true) /// public TypeKind OwnerTypeKind => TypeKind.INPUT_OBJECT; - /// - public override string InternalFullName => $"{this.Parent.InternalFullName}.{this.Property.Name}"; - - /// - public override string InternalName => this.Property.Name; - /// public MetaGraphTypes[] DeclaredTypeWrappers { diff --git a/src/graphql-aspnet/Internal/TypeTemplates/InputObjectGraphTypeTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/InputObjectGraphTypeTemplate.cs similarity index 92% rename from src/graphql-aspnet/Internal/TypeTemplates/InputObjectGraphTypeTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/InputObjectGraphTypeTemplate.cs index 10c13c246..4594a4fca 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/InputObjectGraphTypeTemplate.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/InputObjectGraphTypeTemplate.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; @@ -52,9 +52,9 @@ public InputObjectGraphTypeTemplate(Type objectType) { rejectionReason = $"The type '{objectType.FriendlyName()}' is an enumeration and cannot be parsed as an {nameof(TypeKind.INPUT_OBJECT)} graph type. Use an {typeof(IEnumGraphType).FriendlyName()} instead."; } - else if (GraphQLProviders.ScalarProvider.IsScalar(objectType)) + else if (objectType.IsPrimitive) { - rejectionReason = $"The type '{objectType.FriendlyName()}' is a registered {nameof(TypeKind.SCALAR)} and cannot be parsed as an {nameof(TypeKind.INPUT_OBJECT)} graph type. Try using the scalar definition instead."; + rejectionReason = $"The type '{objectType.FriendlyName()}' is a primative data type and cannot be parsed as an {nameof(TypeKind.INPUT_OBJECT)} graph type."; } else if (objectType == typeof(string)) { @@ -188,9 +188,9 @@ public override void ValidateOrThrow(bool validateChildren = true) if (_invalidFields != null && _invalidFields.Count > 0) { - var fieldNames = string.Join("\n", _invalidFields.Select(x => $"Field: '{x.InternalFullName} ({x.Route.RootCollection.ToString()})'")); + var fieldNames = string.Join("\n", _invalidFields.Select(x => $"Field: '{x.InternalName} ({x.Route.RootCollection.ToString()})'")); throw new GraphTypeDeclarationException( - $"Invalid input field declaration. The type '{this.InternalFullName}' declares fields belonging to a graph collection not allowed given its context. This type can " + + $"Invalid input field declaration. The type '{this.InternalName}' declares fields belonging to a graph collection not allowed given its context. This type can " + $"only declare the following graph collections: '{string.Join(", ", this.AllowedGraphCollectionTypes.Select(x => x.ToString()))}'. " + $"If this field is declared on an object (not a controller) be sure to use '{nameof(GraphFieldAttribute)}' instead " + $"of '{nameof(QueryAttribute)}' or '{nameof(MutationAttribute)}'.\n---------\n " + fieldNames, @@ -213,12 +213,6 @@ public override void ValidateOrThrow(bool validateChildren = true) /// public override AppliedSecurityPolicyGroup SecurityPolicies => AppliedSecurityPolicyGroup.Empty; - /// - public override string InternalFullName => this.ObjectType?.FriendlyName(true); - - /// - public override string InternalName => this.ObjectType?.FriendlyName(); - private IEnumerable AllowedGraphCollectionTypes => SchemaItemCollections.Types.AsEnumerable(); } } \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/TypeTemplates/InterfaceGraphTypeTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/InterfaceGraphTypeTemplate.cs similarity index 87% rename from src/graphql-aspnet/Internal/TypeTemplates/InterfaceGraphTypeTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/InterfaceGraphTypeTemplate.cs index b5ab7ba40..ab1bd97e5 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/InterfaceGraphTypeTemplate.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/InterfaceGraphTypeTemplate.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; @@ -41,16 +41,17 @@ public InterfaceGraphTypeTemplate(Type interfaceType) } /// - protected override IEnumerable GatherPossibleTemplateMembers() + protected override IEnumerable GatherPossibleFieldTemplates() { return this.ObjectType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) - .Where(x => + .Where(x => !x.IsGenericMethod && !x.IsSpecialName && x.DeclaringType != typeof(object) && x.DeclaringType != typeof(ValueType)) - .Cast() - .Concat(this.ObjectType.GetProperties(BindingFlags.Public | BindingFlags.Instance)); + .Cast() + .Concat(this.ObjectType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + .Select(x => new MemberInfoProvider(x)); } /// diff --git a/src/graphql-aspnet/Schemas/Generation/TypeTemplates/ItemSource.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/ItemSource.cs new file mode 100644 index 000000000..514edb697 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/ItemSource.cs @@ -0,0 +1,29 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates +{ + /// + /// An enum that indicates where a piece of data (typically a graph type or resolver) was templated from. + /// + public enum ItemSource + { + /// + /// Indicates that the data item was created from code declared at design time. That it was a + /// defined, precomipled type in the developer's source code. + /// + DesignTime, + + /// + /// Indicates that the data item was created from code declared at run time. That it was configured + /// at startup, after the program code had been compiled. + /// + Runtime, + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/TypeTemplates/MemberInfoProvider.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/MemberInfoProvider.cs new file mode 100644 index 000000000..d4e27f30d --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/MemberInfoProvider.cs @@ -0,0 +1,51 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates +{ + using System.Reflection; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Interfaces.Internal; + + /// + /// A for to abstract out appropriate pieces + /// of that are exposed to the templating system. + /// + public class MemberInfoProvider : IMemberInfoProvider + { + /// + /// Initializes a new instance of the class. + /// + /// The primary member information used in this instance. + /// This object will be used as the primary member info as well as the attribute provider for this instance. + public MemberInfoProvider(MemberInfo memberInfo) + { + this.MemberInfo = Validation.ThrowIfNullOrReturn(memberInfo, nameof(memberInfo)); + this.AttributeProvider = this.MemberInfo; + } + + /// + /// Initializes a new instance of the class. + /// + /// The primary member information used in this instance. + /// The an alternate attribute source + /// to use for determining configuration and control attributes for this instance. + public MemberInfoProvider(MemberInfo memberInfo, ICustomAttributeProvider attributeProvider) + { + this.MemberInfo = Validation.ThrowIfNullOrReturn(memberInfo, nameof(memberInfo)); + this.AttributeProvider = Validation.ThrowIfNullOrReturn(attributeProvider, nameof(attributeProvider)); + } + + /// + public MemberInfo MemberInfo { get; } + + /// + public ICustomAttributeProvider AttributeProvider { get; } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/TypeTemplates/MethodGraphFieldTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/MethodGraphFieldTemplate.cs similarity index 75% rename from src/graphql-aspnet/Internal/TypeTemplates/MethodGraphFieldTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/MethodGraphFieldTemplate.cs index 69b7d3447..fe24f184e 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/MethodGraphFieldTemplate.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/MethodGraphFieldTemplate.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System.Reflection; using GraphQL.AspNet.Attributes; @@ -35,6 +35,20 @@ public MethodGraphFieldTemplate(IGraphTypeTemplate parent, MethodInfo methodInfo this.OwnerTypeKind = ownerTypeKind; } + /// + /// Initializes a new instance of the class. + /// + /// The parent object template that owns this method. + /// The method information. + /// A custom, external attribute provider to use instead for extracting + /// configuration attributes instead of the provider on . + /// The kind of object that will own this field. + public MethodGraphFieldTemplate(IGraphTypeTemplate parent, MethodInfo methodInfo, ICustomAttributeProvider attributeProvider, TypeKind ownerTypeKind) + : base(parent, methodInfo, attributeProvider) + { + this.OwnerTypeKind = ownerTypeKind; + } + /// protected override SchemaItemPath GenerateFieldPath() { @@ -43,7 +57,7 @@ protected override SchemaItemPath GenerateFieldPath() var graphName = this.Method.SingleAttributeOrDefault()?.Template?.Trim() ?? Constants.Routing.ACTION_METHOD_META_NAME; graphName = graphName.Replace(Constants.Routing.ACTION_METHOD_META_NAME, this.Method.Name).Trim(); - GraphValidation.EnsureGraphNameOrThrow(this.InternalFullName, graphName); + GraphValidation.EnsureGraphNameOrThrow(this.InternalName, graphName); return new SchemaItemPath(SchemaItemPath.Join(this.Parent.Route.Path, graphName)); } @@ -60,7 +74,7 @@ public override void ValidateOrThrow(bool validateChildren = true) if (declaration != null && declaration != typeof(GraphFieldAttribute)) { throw new GraphTypeDeclarationException( - $"Invalid graph method declaration. The method '{this.InternalFullName}' declares a '{declaration.FriendlyName()}'. This " + + $"Invalid graph method declaration. The method '{this.InternalName}' declares a '{declaration.FriendlyName()}'. This " + $"attribute is reserved for controller actions. For a general object type use '{nameof(GraphFieldAttribute)}' instead."); } } diff --git a/src/graphql-aspnet/Internal/TypeTemplates/MethodGraphFieldTemplateBase.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/MethodGraphFieldTemplateBase.cs similarity index 55% rename from src/graphql-aspnet/Internal/TypeTemplates/MethodGraphFieldTemplateBase.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/MethodGraphFieldTemplateBase.cs index 1aa44bb97..932340b32 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/MethodGraphFieldTemplateBase.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/MethodGraphFieldTemplateBase.cs @@ -7,28 +7,45 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; + using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Common; + using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal.Resolvers; /// /// A base class representing common functionality between all field templates based on /// C# methods. /// [DebuggerDisplay("Route: {Route.Path}")] - public abstract class MethodGraphFieldTemplateBase : GraphFieldTemplateBase, IGraphFieldResolverMethod + public abstract class MethodGraphFieldTemplateBase : GraphFieldTemplateBase { private readonly List _arguments; + /// + /// Initializes a new instance of the class. + /// + /// The parent object template that owns this method. + /// The method information. + /// A custom, external attribute provider to use instead for extracting + /// configuration attributes instead of the provider on . + protected MethodGraphFieldTemplateBase(IGraphTypeTemplate parent, MethodInfo methodInfo, ICustomAttributeProvider attributeProvider) + : base(parent, attributeProvider) + { + this.Method = Validation.ThrowIfNullOrReturn(methodInfo, nameof(methodInfo)); + this.Parameters = this.Method.GetParameters().ToList(); + _arguments = new List(); + } + /// /// Initializes a new instance of the class. /// @@ -47,10 +64,17 @@ protected override void ParseTemplateDefinition() { base.ParseTemplateDefinition(); + // set the internal name of the item + var fieldDeclaration = this.AttributeProvider.SingleAttributeOfTypeOrDefault(); + if (fieldDeclaration != null) + this.InternalName = fieldDeclaration.InternalName; + if (string.IsNullOrWhiteSpace(this.InternalName)) + this.InternalName = $"{this.Parent.InternalName}.{this.Method.Name}"; + // parse all input parameters from the method signature - foreach (var parameter in this.Method.GetParameters()) + foreach (var parameter in this.Parameters) { - var argTemplate = this.CreateInputArgument(parameter); + var argTemplate = this.CreateArgument(parameter); argTemplate.Parse(); _arguments.Add(argTemplate); } @@ -67,7 +91,7 @@ protected override void ParseTemplateDefinition() /// /// The parameter information. /// IGraphFieldArgumentTemplate. - protected virtual GraphArgumentTemplate CreateInputArgument(ParameterInfo paramInfo) + protected virtual GraphArgumentTemplate CreateArgument(ParameterInfo paramInfo) { return new GraphArgumentTemplate(this, paramInfo); } @@ -77,17 +101,10 @@ public override void ValidateOrThrow(bool validateChildren = true) { base.ValidateOrThrow(validateChildren); - if (this.Method.IsStatic) - { - throw new GraphTypeDeclarationException( - $"Invalid graph method declaration. The method '{this.InternalFullName}' is static. Only " + - "instance members can be registered as field."); - } - if (this.ExpectedReturnType == null) { throw new GraphTypeDeclarationException( - $"Invalid graph method declaration. The method '{this.InternalFullName}' has no valid {nameof(ExpectedReturnType)}. An expected " + + $"Invalid graph method declaration. The method '{this.InternalName}' has no valid {nameof(this.ExpectedReturnType)}. An expected " + "return type must be assigned from the declared return type."); } } @@ -95,25 +112,37 @@ public override void ValidateOrThrow(bool validateChildren = true) /// public override IGraphFieldResolver CreateResolver() { - return new ObjectMethodGraphFieldResolver(this); + return new ObjectMethodGraphFieldResolver(this.CreateResolverMetaData()); } /// - public override string InternalFullName => $"{this.Parent?.InternalFullName}.{this.Method.Name}"; - - /// - public override string InternalName => this.Method.Name; + public override IGraphFieldResolverMetaData CreateResolverMetaData() + { + var paramSet = new FieldResolverParameterMetaDataCollection( + this.Arguments.Select(x => x.CreateResolverMetaData())); + + return new FieldResolverMetaData( + this.Method, + paramSet, + this.ExpectedReturnType, + this.IsAsyncField, + this.InternalName, + this.Method.Name, + this.Parent.ObjectType, + this.Parent.InternalName, + this.Parent.TemplateSource); + } /// public override IReadOnlyList Arguments => _arguments; - /// + /// public MethodInfo Method { get; } /// public override Type DeclaredReturnType => this.Method.ReturnType; - /// + /// public Type ExpectedReturnType { get; protected set; } /// @@ -122,7 +151,10 @@ public override IGraphFieldResolver CreateResolver() /// public override GraphFieldSource FieldSource => GraphFieldSource.Method; - /// + /// + /// Gets the set of parameters found on the target . + /// + /// The parameters. public IReadOnlyList Parameters { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/TypeTemplates/NonLeafGraphTypeTemplateBase.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/NonLeafGraphTypeTemplateBase.cs similarity index 76% rename from src/graphql-aspnet/Internal/TypeTemplates/NonLeafGraphTypeTemplateBase.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/NonLeafGraphTypeTemplateBase.cs index a83aa387b..dce22646e 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/NonLeafGraphTypeTemplateBase.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/NonLeafGraphTypeTemplateBase.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; @@ -63,9 +63,9 @@ internal NonLeafGraphTypeTemplateBase(Type objectType) { rejectionReason = $"The type '{objectType.FriendlyName()}' is an enumeration and cannot be parsed as an {nameof(TypeKind.OBJECT)} graph type. Use an {typeof(IEnumGraphType).FriendlyName()} instead."; } - else if (GraphQLProviders.ScalarProvider.IsScalar(objectType)) + else if (objectType.IsPrimitive) { - rejectionReason = $"The type '{objectType.FriendlyName()}' is a registered {nameof(TypeKind.SCALAR)} and cannot be parsed as an {nameof(TypeKind.OBJECT)} graph type. Try using the scalar definition instead."; + rejectionReason = $"The type '{objectType.FriendlyName()}' is a primative data type and cannot be parsed as an {nameof(TypeKind.OBJECT)} graph type."; } else if (objectType == typeof(string)) { @@ -105,7 +105,7 @@ protected override void ParseTemplateDefinition() // ------------------------------------ _fields.Clear(); - var templateMembers = this.GatherPossibleTemplateMembers(); + var templateMembers = this.GatherPossibleFieldTemplates(); foreach (var member in templateMembers) { @@ -147,25 +147,27 @@ protected override void ParseTemplateDefinition() /// Extract the possible template members of /// that might be includable in this template. /// - /// IEnumerable<MemberInfo>. - protected abstract IEnumerable GatherPossibleTemplateMembers(); + /// IEnumerable<IFieldMemberInfoProvider>. + protected abstract IEnumerable GatherPossibleFieldTemplates(); /// - /// Creates the member template from the given info. If overriden in a child class methods and - /// may no longer be called. This method gives you a point of inflection to override how all - /// field templates are created or just those for a given member type. + /// Creates the member template from the given info. If a provided + /// should not be templatized, return null. /// - /// The member. + /// The member to templatize. /// GraphQL.AspNet.Internal.Interfaces.IGraphFieldTemplate. - protected virtual IGraphFieldTemplate CreateFieldTemplate(MemberInfo member) + protected virtual IGraphFieldTemplate CreateFieldTemplate(IMemberInfoProvider fieldProvider) { - switch (member) + if (fieldProvider?.MemberInfo == null) + return null; + + switch (fieldProvider.MemberInfo) { case PropertyInfo pi: - return this.CreatePropertyFieldTemplate(pi); + return new PropertyGraphFieldTemplate(this, pi, fieldProvider.AttributeProvider, this.Kind); case MethodInfo mi: - return this.CreateMethodFieldTemplate(mi); + return new MethodGraphFieldTemplate(this, mi, fieldProvider.AttributeProvider, this.Kind); } return null; @@ -176,34 +178,36 @@ protected virtual IGraphFieldTemplate CreateFieldTemplate(MemberInfo member) /// explictly declared as such or that it conformed to the required parameters of being /// a field. /// - /// The member information to check. + /// The member information to check. /// /// true if the info represents a possible graph field; otherwise, false. - protected virtual bool CouldBeGraphField(MemberInfo memberInfo) + protected virtual bool CouldBeGraphField(IMemberInfoProvider fieldProvider) { // always skip those marked as such regardless of anything else - if (memberInfo.HasAttribute()) + if (fieldProvider.AttributeProvider.HasAttribute()) return false; - if (Constants.IgnoredFieldNames.Contains(memberInfo.Name)) + if (Constants.IgnoredFieldNames.Contains(fieldProvider.MemberInfo.Name)) return false; - if (memberInfo.DeclaringType?.IsRecord() ?? false) + if (fieldProvider.MemberInfo.DeclaringType?.IsRecord() ?? false) { - if (Constants.IgnoredRecordFieldNames.Contains(memberInfo.Name)) + if (Constants.IgnoredRecordFieldNames.Contains(fieldProvider.MemberInfo.Name)) return false; } // when the member declares any known attribute in the library include it // and allow it to generate validation failures if its not properly constructed - if (memberInfo.SingleAttributeOfTypeOrDefault() != null) + if (fieldProvider.AttributeProvider.SingleAttributeOfTypeOrDefault() != null) return true; // do some preliminary validation and skip those items that could never be valid // this is different in v2+ - switch (memberInfo) + switch (fieldProvider.MemberInfo) { case MethodInfo mi: + if (mi.IsStatic) + return false; if (!GraphValidation.IsValidGraphType(mi.ReturnType, false)) return false; if (mi.GetParameters().Any(x => !GraphValidation.IsValidGraphType(x.ParameterType, false))) @@ -213,6 +217,8 @@ protected virtual bool CouldBeGraphField(MemberInfo memberInfo) case PropertyInfo pi: if (pi.GetGetMethod() == null) return false; + if (pi.GetGetMethod().IsStatic) + return false; if (pi.GetIndexParameters().Length > 0) return false; @@ -225,7 +231,7 @@ protected virtual bool CouldBeGraphField(MemberInfo memberInfo) } /// - /// When overridden in a child class, this metyhod builds the route that will be assigned to this method + /// When overridden in a child class, this method builds the route that will be assigned to this method /// using the implementation rules of the concrete type. /// /// GraphRoutePath. @@ -244,9 +250,9 @@ public override void ValidateOrThrow(bool validateChildren = true) if (_invalidFields != null && _invalidFields.Count > 0) { - var fieldNames = string.Join("\n", _invalidFields.Select(x => $"Field: '{x.InternalFullName} ({x.Route.RootCollection.ToString()})'")); + var fieldNames = string.Join("\n", _invalidFields.Select(x => $"Field: '{x.InternalName} ({x.Route.RootCollection.ToString()})'")); throw new GraphTypeDeclarationException( - $"Invalid field declarations. The type '{this.InternalFullName}' declares fields belonging to a graph collection not allowed given its context. This type can " + + $"Invalid field declarations. The type '{this.InternalName}' declares fields belonging to a graph collection not allowed given its context. This type can " + $"only be declared the following graph collections: '{string.Join(", ", this.AllowedSchemaItemCollections.Select(x => x.ToString()))}'. " + $"If this field is declared on an object (not a controller) be sure to use '{nameof(GraphFieldAttribute)}' instead " + $"of '{nameof(QueryAttribute)}' or '{nameof(MutationAttribute)}'.\n---------\n " + fieldNames, @@ -260,34 +266,6 @@ public override void ValidateOrThrow(bool validateChildren = true) } } - /// - /// When overridden in a child, allows the class to create custom templates that inherit from - /// to provide additional functionality or guarantee a certian type structure for all methods on this object template. - /// - /// The method information to templatize. - /// IGraphFieldTemplate. - protected virtual IGraphFieldTemplate CreateMethodFieldTemplate(MethodInfo methodInfo) - { - if (methodInfo == null) - return null; - - return new MethodGraphFieldTemplate(this, methodInfo, this.Kind); - } - - /// - /// When overridden in a child, allows the class to create custom templates to provide additional functionality or - /// guarantee a certian type structure for all properties on this object template. - /// - /// The property information to templatize. - /// IGraphFieldTemplate. - protected virtual IGraphFieldTemplate CreatePropertyFieldTemplate(PropertyInfo propInfo) - { - if (propInfo == null) - return null; - - return new PropertyGraphFieldTemplate(this, propInfo, this.Kind); - } - /// public override AppliedSecurityPolicyGroup SecurityPolicies => _securityPolicies; @@ -297,12 +275,6 @@ protected virtual IGraphFieldTemplate CreatePropertyFieldTemplate(PropertyInfo p /// The methods. public IReadOnlyList FieldTemplates => _fields; - /// - public override string InternalFullName => this.ObjectType?.FriendlyName(true); - - /// - public override string InternalName => this.ObjectType?.FriendlyName(); - /// /// Gets a set of item collections to which this object template can be declared. /// diff --git a/src/graphql-aspnet/Internal/TypeTemplates/ObjectGraphTypeTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/ObjectGraphTypeTemplate.cs similarity index 67% rename from src/graphql-aspnet/Internal/TypeTemplates/ObjectGraphTypeTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/ObjectGraphTypeTemplate.cs index d673bca42..09c6a1fa9 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/ObjectGraphTypeTemplate.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/ObjectGraphTypeTemplate.cs @@ -7,20 +7,18 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; - using GraphQL.AspNet.Common.Extensions; - using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Schemas.TypeSystem; /// - /// An graph type template describing an OBJECT graph type. + /// A graph type template describing an OBJECT graph type. /// [DebuggerDisplay("Object: {InternalName}")] public class ObjectGraphTypeTemplate : NonLeafGraphTypeTemplateBase, IObjectGraphTypeTemplate @@ -35,17 +33,18 @@ public ObjectGraphTypeTemplate(Type objectType) } /// - protected override IEnumerable GatherPossibleTemplateMembers() + protected override IEnumerable GatherPossibleFieldTemplates() { return this.ObjectType.GetMethods(BindingFlags.Public | BindingFlags.Instance) - .Where(x => - !x.IsAbstract && - !x.IsGenericMethod && - !x.IsSpecialName && - x.DeclaringType != typeof(object) && - x.DeclaringType != typeof(ValueType)) - .Cast() - .Concat(this.ObjectType.GetProperties(BindingFlags.Public | BindingFlags.Instance)); + .Where(x => + !x.IsAbstract && + !x.IsGenericMethod && + !x.IsSpecialName && + x.DeclaringType != typeof(object) && + x.DeclaringType != typeof(ValueType)) + .Cast() + .Concat(this.ObjectType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + .Select(x => new MemberInfoProvider(x)); } /// diff --git a/src/graphql-aspnet/Internal/TypeTemplates/PropertyGraphFieldTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/PropertyGraphFieldTemplate.cs similarity index 59% rename from src/graphql-aspnet/Internal/TypeTemplates/PropertyGraphFieldTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/PropertyGraphFieldTemplate.cs index bfbdc7e9a..d2a52dcb3 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/PropertyGraphFieldTemplate.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/PropertyGraphFieldTemplate.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; @@ -18,9 +18,9 @@ namespace GraphQL.AspNet.Internal.TypeTemplates using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal.Resolvers; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; @@ -29,13 +29,30 @@ namespace GraphQL.AspNet.Internal.TypeTemplates /// is created from a C# object property. /// [DebuggerDisplay("Route: {Route.Path}")] - public class PropertyGraphFieldTemplate : GraphFieldTemplateBase, IGraphFieldResolverMethod + public class PropertyGraphFieldTemplate : GraphFieldTemplateBase { /// /// Initializes a new instance of the class. /// - /// The parent. - /// The property information. + /// The owner of this field template. + /// The property information that will be used to create the field. + /// A custom, external attribute provider to use instead for extracting + /// configuration attributes instead of the provider on . + /// The kind of graph type that will own this field. + public PropertyGraphFieldTemplate(IGraphTypeTemplate parent, PropertyInfo propInfo, ICustomAttributeProvider attributeProvider, TypeKind ownerKind) + : base(parent, attributeProvider) + { + this.Property = Validation.ThrowIfNullOrReturn(propInfo, nameof(propInfo)); + this.Method = this.Property.GetGetMethod(); + this.Parameters = this.Method?.GetParameters().ToList() ?? new List(); + this.OwnerTypeKind = ownerKind; + } + + /// + /// Initializes a new instance of the class. + /// + /// The owner of this field template. + /// The property information that will be used to create the field. /// The kind of graph type that will own this field. public PropertyGraphFieldTemplate(IGraphTypeTemplate parent, PropertyInfo propInfo, TypeKind ownerKind) : base(parent, propInfo) @@ -61,22 +78,22 @@ protected override SchemaItemPath GenerateFieldPath() /// public override void ValidateOrThrow(bool validateChildren = true) { - base.ValidateOrThrow(validateChildren); - // ensure property has a public getter (kinda useless otherwise) if (this.Property.GetGetMethod() == null) { throw new GraphTypeDeclarationException( - $"The graph property {this.InternalFullName} does not define a public getter. It cannot be parsed or added " + + $"The graph property {this.InternalName} does not define a public getter. It cannot be parsed or added " + "to the object graph."); } if (this.ExpectedReturnType == null) { throw new GraphTypeDeclarationException( - $"The graph property '{this.InternalFullName}' has no valid {nameof(ExpectedReturnType)}. An expected " + + $"The graph property '{this.InternalName}' has no valid {nameof(this.ExpectedReturnType)}. An expected " + "return type must be assigned from the declared return type."); } + + base.ValidateOrThrow(validateChildren); } /// @@ -84,6 +101,13 @@ protected override void ParseTemplateDefinition() { base.ParseTemplateDefinition(); + // set the internal name of the item + var fieldDeclaration = this.AttributeProvider.SingleAttributeOfTypeOrDefault(); + if (fieldDeclaration != null) + this.InternalName = fieldDeclaration.InternalName; + if (string.IsNullOrWhiteSpace(this.InternalName) && this.Method != null) + this.InternalName = $"{this.Parent.InternalName}.{this.Method.Name}"; + this.ExpectedReturnType = GraphValidation.EliminateWrappersFromCoreType( this.DeclaredReturnType, false, @@ -94,13 +118,31 @@ protected override void ParseTemplateDefinition() /// public override IGraphFieldResolver CreateResolver() { - return new ObjectPropertyGraphFieldResolver(this); + return new ObjectPropertyGraphFieldResolver(this.CreateResolverMetaData()); } /// - public override Type DeclaredReturnType => this.Property.PropertyType; + public override IGraphFieldResolverMetaData CreateResolverMetaData() + { + var paramSet = new FieldResolverParameterMetaDataCollection( + this.Arguments.Select(x => x.CreateResolverMetaData())); + + return new FieldResolverMetaData( + this.Method, + paramSet, + this.ExpectedReturnType, + this.IsAsyncField, + this.InternalName, + this.Property.Name, + this.Parent.ObjectType, + this.Parent.InternalName, + this.Parent.TemplateSource); + } /// + public override Type DeclaredReturnType => this.Property.PropertyType; + + /// public Type ExpectedReturnType { get; private set; } /// @@ -118,19 +160,16 @@ public override IGraphFieldResolver CreateResolver() /// The property. private PropertyInfo Property { get; } - /// + /// public MethodInfo Method { get; } - /// + /// + /// Gets the set of parameters defined by . + /// + /// The parameters. public IReadOnlyList Parameters { get; } /// public override IReadOnlyList Arguments { get; } = new List(); - - /// - public override string InternalFullName => $"{this.Parent.InternalFullName}.{this.Property.Name}"; - - /// - public override string InternalName => this.Property.Name; } } \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/TypeTemplates/RuntimeGraphControllerTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/RuntimeGraphControllerTemplate.cs new file mode 100644 index 000000000..218f3fb83 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/RuntimeGraphControllerTemplate.cs @@ -0,0 +1,72 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates +{ + using System.Collections.Generic; + using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + + /// + /// A "controller template" representing a single runtime configured field (e.g. minimal api). + /// This template is never cached. + /// + internal class RuntimeGraphControllerTemplate : GraphControllerTemplate + { + private readonly IMemberInfoProvider _fieldProvider; + private readonly IGraphQLRuntimeResolvedFieldDefinition _fieldDefinition; + + /// + /// Initializes a new instance of the class. + /// + /// A single, runtime configured, field definition + /// to templatize for a specfic schema. + public RuntimeGraphControllerTemplate(IGraphQLRuntimeResolvedFieldDefinition fieldDefinition) + : base(typeof(RuntimeFieldExecutionController)) + { + _fieldDefinition = fieldDefinition; + if (fieldDefinition.Resolver?.Method != null) + { + _fieldProvider = new MemberInfoProvider( + fieldDefinition.Resolver.Method, + new RuntimeSchemaItemAttributeProvider(fieldDefinition)); + } + } + + /// + protected override IEnumerable GatherPossibleFieldTemplates() + { + yield return _fieldProvider; + } + + /// + protected override bool CouldBeGraphField(IMemberInfoProvider fieldProvider) + { + return fieldProvider != null && fieldProvider == _fieldProvider; + } + + /// + public override void ValidateOrThrow(bool validateChildren = true) + { + if (_fieldDefinition?.Resolver?.Method == null) + { + throw new GraphTypeDeclarationException( + $"Unable to templatize the runtime field definition of '{_fieldDefinition?.Route.Path ?? "~null~"}' the resolver " + + $"is not properly configured."); + } + + base.ValidateOrThrow(validateChildren); + } + + /// + public override ItemSource TemplateSource => ItemSource.Runtime; + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/TypeTemplates/RuntimeGraphDirectiveTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/RuntimeGraphDirectiveTemplate.cs new file mode 100644 index 000000000..4effa6b31 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/RuntimeGraphDirectiveTemplate.cs @@ -0,0 +1,94 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates +{ + using System.Collections.Generic; + using System.Diagnostics; + using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + + /// + /// A "controller template" representing a single runtime configured field (e.g. minimal api). + /// This template is never cached. + /// + [DebuggerDisplay("{Route.Name} - RuntimeDirective")] + internal class RuntimeGraphDirectiveTemplate : GraphDirectiveTemplate + { + private readonly IMemberInfoProvider _fieldProvider; + private readonly IGraphQLRuntimeDirectiveDefinition _directiveDefinition; + + /// + /// Initializes a new instance of the class. + /// + /// A single, runtime configured, directive definition + /// to templatize for a specfic schema. + public RuntimeGraphDirectiveTemplate(IGraphQLRuntimeDirectiveDefinition directiveDef) + : base(typeof(RuntimeExecutionDirective), new RuntimeSchemaItemAttributeProvider(directiveDef)) + { + _directiveDefinition = directiveDef; + if (directiveDef?.Resolver?.Method != null) + { + _fieldProvider = new MemberInfoProvider( + directiveDef.Resolver.Method, + new RuntimeSchemaItemAttributeProvider(directiveDef)); + } + } + + /// + protected override void ParseTemplateDefinition() + { + base.ParseTemplateDefinition(); + + if (!string.IsNullOrWhiteSpace(_directiveDefinition?.InternalName)) + this.InternalName = _directiveDefinition.InternalName; + } + + /// + protected override IEnumerable GatherPossibleDirectiveExecutionMethods() + { + yield return this._fieldProvider; + } + + /// + protected override bool CouldBeDirectiveExecutionMethod(IMemberInfoProvider memberInfgo) + { + return memberInfgo != null + && memberInfgo == _fieldProvider + && base.CouldBeDirectiveExecutionMethod(memberInfgo); + } + + /// + protected override string DetermineDirectiveName() + { + if (_directiveDefinition != null) + return _directiveDefinition?.Route.Name; + + return base.DetermineDirectiveName(); + } + + /// + public override void ValidateOrThrow(bool validateChildren = true) + { + if (_directiveDefinition?.Resolver?.Method == null) + { + throw new GraphTypeDeclarationException( + $"Unable to templatize the runtime directive definition of '{_directiveDefinition?.Route.Path ?? "~null~"}' the resolver " + + $"is not properly configured."); + } + + base.ValidateOrThrow(validateChildren); + } + + /// + public override ItemSource TemplateSource => ItemSource.Runtime; + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/TypeTemplates/RuntimeSchemaItemAttributeProvider.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/RuntimeSchemaItemAttributeProvider.cs new file mode 100644 index 000000000..c1713c017 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/RuntimeSchemaItemAttributeProvider.cs @@ -0,0 +1,84 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates +{ + using System; + using System.Collections.Generic; + using System.Reflection; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + + /// + /// A custom that can provide runtime declared attributes + /// to the templating engine, instead of them being provided at design time on a method. + /// + public class RuntimeSchemaItemAttributeProvider : ICustomAttributeProvider + { + private readonly IGraphQLResolvableSchemaItemDefinition _fieldDef; + private readonly object[] _onlyDefinedAttributes; + private readonly object[] _allAttributes; + + /// + /// Initializes a new instance of the class. + /// + /// The field definition created at runtime + /// that contains the attributes to be provided. + public RuntimeSchemaItemAttributeProvider(IGraphQLResolvableSchemaItemDefinition fieldDefinition) + { + _fieldDef = Validation.ThrowIfNullOrReturn(fieldDefinition, nameof(fieldDefinition)); + + var all = new List(); + var onlyDefined = new List(); + + all.AddRange(_fieldDef.Attributes); + all.AddRange(_fieldDef.Resolver.Method.GetCustomAttributes(true)); + + onlyDefined.AddRange(_fieldDef.Attributes); + + _onlyDefinedAttributes = onlyDefined.ToArray(); + _allAttributes = all.ToArray(); + } + + /// + public object[] GetCustomAttributes(bool inherit) + { + return inherit ? _allAttributes : _onlyDefinedAttributes; + } + + /// + public object[] GetCustomAttributes(Type attributeType, bool inherit) + { + var attribs = this.GetCustomAttributes(inherit); + + var outList = new List(); + foreach (var attrib in attribs) + { + if (Validation.IsCastable(attrib?.GetType(), attributeType)) + outList.Add(attrib); + } + + return outList.ToArray(); + } + + /// + public bool IsDefined(Type attributeType, bool inherit) + { + // MemberInfo defines this method to be the type or one that inherits from it + var attribs = this.GetCustomAttributes(inherit); + foreach (var attrib in attribs) + { + if (attrib?.GetType() != null && Validation.IsCastable(attrib.GetType(), attributeType)) + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Generation/TypeTemplates/ScalarGraphTypeTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/ScalarGraphTypeTemplate.cs new file mode 100644 index 000000000..b33fe1111 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/ScalarGraphTypeTemplate.cs @@ -0,0 +1,110 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates +{ + using System; + using System.Collections.Generic; + using System.Linq; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Security; + + /// + /// An graph type template describing a SCALAR graph type. + /// + public class ScalarGraphTypeTemplate : GraphTypeTemplateBase, IScalarGraphTypeTemplate + { + private readonly Type _scalarType; + private IScalarGraphType _instance; + + /// + /// Initializes a new instance of the class. + /// + /// The type to template. + public ScalarGraphTypeTemplate(Type typeToTemplate) + : base(GlobalTypes.FindBuiltInScalarType(typeToTemplate) ?? typeToTemplate) + { + Validation.ThrowIfNull(typeToTemplate, nameof(typeToTemplate)); + _scalarType = GlobalTypes.FindBuiltInScalarType(typeToTemplate) ?? typeToTemplate; + _instance = GlobalTypes.CreateScalarInstance(_scalarType); + } + + /// + protected override void ParseTemplateDefinition() + { + base.ParseTemplateDefinition(); + + if (_instance != null) + { + this.Route = new SchemaItemPath(SchemaItemPath.Join(SchemaItemCollections.Types, _instance.Name)); + this.ObjectType = _instance.ObjectType; + this.InternalName = _instance.InternalName; + } + + if (string.IsNullOrWhiteSpace(this.InternalName)) + this.InternalName = _scalarType?.FriendlyName(); + } + + /// + public override void ValidateOrThrow(bool validateChildren = true) + { + GlobalTypes.ValidateScalarTypeOrThrow(this.ScalarType); + base.ValidateOrThrow(validateChildren); + } + + /// + protected override IEnumerable ParseAppliedDiretives() + { + if (_instance != null) + { + return _instance.AppliedDirectives.Select(x => new AppliedDirectiveTemplate( + this, + x.DirectiveName, + x.ArgumentValues) + { + DirectiveType = x.DirectiveType, + }); + } + + return Enumerable.Empty(); + } + + /// + public override AppliedSecurityPolicyGroup SecurityPolicies { get; } + + /// + public override TypeKind Kind => TypeKind.SCALAR; + + /// + public override string Name => _instance?.Name; + + /// + public override string Description + { + get + { + return _instance?.Description; + } + + protected set + { + // description is fixed to that in the scalar instance. + } + } + + /// + public Type ScalarType => _scalarType; + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/TypeTemplates/SchemaItemTemplateBase.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/SchemaItemTemplateBase.cs similarity index 85% rename from src/graphql-aspnet/Internal/TypeTemplates/SchemaItemTemplateBase.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/SchemaItemTemplateBase.cs index ef4ae37e6..e28bbcf55 100644 --- a/src/graphql-aspnet/Internal/TypeTemplates/SchemaItemTemplateBase.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/SchemaItemTemplateBase.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal.TypeTemplates +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; @@ -87,14 +87,14 @@ public virtual void ValidateOrThrow(bool validateChildren = true) if (!_isParsed) { throw new InvalidOperationException( - $"The graph item has not been parsed and cannot pass validation. Be sure to call {nameof(this.Parse)}() before attempting to " + + $"The schema template item has not been parsed and cannot pass validation. Be sure to call {nameof(this.Parse)}() before attempting to " + "validate this instance."); } if (this.AttributeProvider.SingleAttributeOfTypeOrDefault() != null) { throw new GraphTypeDeclarationException( - $"The graph item {this.InternalFullName} defines a {nameof(GraphSkipAttribute)}. It cannot be parsed or added " + + $"The schema template item {this.InternalName} defines a {nameof(GraphSkipAttribute)}. It cannot be parsed or added " + "to the object graph.", this.ObjectType); } @@ -102,11 +102,18 @@ public virtual void ValidateOrThrow(bool validateChildren = true) if (this.Route == null || !this.Route.IsValid) { throw new GraphTypeDeclarationException( - $"The template item '{this.InternalFullName}' declares an invalid route of '{this.Route?.Path ?? ""}'. " + + $"The schema template item '{this.InternalName}' declares an invalid route of '{this.Route?.Raw ?? ""}'. " + $"Each segment of the route must conform to standard graphql naming rules. (Regex: {Constants.RegExPatterns.NameRegex} )", this.ObjectType); } + if (string.IsNullOrWhiteSpace(this.InternalName)) + { + throw new GraphTypeDeclarationException( + $"The schema template item identified by `{this.ObjectType.FriendlyName()}` and named '{this.Name}' on a schema " + + $"does not declare a valid internal name."); + } + foreach (var directive in this.AppliedDirectives) directive.ValidateOrThrow(); } @@ -124,10 +131,7 @@ public virtual void ValidateOrThrow(bool validateChildren = true) public SchemaItemPath Route { get; protected set; } /// - public abstract string InternalFullName { get; } - - /// - public abstract string InternalName { get; } + public string InternalName { get; protected set; } /// public ICustomAttributeProvider AttributeProvider { get; } diff --git a/src/graphql-aspnet/Schemas/Generation/TypeTemplates/UnionGraphTypeTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/UnionGraphTypeTemplate.cs new file mode 100644 index 000000000..0a8555b04 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/UnionGraphTypeTemplate.cs @@ -0,0 +1,98 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Generation.TypeTemplates +{ + using System; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Common.Generics; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Security; + + /// + /// An graph type template describing a SCALAR graph type. + /// + public class UnionGraphTypeTemplate : GraphTypeTemplateBase, IUnionGraphTypeTemplate + { + private readonly Type _proxyType; + private IGraphUnionProxy _instance; + + /// + /// Initializes a new instance of the class. + /// + /// The type that implements . + public UnionGraphTypeTemplate(Type proxyType) + : base(proxyType) + { + _proxyType = proxyType; + this.ObjectType = null; + } + + /// + protected override void ParseTemplateDefinition() + { + base.ParseTemplateDefinition(); + + try + { + if (_proxyType != null) + { + _instance = GlobalTypes.CreateUnionProxyFromType(_proxyType); + if (_instance != null) + { + this.Route = new SchemaItemPath(SchemaItemPath.Join(SchemaItemCollections.Types, _instance.Name)); + this.InternalName = _instance.InternalName; + } + + this.InternalName = this.InternalName ?? _proxyType.FriendlyName(); + } + } + catch + { + } + + if (string.IsNullOrWhiteSpace(this.InternalName)) + { + // ad-hoc unions will be a flat instance of GraphUnionProxy, not a differentiated instance + // BUT internally it will always be guarunteed that the flat instance is instantiable + // and that a name will be defined so this "should" never run....best laid plans though, am i rite? + this.InternalName = _proxyType?.FriendlyName(); + } + } + + /// + public override void ValidateOrThrow(bool validateChildren = true) + { + base.ValidateOrThrow(validateChildren); + GlobalTypes.ValidateUnionProxyOrThrow(_proxyType); + } + + /// + public override AppliedSecurityPolicyGroup SecurityPolicies => null; + + /// + public override string Name => _instance?.Name; + + /// + public override string Description => _instance?.Description; + + /// + public override TypeKind Kind => TypeKind.UNION; + + /// + public Type ProxyType => _proxyType; + + /// + public override bool Publish => _instance?.Publish ?? false; + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/GraphSchema.cs b/src/graphql-aspnet/Schemas/GraphSchema.cs index 5337998ec..94152956c 100644 --- a/src/graphql-aspnet/Schemas/GraphSchema.cs +++ b/src/graphql-aspnet/Schemas/GraphSchema.cs @@ -17,7 +17,6 @@ namespace GraphQL.AspNet.Schemas using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Configuration; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Schemas.TypeSystem.TypeCollections; @@ -61,6 +60,7 @@ public GraphSchema() this.Route = new SchemaItemPath(SchemaItemCollections.Schemas, graphName); this.Name = DEFAULT_NAME; + this.InternalName = this.GetType().FriendlyName(); this.Description = DEFAULT_DESCRIPTION; } @@ -79,6 +79,9 @@ public GraphSchema() /// public virtual string Name { get; set; } + /// + public virtual string InternalName { get; set; } + /// public virtual string Description { get; set; } diff --git a/src/graphql-aspnet/Schemas/GraphSchemaManager.cs b/src/graphql-aspnet/Schemas/GraphSchemaManager.cs deleted file mode 100644 index c874334c0..000000000 --- a/src/graphql-aspnet/Schemas/GraphSchemaManager.cs +++ /dev/null @@ -1,486 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Schemas -{ - using System; - using System.Collections.Generic; - using System.Linq; - using GraphQL.AspNet.Common; - using GraphQL.AspNet.Common.Extensions; - using GraphQL.AspNet.Configuration.Formatting; - using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Engine.TypeMakers; - using GraphQL.AspNet.Execution; - using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; - using GraphQL.AspNet.Internal.TypeTemplates; - using GraphQL.AspNet.Schemas.Structural; - using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Schemas.TypeSystem.Introspection; - using GraphQL.AspNet.Schemas.TypeSystem.Introspection.Fields; - using GraphQL.AspNet.Schemas.TypeSystem.Introspection.Model; - - /// - /// Assists with the creation of data - /// parsed from any or any manually added - /// to the this instance is managing. - /// - internal sealed class GraphSchemaManager - { - private readonly GraphNameFormatter _formatter; - - /// - /// Initializes a new instance of the class. - /// - /// The schema instance to be managed. - public GraphSchemaManager(ISchema schema) - { - this.Schema = Validation.ThrowIfNullOrReturn(schema, nameof(schema)); - _formatter = this.Schema.Configuration.DeclarationOptions.GraphNamingFormatter; - this.EnsureSchemaDependencies(); - this.EnsureGraphOperationType(GraphOperationType.Query); - } - - /// - /// Inspects the schema instance for any necessary dependencies - /// and adds them to itself (e.g. schema declared directives). - /// - private void EnsureSchemaDependencies() - { - // all schemas depend on String because of the __typename field - this.EnsureGraphType(); - - // ensure top level schema directives are accounted for - foreach (var directive in this.Schema.GetType().ExtractAppliedDirectives()) - { - this.Schema.AppliedDirectives.Add(directive); - } - - foreach (var appliedDirective in this.Schema.AppliedDirectives.Where(x => x.DirectiveType != null)) - { - this.EnsureGraphType( - appliedDirective.DirectiveType, - TypeKind.DIRECTIVE); - } - } - - /// - /// Adds the internal introspection fields to the query operation type if and only if the contained schema allows - /// it through its internal configuration. This method is idempotent. - /// - private void AddIntrospectionFields() - { - this.EnsureGraphOperationType(GraphOperationType.Query); - var queryField = this.Schema.Operations[GraphOperationType.Query]; - - // Note: introspection fields are defined by the graphql spec, no custom name or item formatting is allowed - // for Type and field name formatting. - // spec: https://graphql.github.io/graphql-spec/October2021/#sec-Schema-Introspection - if (!queryField.Fields.ContainsKey(Constants.ReservedNames.SCHEMA_FIELD)) - { - var introspectedSchema = new IntrospectedSchema(this.Schema); - queryField.Extend(new Introspection_SchemaField(introspectedSchema)); - queryField.Extend(new Introspection_TypeGraphField(introspectedSchema)); - - this.EnsureGraphType(typeof(string)); - this.EnsureGraphType(typeof(bool)); - this.Schema.KnownTypes.EnsureGraphType(new Introspection_DirectiveLocationType(), typeof(DirectiveLocation)); - this.Schema.KnownTypes.EnsureGraphType(new Introspection_DirectiveType(), typeof(IntrospectedDirective)); - this.Schema.KnownTypes.EnsureGraphType(new Introspection_EnumValueType(), typeof(IntrospectedEnumValue)); - this.Schema.KnownTypes.EnsureGraphType(new Introspection_FieldType(), typeof(IntrospectedField)); - this.Schema.KnownTypes.EnsureGraphType(new Introspection_InputValueType(), typeof(IntrospectedInputValueType)); - this.Schema.KnownTypes.EnsureGraphType(new Introspection_SchemaType(), typeof(IntrospectedSchema)); - this.Schema.KnownTypes.EnsureGraphType(new Introspection_TypeKindType(), typeof(TypeKind)); - this.Schema.KnownTypes.EnsureGraphType(new Introspection_TypeType(), typeof(IntrospectedType)); - } - } - - /// - /// Adds the built in directives supported by the graphql runtime. - /// - public void AddBuiltInDirectives() - { - foreach (var type in Constants.GlobalDirectives) - { - this.EnsureGraphType(type); - } - } - - /// - /// Adds the and all associated types and virtual types to the schema. - /// - /// The template fully describing the controller. - private void AddController(IGraphControllerTemplate gcd) - { - foreach (var action in gcd.Actions) - this.AddAction(action); - - foreach (var extension in gcd.Extensions) - this.AddTypeExtension(extension); - } - - /// - /// Adds the type extension to the schema for the configured concrete type. If the type - /// is not registered to the schema the field extension is queued for when it is added (if ever). - /// - /// The extension to add. - private void AddTypeExtension(IGraphFieldTemplate extension) - { - var fieldMaker = GraphQLProviders.GraphTypeMakerProvider.CreateFieldMaker(this.Schema); - var fieldResult = fieldMaker.CreateField(extension); - - if (fieldResult != null) - { - this.Schema.KnownTypes.EnsureGraphFieldExtension(extension.SourceObjectType, fieldResult.Field); - this.EnsureDependents(fieldResult); - } - } - - /// - /// Adds the to the schema. Any required parent fields - /// will be automatically created if necessary to ensure proper nesting. - /// - /// The action to add to the schema. - private void AddAction(IGraphFieldTemplate action) - { - if (this.Schema.Configuration.DeclarationOptions.AllowedOperations.Contains(action.Route.RootCollection.ToGraphOperationType())) - { - var operation = action.Route.RootCollection.ToGraphOperationType(); - this.EnsureGraphOperationType(operation); - var parentField = this.AddOrRetrieveControllerRoutePath(action); - this.AddActionAsField(parentField, action); - } - else - { - throw new ArgumentOutOfRangeException( - nameof(action), - $"The '{action.InternalFullName}' action's operation root ({action.Route.RootCollection}) is not " + - $"allowed by the target schema (Name: {this.Schema.Name})."); - } - } - - /// - /// Ensures that the root operation type (query, mutation etc.) exists on this schema and the associated virtual - /// type representing it also exists in the schema's type collection. - /// - /// Type of the operation. - private void EnsureGraphOperationType(GraphOperationType operationType) - { - if (operationType == GraphOperationType.Unknown) - { - throw new ArgumentOutOfRangeException($"The operation type '{operationType}' is " + - $"not supported by graphql."); - } - - if (!this.Schema.Operations.ContainsKey(operationType)) - { - var operation = new GraphOperation(operationType); - this.Schema.KnownTypes.EnsureGraphType(operation); - this.Schema.Operations.Add(operation.OperationType, operation); - } - } - - /// - /// Inspects the root and ensures that any intermediate, virtual fields - /// are accounted for and returns a reference to the immediate parent this action should be added to. - /// - /// The action. - /// IGraphField. - private IObjectGraphType AddOrRetrieveControllerRoutePath(IGraphFieldTemplate action) - { - var pathSegments = action.Route.GenerateParentPathSegments(); - - // loop through all parent path parts of this action - // creating virtual fields as necessary or using existing ones and adding on to them - IObjectGraphType parentType = this.Schema.Operations[action.Route.RootCollection.ToGraphOperationType()]; - - for (var i = 0; i < pathSegments.Count; i++) - { - var segment = pathSegments[i]; - var formattedName = _formatter.FormatFieldName(segment.Name); - if (parentType.Fields.ContainsKey(formattedName)) - { - var field = parentType[formattedName]; - var foundType = Schema.KnownTypes.FindGraphType(field.TypeExpression.TypeName); - - var ogt = foundType as IObjectGraphType; - if (ogt != null) - { - if (ogt.IsVirtual) - { - parentType = ogt; - continue; - } - - throw new GraphTypeDeclarationException( - $"The action '{action.Route}' attempted to nest itself under the {foundType.Kind} graph type '{foundType.Name}', which is returned by " + - $"the route '{field.Route}'. Actions can only be added to virtual graph types created by their parent controller."); - } - - if (foundType != null) - { - throw new GraphTypeDeclarationException( - $"The action '{action.Route.Path}' attempted to nest itself under the graph type '{foundType.Name}'. {foundType.Kind} graph types cannot " + - "accept fields."); - } - else - { - throw new GraphTypeDeclarationException( - $"The action '{action.Route.Path}' attempted to nest itself under the field '{field.Route}' but no graph type was found " + - "that matches its type."); - } - } - - parentType = this.CreateVirtualFieldOnParent( - parentType, - formattedName, - segment, - i == 0 ? action.Parent : null); - } - - return parentType; - } - - /// - /// Performs an out-of-band append of a new graph field to a parent. Accounts for type updates in this schema ONLY. - /// - /// the parent type to add the new field to. - /// Name of the field. - /// The path segment to represent the new field. - /// The definition item from which graph attributes should be used, if any. Attributes will be set to an empty string if not supplied. - /// The type associated with the field added to the parent type. - private IObjectGraphType CreateVirtualFieldOnParent( - IObjectGraphType parentType, - string fieldName, - SchemaItemPath path, - ISchemaItemTemplate definition = null) - { - var childField = new VirtualGraphField( - parentType, - fieldName, - path, - this.MakeSafeTypeNameFromRoutePath(path)) - { - IsDepreciated = false, - DepreciationReason = string.Empty, - Description = definition?.Description ?? string.Empty, - }; - - parentType.Extend(childField); - this.Schema.KnownTypes.EnsureGraphType(childField.AssociatedGraphType); - this.EnsureDependents(childField); - - return childField.AssociatedGraphType; - } - - /// - /// Makes the unique route being used for this virtual field type safe, removing special control characters - /// but retaining its uniqueness. - /// - /// The path. - /// System.String. - private string MakeSafeTypeNameFromRoutePath(SchemaItemPath path) - { - var segments = new List(); - foreach (var pathSegmentName in path) - { - switch (pathSegmentName) - { - case Constants.Routing.QUERY_ROOT: - segments.Add(Constants.ReservedNames.QUERY_TYPE_NAME); - break; - - case Constants.Routing.MUTATION_ROOT: - segments.Add(Constants.ReservedNames.MUTATION_TYPE_NAME); - break; - - case Constants.Routing.SUBSCRIPTION_ROOT: - segments.Add(Constants.ReservedNames.SUBSCRIPTION_TYPE_NAME); - break; - - default: - segments.Add(_formatter.FormatGraphTypeName(pathSegmentName)); - break; - } - } - - segments.Reverse(); - return string.Join("_", segments); - } - - /// - /// Iterates the given and adds - /// all found types to the type system for this . Generates - /// a field reference on the provided parent with a resolver pointing to the provided graph action. - /// - /// The parent which will own the generated action field. - /// The action. - private void AddActionAsField(IObjectGraphType parentType, IGraphFieldTemplate action) - { - // apend the action as a field on the parent - var maker = GraphQLProviders.GraphTypeMakerProvider.CreateFieldMaker(this.Schema); - var fieldResult = maker.CreateField(action); - - if (fieldResult != null) - { - if (parentType.Fields.ContainsKey(fieldResult.Field.Name)) - { - throw new GraphTypeDeclarationException( - $"The '{parentType.Kind}' graph type '{parentType.Name}' already contains a field named '{fieldResult.Field.Name}'. " + - $"The action method '{action.InternalFullName}' cannot be added to the graph type with the same name."); - } - - parentType.Extend(fieldResult.Field); - this.EnsureDependents(fieldResult); - } - } - - /// - /// Inspects and adds the given type to the schema as a graph type or a registered controller depending - /// on the type. The type kind will be automatically inferred or an error will be thrown. - /// - /// The type of the item to add to the schema. - public void EnsureGraphType() - { - this.EnsureGraphType(typeof(TItem)); - } - - /// - /// Inspects and adds the given type to the schema as a graph type or a registered controller depending - /// on the type. The type kind will be automatically inferred or an error will be thrown. - /// - /// The type of the item to add to the schema. - /// The kind of graph type to create from the supplied concrete type. - /// Only necessary to differentiate between OBJECT and INPUT_OBJECT types. - public void EnsureGraphType(TypeKind? kind = null) - { - this.EnsureGraphType(typeof(TItem), kind); - } - - /// - /// Adds the given type to the schema as a graph type or a registered controller depending - /// on the type. - /// - /// The concrete type to add. - /// The kind of graph type to create from the supplied concrete type. If not supplied the concrete type will - /// attempt to auto assign a type of scalar, enum or object as necessary. - public void EnsureGraphType(Type type, TypeKind? kind = null) - { - Validation.ThrowIfNull(type, nameof(type)); - try - { - this.EnsureGraphTypeInternal(type, kind); - } - catch (GraphTypeDeclarationException ex) - { - if (ex.FailedObjectType == type) - throw; - - // wrap a thrown exception to be nested within this type that was being parsed - throw new GraphTypeDeclarationException( - $"An error occured while trying to add a dependent of '{type.FriendlyName()}' " + - $"to the target schema. See inner exception for details.", - type, - ex); - } - } - - private void EnsureGraphTypeInternal(Type type, TypeKind? kind = null) - { - Validation.ThrowIfNull(type, nameof(type)); - if (Validation.IsCastable(type)) - { - if (GraphQLProviders.TemplateProvider.ParseType(type) is IGraphControllerTemplate controllerDefinition) - this.AddController(controllerDefinition); - - return; - } - - type = GraphValidation.EliminateWrappersFromCoreType(type); - - // if the type is already registered, early exit no point in running it through again - var actualKind = GraphValidation.ResolveTypeKindOrThrow(type, kind); - if (this.Schema.KnownTypes.Contains(type, actualKind)) - return; - - GraphTypeCreationResult makerResult = null; - var maker = GraphQLProviders.GraphTypeMakerProvider.CreateTypeMaker(this.Schema, actualKind); - if (maker != null) - { - // if a maker can be assigned for this graph type - // create the graph type directly - makerResult = maker.CreateGraphType(type); - } - else if (Validation.IsCastable(type)) - { - // if this type represents a well-known union proxy try and add in the union proxy - // directly - var unionProxy = GraphQLProviders.GraphTypeMakerProvider.CreateUnionProxyFromType(type); - if (unionProxy != null) - { - var unionMaker = GraphQLProviders.GraphTypeMakerProvider.CreateUnionMaker(this.Schema); - makerResult = unionMaker.CreateUnionFromProxy(unionProxy); - } - } - - if (makerResult != null) - { - this.Schema.KnownTypes.EnsureGraphType(makerResult.GraphType, makerResult.ConcreteType); - this.EnsureDependents(makerResult); - } - } - - /// - /// Ensures the dependents in teh given collection are part of the target . - /// - /// The dependency set. - private void EnsureDependents(IGraphItemDependencies dependencySet) - { - foreach (var abstractType in dependencySet.AbstractGraphTypes) - { - this.Schema.KnownTypes.EnsureGraphType(abstractType); - } - - foreach (var dependent in dependencySet.DependentTypes) - { - this.EnsureGraphType(dependent.Type, dependent.ExpectedKind); - } - } - - /// - /// Clears, builds and caches the introspection metadata to describe this schema. If introspection - /// fields have not been added to the schema this method does nothing. No changes to the schema - /// items themselves happens during this method. - /// - public void RebuildIntrospectionData() - { - if (this.Schema.Configuration.DeclarationOptions.DisableIntrospection) - return; - - this.EnsureGraphOperationType(GraphOperationType.Query); - this.AddIntrospectionFields(); - - var queryType = this.Schema.Operations[GraphOperationType.Query]; - if (!queryType.Fields.ContainsKey(Constants.ReservedNames.SCHEMA_FIELD)) - return; - - var field = queryType.Fields[Constants.ReservedNames.SCHEMA_FIELD] as Introspection_SchemaField; - field.IntrospectedSchema.Rebuild(); - } - - /// - /// Gets the schema being managed and built by this instance. - /// - /// The schema. - public ISchema Schema { get; } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/GraphTypeExpression_Statics.cs b/src/graphql-aspnet/Schemas/GraphTypeExpression_Statics.cs index 238876dff..2fb145540 100644 --- a/src/graphql-aspnet/Schemas/GraphTypeExpression_Statics.cs +++ b/src/graphql-aspnet/Schemas/GraphTypeExpression_Statics.cs @@ -15,7 +15,7 @@ namespace GraphQL.AspNet.Schemas using System.Linq; using GraphQL.AspNet.Common; using GraphQL.AspNet.Execution.Parsing.Lexing.Tokens; - using GraphQL.AspNet.Internal; + using GraphQL.AspNet.Execution.RulesEngine.RuleSets.DocumentValidation.QueryFragmentSteps; using GraphQL.AspNet.Schemas.TypeSystem; /// @@ -263,41 +263,43 @@ public static GraphTypeExpression FromType(Type typeToCheck, string typeName, Me /// /// The target expression to which a value is being given. /// The expression of the value to be supplied to the target. + /// When true, the inner type name of the expression must match exactly (case-sensitive) to + /// that of the expression. /// true if a value of the expression is /// compatiable and could be supplied or used as an input value to an item of the expression, false otherwise. - public static bool AreTypesCompatiable(GraphTypeExpression target, GraphTypeExpression supplied) + public static bool AreTypesCompatiable(GraphTypeExpression target, GraphTypeExpression supplied, bool matchTypeName = true) { if (target == null || supplied == null) return false; - // when root types don't match they can never be compatible - if (target.TypeName != supplied.TypeName) - return false; - if (!target.IsNullable) { if (supplied.IsNullable) return false; - return AreTypesCompatiable(target.UnWrapExpression(), supplied.UnWrapExpression()); + return AreTypesCompatiable(target.UnWrapExpression(), supplied.UnWrapExpression(), matchTypeName); } else if (!supplied.IsNullable) { - return AreTypesCompatiable(target, supplied.UnWrapExpression()); + return AreTypesCompatiable(target, supplied.UnWrapExpression(), matchTypeName); } else if (target.IsListOfItems) { if (!supplied.IsListOfItems) return false; - return AreTypesCompatiable(target.UnWrapExpression(), supplied.UnWrapExpression()); + return AreTypesCompatiable(target.UnWrapExpression(), supplied.UnWrapExpression(), matchTypeName); } else if (supplied.IsListOfItems) { return false; } - return target == supplied; + // when root types don't match they can never be compatible + if (matchTypeName) + return target == supplied; + + return true; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/GraphTypeNames.cs b/src/graphql-aspnet/Schemas/GraphTypeNames.cs similarity index 97% rename from src/graphql-aspnet/Internal/GraphTypeNames.cs rename to src/graphql-aspnet/Schemas/GraphTypeNames.cs index 2c06d6775..4cedfd4ee 100644 --- a/src/graphql-aspnet/Internal/GraphTypeNames.cs +++ b/src/graphql-aspnet/Schemas/GraphTypeNames.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal +namespace GraphQL.AspNet.Schemas { using System; using System.Collections.Concurrent; @@ -97,9 +97,9 @@ public static string ParseName(Type type, TypeKind kind) return typeName; type = GraphValidation.EliminateWrappersFromCoreType(type); - if (GraphQLProviders.ScalarProvider.IsScalar(type)) + if (GlobalTypes.IsBuiltInScalar(type)) { - typeName = GraphQLProviders.ScalarProvider.RetrieveScalarName(type); + typeName = GlobalTypes.CreateScalarInstanceOrThrow(type).Name; } else if (type.IsEnum) { diff --git a/src/graphql-aspnet/Internal/GraphValidation.cs b/src/graphql-aspnet/Schemas/GraphValidation.cs similarity index 87% rename from src/graphql-aspnet/Internal/GraphValidation.cs rename to src/graphql-aspnet/Schemas/GraphValidation.cs index 2f896ad98..e0f56db33 100644 --- a/src/graphql-aspnet/Internal/GraphValidation.cs +++ b/src/graphql-aspnet/Schemas/GraphValidation.cs @@ -7,21 +7,20 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Internal +namespace GraphQL.AspNet.Schemas { using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; - using System.Threading; using System.Threading.Tasks; using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Directives; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.TypeSystem; using Microsoft.AspNetCore.Authorization; @@ -44,41 +43,6 @@ public static IEnumerable RetrieveSecurityPolicies(ICustomAttrib return attributeProvider.GetCustomAttributes(false).OfType(); } - /// - /// Determines whether the given type is parsable and usable by this graphql library. - /// - /// The type to parse. - /// true if the type is parsable; otherwise, false. - public static bool IsParseableType(Type type) - { - if (Validation.IsCastable(type)) - return false; - - return GraphValidation.IsValidGraphType(type); - } - - /// - /// Resolves the of the provided concrete type. If provided, the override - /// value will be used by default if the type does not represent a reserved . If an override value is supplied - /// but the type cannot be corerced into said kind an exception is thrown. - /// - /// The type to check. - /// The override value to use. Pass null to attempt default resolution. - /// TypeKind. - public static TypeKind ResolveTypeKindOrThrow(Type type, TypeKind? overrideValue = null) - { - var outKind = ResolveTypeKind(type, overrideValue); - if (overrideValue.HasValue && outKind != overrideValue.Value && !overrideValue.Value.CanBecome(outKind)) - { - throw new GraphTypeDeclarationException( - $"The concrete type '{type.FriendlyName()}' was to be resolved as a graph type of kind '{overrideValue.Value}' but " + - $"can only be assigned as '{outKind.ToString()}'", - type); - } - - return outKind; - } - /// /// Attempts to classify the provided to determine its /// (enum, scalar, object etc.). If provided, the override @@ -93,30 +57,23 @@ public static TypeKind ResolveTypeKind(Type type, TypeKind? overrideValue = null { if (type != null) { - if (GraphQLProviders.ScalarProvider.IsScalar(type)) - { - return TypeKind.SCALAR; - } - if (type.IsEnum) - { return TypeKind.ENUM; - } if (Validation.IsCastable(type)) - { return TypeKind.UNION; - } if (type.IsInterface) - { return TypeKind.INTERFACE; - } + + if (Validation.IsCastable(type)) + return TypeKind.CONTROLLER; if (Validation.IsCastable(type)) - { return TypeKind.DIRECTIVE; - } + + if (GlobalTypes.IsBuiltInScalar(type)) + return TypeKind.SCALAR; } return overrideValue ?? TypeKind.OBJECT; @@ -213,6 +170,9 @@ public static void EnsureGraphNameOrThrow(string internalName, string nameToTest /// true if the supplied name represents a valid graph name; otherwise, false. public static bool IsValidGraphName(string nameToTest) { + if (nameToTest == null) + return false; + return Constants.RegExPatterns.NameRegex.IsMatch(nameToTest); } @@ -359,6 +319,19 @@ public static bool IsValidGraphType(Type type, bool throwOnFailure = false) return true; } + /// + /// Determines if the provided type, if converted to a graph type, must be a + /// leaf type and will never contain fields. + /// + /// The type to check. + /// true if the type must be converted to a leaf graph type, false otherwise. + public static bool MustBeLeafType(Type typeToCheck) + { + Validation.ThrowIfNull(typeToCheck, nameof(typeToCheck)); + + return typeToCheck.IsPrimitive || typeToCheck.IsEnum; + } + /// /// Checks if the concrete type MUST be provided on the object graph or if it can be represented with 'null'. /// diff --git a/src/graphql-aspnet/Schemas/SchemaLanguageGenerator.cs b/src/graphql-aspnet/Schemas/SchemaLanguageGenerator.cs index 250c68225..3df80e902 100644 --- a/src/graphql-aspnet/Schemas/SchemaLanguageGenerator.cs +++ b/src/graphql-aspnet/Schemas/SchemaLanguageGenerator.cs @@ -17,7 +17,6 @@ namespace GraphQL.AspNet.Schemas using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Common.Generics; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; using GraphQL.AspNet.Schemas.TypeSystem; /// @@ -149,10 +148,10 @@ private string SerializeInputObject(object obj, IInputObjectGraphType inputObjec var getters = InstanceFactory.CreatePropertyGetterInvokerCollection(obj.GetType()); foreach (var field in inputObjectGraphType.Fields) { - if (!getters.ContainsKey(field.InternalName)) + if (!getters.ContainsKey(field.DeclaredName)) continue; - var getter = getters[field.InternalName]; + var getter = getters[field.DeclaredName]; builder.Append(field.Name); builder.Append(Constants.QueryLanguage.FieldValueSeperator); builder.Append(" "); diff --git a/src/graphql-aspnet/Schemas/Structural/EnumValueCollection.cs b/src/graphql-aspnet/Schemas/Structural/EnumValueCollection.cs index 0c5481cc6..4893ae636 100644 --- a/src/graphql-aspnet/Schemas/Structural/EnumValueCollection.cs +++ b/src/graphql-aspnet/Schemas/Structural/EnumValueCollection.cs @@ -51,13 +51,13 @@ public void Add(IEnumValue value) } _enumValuesByName.Add(value.Name, value); - _enumValuesByInternalLabel.Add(value.InternalLabel, value); + _enumValuesByInternalLabel.Add(value.DeclaredLabel, value); } /// - /// Removes the specified name if it exists. When found, the removed item is returned. + /// Removes the specified enum value if it exists. When found, the removed item is returned. /// - /// The name of the item to remove. + /// The name of the enum value, as it exists in the schema. /// IEnumValue. public IEnumValue Remove(string name) { @@ -66,8 +66,8 @@ public IEnumValue Remove(string name) var removedOption = _enumValuesByName[name]; _enumValuesByName.Remove(name); - if (_enumValuesByInternalLabel.ContainsKey(removedOption.InternalLabel)) - _enumValuesByInternalLabel.Remove(removedOption.InternalLabel); + if (_enumValuesByInternalLabel.ContainsKey(removedOption.DeclaredLabel)) + _enumValuesByInternalLabel.Remove(removedOption.DeclaredLabel); return removedOption; } @@ -83,9 +83,9 @@ public IEnumValue FindByEnumValue(object enumValue) if (_graphType.ValidateObject(enumValue)) { - var internalLabel = Enum.GetName(_graphType.ObjectType, enumValue); - if (_enumValuesByInternalLabel.ContainsKey(internalLabel)) - return _enumValuesByInternalLabel[internalLabel]; + var declaredLabel = Enum.GetName(_graphType.ObjectType, enumValue); + if (_enumValuesByInternalLabel.ContainsKey(declaredLabel)) + return _enumValuesByInternalLabel[declaredLabel]; } return null; diff --git a/src/graphql-aspnet/Schemas/Structural/GraphFieldArgumentCollection.cs b/src/graphql-aspnet/Schemas/Structural/GraphFieldArgumentCollection.cs index 519cb6692..097d44ce3 100644 --- a/src/graphql-aspnet/Schemas/Structural/GraphFieldArgumentCollection.cs +++ b/src/graphql-aspnet/Schemas/Structural/GraphFieldArgumentCollection.cs @@ -26,7 +26,7 @@ internal class GraphFieldArgumentCollection : IGraphArgumentCollection { private readonly ISchemaItem _owner; private readonly OrderedDictionary _arguments; - private IGraphArgument _sourceArgument; + private readonly Dictionary _argumentByInternalName; /// /// Initializes a new instance of the class. @@ -37,16 +37,16 @@ public GraphFieldArgumentCollection(ISchemaItem owner) { _owner = Validation.ThrowIfNullOrReturn(owner, nameof(owner)); _arguments = new OrderedDictionary(StringComparer.Ordinal); + _argumentByInternalName = new Dictionary(); } /// public IGraphArgument AddArgument(IGraphArgument argument) { Validation.ThrowIfNull(argument, nameof(argument)); - _arguments.Add(argument.Name, argument); - if (argument.ArgumentModifiers.IsSourceParameter() && _sourceArgument == null) - _sourceArgument = argument; + _arguments.Add(argument.Name, argument); + _argumentByInternalName.Add(argument.ParameterName, argument); return argument; } @@ -54,20 +54,20 @@ public IGraphArgument AddArgument(IGraphArgument argument) /// Adds the input argument to the growing collection. /// /// The name of the argument in the object graph. - /// Name of the argument as it was defined in the code. + /// The fully qualfiied name of the argument as it was defined in the code. /// The type expression representing how this value is represented in this graph schema. /// The concrete type this field is on the server. /// IGraphFieldArgument. public IGraphArgument AddArgument( string name, - string internalName, + string internalFullName, GraphTypeExpression typeExpression, Type concreteType) { var argument = new VirtualGraphFieldArgument( _owner, name, - internalName, + internalFullName, typeExpression, _owner.Route.CreateChild(name), concreteType, @@ -80,7 +80,7 @@ public IGraphArgument AddArgument( /// Adds the input argument to the growing collection. /// /// The name of the argument in the object graph. - /// Name of the argument as it was defined in the code. + /// The fully qualified name of the argument as it was defined in the code. /// The type expression representing how this value is represented in this graph schema. /// The concrete type this field is on the server. /// The default value to set for the argument. If null, indicates @@ -88,7 +88,7 @@ public IGraphArgument AddArgument( /// IGraphFieldArgument. public IGraphArgument AddArgument( string name, - string internalName, + string internalFullName, GraphTypeExpression typeExpression, Type concreteType, object defaultValue) @@ -96,17 +96,28 @@ public IGraphArgument AddArgument( var argument = new VirtualGraphFieldArgument( _owner, name, - internalName, + internalFullName, typeExpression, _owner.Route.CreateChild(name), concreteType, true, - defaultValue, - GraphArgumentModifiers.None); + defaultValue); return this.AddArgument(argument); } + /// + public void Remove(IGraphArgument arg) + { + if (arg == null) + return; + + if (_argumentByInternalName.ContainsKey(arg.ParameterName)) + _argumentByInternalName.Remove(arg.ParameterName); + if (_arguments.ContainsKey(arg.Name)) + _arguments.Remove(arg.Name); + } + /// public bool ContainsKey(string argumentName) { @@ -125,6 +136,15 @@ public IGraphArgument FindArgument(string argumentName) return null; } + /// + public IGraphArgument FindArgumentByParameterName(string parameterName) + { + if (_argumentByInternalName.ContainsKey(parameterName)) + return _argumentByInternalName[parameterName]; + + return null; + } + /// public IGraphArgument this[string argumentName] => _arguments[argumentName]; @@ -134,9 +154,6 @@ public IGraphArgument FindArgument(string argumentName) /// public int Count => _arguments.Count; - /// - public IGraphArgument SourceDataArgument => _sourceArgument; - /// public IEnumerator GetEnumerator() { diff --git a/src/graphql-aspnet/Schemas/Structural/GraphFieldCollection.cs b/src/graphql-aspnet/Schemas/Structural/GraphFieldCollection.cs index d3311a846..1a8b91883 100644 --- a/src/graphql-aspnet/Schemas/Structural/GraphFieldCollection.cs +++ b/src/graphql-aspnet/Schemas/Structural/GraphFieldCollection.cs @@ -17,9 +17,8 @@ namespace GraphQL.AspNet.Schemas.Structural using GraphQL.AspNet.Common; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; - using GraphQL.AspNet.Internal.Resolvers; using GraphQL.AspNet.Schemas.TypeSystem; /// @@ -92,10 +91,11 @@ internal IGraphField AddField( { var field = new MethodGraphField( fieldName, + "GraphQLCustomInternalField", typeExpression, route, - GraphValidation.EliminateNextWrapperFromCoreType(typeof(TReturn)), typeof(TReturn), + GraphValidation.EliminateNextWrapperFromCoreType(typeof(TReturn)), FieldResolutionMode.PerSourceItem, new FunctionGraphFieldResolver(resolver)); field.Description = description; diff --git a/src/graphql-aspnet/Schemas/Structural/SchemaItemPath.cs b/src/graphql-aspnet/Schemas/Structural/SchemaItemPath.cs index dafe15709..856ccf95d 100644 --- a/src/graphql-aspnet/Schemas/Structural/SchemaItemPath.cs +++ b/src/graphql-aspnet/Schemas/Structural/SchemaItemPath.cs @@ -44,6 +44,7 @@ static SchemaItemPath() private bool _pathInitialized; private SchemaItemCollections _rootCollection; private string _path; + private string _pathMinusCollection; private SchemaItemPath _parentField; private string _name; private bool _isTopLevelField; @@ -72,6 +73,7 @@ public SchemaItemPath(string fullPath) _parentField = null; _isTopLevelField = false; _path = string.Empty; + _pathMinusCollection = string.Empty; _name = string.Empty; _isValid = false; _rootCollection = SchemaItemCollections.Unknown; @@ -91,7 +93,6 @@ private void EnsurePathInitialized() // split the path into its fragments List pathFragments = workingPath.Split(new[] { RouteConstants.PATH_SEPERATOR }, StringSplitOptions.None).ToList(); - switch (pathFragments[0]) { case RouteConstants.QUERY_ROOT: @@ -149,6 +150,12 @@ private void EnsurePathInitialized() _isTopLevelField = pathFragments.Count == 1 || (pathFragments.Count == 2 && _rootCollection > SchemaItemCollections.Unknown); // e.g. "[query]/name" _isValid = _name.Length > 0; _path = this.GeneratePathString(pathFragments); + + if (_rootCollection == SchemaItemCollections.Unknown) + _pathMinusCollection = this.GeneratePathString(pathFragments); + else + _pathMinusCollection = $"{RouteConstants.PATH_SEPERATOR}{this.GeneratePathString(pathFragments.Skip(1).ToList())}"; + _pathInitialized = true; } } @@ -193,6 +200,17 @@ public bool HasChildRoute(SchemaItemPath route) return (route?.Path?.Length ?? 0) > 0 && route.Path.StartsWith(this.Path); } + /// + /// Deconstructs the instance into its constituent parts. + /// + /// The collection this item belongs to. + /// The path within the collection that points to this item. + public void Deconstruct(out SchemaItemCollections collection, out string path) + { + collection = this.RootCollection; + path = _pathMinusCollection; + } + /// /// Gets the raw string provided to this instance. /// @@ -312,40 +330,6 @@ public IEnumerator GetEnumerator() while (item != null); } - /// - /// Normalizes a given route path fragement removing duplicate seperators, ensuring starting and tail end seperators - /// are correct etc. - /// - /// The fragment to normalize. - /// System.String. - public static string NormalizeFragment(string routefragment) - { - // ensure a working path - routefragment = routefragment?.Trim() ?? string.Empty; - routefragment = routefragment.Replace(RouteConstants.ALT_PATH_SEPERATOR, RouteConstants.PATH_SEPERATOR); - - // doubled up seperators may happen if a 3rd party is joining route fragments (especially if the seperator is a '/') - // trim them down - while (routefragment.Contains(RouteConstants.DOUBLE_PATH_SEPERATOR)) - { - routefragment = routefragment.Replace(RouteConstants.DOUBLE_PATH_SEPERATOR, RouteConstants.PATH_SEPERATOR); - } - - // if the path ends with or starts with a seperator (indicating a potential group segment) - // thats fine but is not needed to identify the segment in and of itself, trim it off - while (routefragment.EndsWith(RouteConstants.PATH_SEPERATOR)) - { - routefragment = routefragment.Substring(0, routefragment.Length - RouteConstants.PATH_SEPERATOR.Length); - } - - while (routefragment.StartsWith(RouteConstants.PATH_SEPERATOR)) - { - routefragment = routefragment.Substring(routefragment.IndexOf(RouteConstants.PATH_SEPERATOR, StringComparison.Ordinal) + RouteConstants.PATH_SEPERATOR.Length); - } - - return routefragment; - } - /// /// Creates a list of all the fully qualified parent paths this path is nested under. /// diff --git a/src/graphql-aspnet/Schemas/Structural/SchemaItemPath_Statics.cs b/src/graphql-aspnet/Schemas/Structural/SchemaItemPath_Statics.cs index 44dacf668..68a0e3d57 100644 --- a/src/graphql-aspnet/Schemas/Structural/SchemaItemPath_Statics.cs +++ b/src/graphql-aspnet/Schemas/Structural/SchemaItemPath_Statics.cs @@ -9,6 +9,7 @@ namespace GraphQL.AspNet.Schemas.Structural { + using System; using System.Linq; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution; @@ -41,6 +42,40 @@ public static string Join(SchemaItemCollections fieldType, params string[] route return SchemaItemPath.Join(fieldType.ToRouteRoot().AsEnumerable().Concat(routeSegments).ToArray()); } + /// + /// Normalizes a given route path fragement removing duplicate seperators, ensuring starting and tail end seperators + /// are correct etc. + /// + /// The fragment to normalize. + /// System.String. + public static string NormalizeFragment(string routefragment) + { + // ensure a working path + routefragment = routefragment?.Trim() ?? string.Empty; + routefragment = routefragment.Replace(RouteConstants.ALT_PATH_SEPERATOR, RouteConstants.PATH_SEPERATOR); + + // doubled up seperators may happen if a 3rd party is joining route fragments (especially if the seperator is a '/') + // trim them down + while (routefragment.Contains(RouteConstants.DOUBLE_PATH_SEPERATOR)) + { + routefragment = routefragment.Replace(RouteConstants.DOUBLE_PATH_SEPERATOR, RouteConstants.PATH_SEPERATOR); + } + + // if the path ends with or starts with a seperator (indicating a potential group segment) + // thats fine but is not needed to identify the segment in and of itself, trim it off + while (routefragment.EndsWith(RouteConstants.PATH_SEPERATOR)) + { + routefragment = routefragment.Substring(0, routefragment.Length - RouteConstants.PATH_SEPERATOR.Length); + } + + while (routefragment.StartsWith(RouteConstants.PATH_SEPERATOR)) + { + routefragment = routefragment.Substring(routefragment.IndexOf(RouteConstants.PATH_SEPERATOR, StringComparison.Ordinal) + RouteConstants.PATH_SEPERATOR.Length); + } + + return routefragment; + } + /// /// Implements the == operator. /// diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Directive.cs b/src/graphql-aspnet/Schemas/TypeSystem/Directive.cs index 4278f19cb..46f82dfc9 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Directive.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Directive.cs @@ -12,9 +12,7 @@ namespace GraphQL.AspNet.Schemas.TypeSystem using System; using System.Collections.Generic; using System.Diagnostics; - using System.Linq; using GraphQL.AspNet.Common; - using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas.Structural; @@ -31,6 +29,7 @@ public class Directive : IDirective /// Initializes a new instance of the class. /// /// The name of the directive as it appears in the schema. + /// The internal name of the directive as defined in the source code. /// The locations where this directive is valid. /// The concrete type of the directive. /// The route path that identifies this directive. @@ -41,6 +40,7 @@ public class Directive : IDirective /// that must be passed before this directive can be invoked. public Directive( string name, + string internalName, DirectiveLocation locations, Type directiveType, SchemaItemPath route, @@ -55,7 +55,7 @@ public Directive( this.Publish = true; this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); this.ObjectType = Validation.ThrowIfNullOrReturn(directiveType, nameof(directiveType)); - this.InternalName = this.ObjectType.FriendlyName(); + this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalName, nameof(internalName)); this.AppliedDirectives = new AppliedDirectiveCollection(this); this.IsRepeatable = isRepeatable; diff --git a/src/graphql-aspnet/Schemas/TypeSystem/EnumGraphType.cs b/src/graphql-aspnet/Schemas/TypeSystem/EnumGraphType.cs index cc5074fb3..5513a0bcb 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/EnumGraphType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/EnumGraphType.cs @@ -13,9 +13,9 @@ namespace GraphQL.AspNet.Schemas.TypeSystem using System.Diagnostics; using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.Resolvers; using GraphQL.AspNet.Schemas.Structural; /// @@ -31,11 +31,17 @@ public class EnumGraphType : IEnumGraphType /// Initializes a new instance of the class. /// /// The name to assign to this enumeration in the graph. + /// The internal name of this graph type as its assigned in source code. /// Type of the enum. /// The route path that identifies this enum type. /// The directives to apply to this enum type. - public EnumGraphType(string name, Type enumType, SchemaItemPath route, IAppliedDirectiveCollection directives = null) - : this(name, enumType, route, new EnumLeafValueResolver(enumType), directives) + public EnumGraphType( + string name, + string internalName, + Type enumType, + SchemaItemPath route, + IAppliedDirectiveCollection directives = null) + : this(name, internalName, enumType, route, new EnumLeafValueResolver(enumType), directives) { } @@ -43,23 +49,26 @@ public EnumGraphType(string name, Type enumType, SchemaItemPath route, IAppliedD /// Initializes a new instance of the class. /// /// The name to assign to this enumeration in the graph. + /// The internal name of this graph type as its assigned in source code. /// Type of the enum. /// The route path that identifies this enum type. /// The resolver. /// The directives to apply to this enum type. public EnumGraphType( string name, + string internalName, Type enumType, SchemaItemPath route, ILeafValueResolver resolver, IAppliedDirectiveCollection directives = null) { - this.Name = Validation.ThrowIfNullEmptyOrReturn(name, nameof(name)); + this.Name = Validation.ThrowIfNullWhiteSpaceOrReturn(name, nameof(name)); + this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalName, nameof(internalName)); + this.ObjectType = Validation.ThrowIfNullOrReturn(enumType, nameof(enumType)); this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); this.SourceResolver = Validation.ThrowIfNullOrReturn(resolver, nameof(resolver)); - this.InternalName = this.ObjectType.FriendlyName(); this.Publish = true; this.AppliedDirectives = directives?.Clone(this) ?? new AppliedDirectiveCollection(this); diff --git a/src/graphql-aspnet/Schemas/TypeSystem/EnumValue.cs b/src/graphql-aspnet/Schemas/TypeSystem/EnumValue.cs index 68d02caf4..119295311 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/EnumValue.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/EnumValue.cs @@ -26,19 +26,22 @@ public class EnumValue : IEnumValue /// /// The parent enum graph type that owns this value. /// The value. + /// The internal name assigned to this enum value. Typically the same as + /// but can be customized by the developer for reporting purposes. /// The description. /// The route path that uniquely identifies this enum option. /// The value of the enum as its declared in .NET. - /// A string representation of label applied to the enum value in .NET. + /// A string representation of label declared on the enum value in .NET. /// The set of directives to execute /// against this option when it is added to the schema. public EnumValue( IEnumGraphType parent, string name, + string internalName, string description, SchemaItemPath route, object internalValue, - string internalLabel, + string declaredLabel, IAppliedDirectiveCollection directives = null) { this.Parent = Validation.ThrowIfNullOrReturn(parent, nameof(parent)); @@ -46,8 +49,9 @@ public EnumValue( this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); this.Description = description?.Trim(); this.AppliedDirectives = directives?.Clone(this) ?? new AppliedDirectiveCollection(this); - this.InternalValue = Validation.ThrowIfNullOrReturn(internalValue, nameof(internalValue)); - this.InternalLabel = Validation.ThrowIfNullWhiteSpaceOrReturn(internalLabel, nameof(internalLabel)); + this.DeclaredValue = Validation.ThrowIfNullOrReturn(internalValue, nameof(internalValue)); + this.DeclaredLabel = Validation.ThrowIfNullWhiteSpaceOrReturn(declaredLabel, nameof(declaredLabel)); + this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalName, nameof(internalName)); if (Constants.QueryLanguage.IsReservedKeyword(this.Name)) { @@ -78,9 +82,12 @@ public EnumValue( public IEnumGraphType Parent { get; } /// - public object InternalValue { get; } + public object DeclaredValue { get; } /// - public string InternalLabel { get; } + public string DeclaredLabel { get; } + + /// + public string InternalName { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/TypeSystem/ExtendableGraphTypeExtensions.cs b/src/graphql-aspnet/Schemas/TypeSystem/ExtendableGraphTypeExtensions.cs index 41c702391..8fa73f419 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/ExtendableGraphTypeExtensions.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/ExtendableGraphTypeExtensions.cs @@ -12,9 +12,8 @@ namespace GraphQL.AspNet.Schemas.TypeSystem using System.Threading.Tasks; using GraphQL.AspNet.Common; using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; - using GraphQL.AspNet.Internal.Resolvers; using GraphQL.AspNet.Schemas.Structural; /// @@ -66,16 +65,17 @@ public static IGraphField Extend( where TSource : class { Validation.ThrowIfNullOrReturn(graphType, nameof(graphType)); - Validation.ThrowIfNullWhiteSpaceOrReturn(fieldName, nameof(fieldName)); + fieldName = Validation.ThrowIfNullWhiteSpaceOrReturn(fieldName, nameof(fieldName)); var fieldRoute = graphType.Route.CreateChild(fieldName); var field = new MethodGraphField( fieldName, + $"GraphQLExtendedField", typeExpression, fieldRoute, - GraphValidation.EliminateNextWrapperFromCoreType(typeof(TReturn)), typeof(TReturn), + GraphValidation.EliminateNextWrapperFromCoreType(typeof(TReturn)), FieldResolutionMode.PerSourceItem, new FunctionGraphFieldResolver(resolver)); field.Description = description; diff --git a/src/graphql-aspnet/Schemas/TypeSystem/GlobalTypes_Scalars.cs b/src/graphql-aspnet/Schemas/TypeSystem/GlobalTypes_Scalars.cs new file mode 100644 index 000000000..0b7f4367a --- /dev/null +++ b/src/graphql-aspnet/Schemas/TypeSystem/GlobalTypes_Scalars.cs @@ -0,0 +1,370 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.TypeSystem +{ + using System; + using System.Collections.Generic; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Common.Generics; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + + /// + /// A map of .NET types and their related built in scalar types and unions. + /// + public static partial class GlobalTypes + { + private static readonly Dictionary _scalarGraphTypeTypesByConcreteType; + private static readonly Dictionary _scalarsByName; + private static readonly HashSet _fixedNamedScalars; + private static readonly HashSet _allBuiltInScalars; + + static GlobalTypes() + { + _scalarGraphTypeTypesByConcreteType = new Dictionary(); + _scalarsByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + _fixedNamedScalars = new HashSet(); + _allBuiltInScalars = new HashSet(); + + // specification defined scalars (cannot be altered) + ValidateAndRegisterBuiltInScalar(true); + ValidateAndRegisterBuiltInScalar(true); + ValidateAndRegisterBuiltInScalar(true); + ValidateAndRegisterBuiltInScalar(true); + ValidateAndRegisterBuiltInScalar(true); + + // other helpful scalars added to the library for + // convience with .NET + ValidateAndRegisterBuiltInScalar(); + ValidateAndRegisterBuiltInScalar(); + ValidateAndRegisterBuiltInScalar(); + ValidateAndRegisterBuiltInScalar(); + ValidateAndRegisterBuiltInScalar(); + ValidateAndRegisterBuiltInScalar(); + ValidateAndRegisterBuiltInScalar(); + ValidateAndRegisterBuiltInScalar(); + ValidateAndRegisterBuiltInScalar(); + ValidateAndRegisterBuiltInScalar(); + ValidateAndRegisterBuiltInScalar(); + ValidateAndRegisterBuiltInScalar(); + ValidateAndRegisterBuiltInScalar(); + ValidateAndRegisterBuiltInScalar(); + ValidateAndRegisterBuiltInScalar(); + } + + private static void ValidateAndRegisterBuiltInScalar(bool isFixedName = false) + where T : IScalarGraphType + { + var instance = CreateScalarInstanceOrThrow(typeof(T)); + if (_scalarGraphTypeTypesByConcreteType.TryGetValue(instance.ObjectType, out Type registeredType1)) + { + throw new GraphTypeDeclarationException( + $"The scalar '{typeof(T).FriendlyName()}' is attempting to register a known type of '{instance.ObjectType.FriendlyName()}' but it is " + + $"already reserved by the scalar '{registeredType1.FriendlyName()}'. Built in scalar type mappings must be unique."); + } + + if (_scalarsByName.TryGetValue(instance.Name, out Type registeredType)) + { + throw new GraphTypeDeclarationException( + $"The scalar '{typeof(T).FriendlyName()}' is attempting to register with the name '{instance.Name}' but it is " + + $"already reserved by the scalar '{registeredType.FriendlyName()}'. Built in scalar type names must be globally unique."); + } + + _scalarGraphTypeTypesByConcreteType.Add(instance.ObjectType, typeof(T)); + _scalarsByName.Add(instance.Name, typeof(T)); + + _allBuiltInScalars.Add(typeof(T)); + if (isFixedName) + _fixedNamedScalars.Add(typeof(T)); + } + + /// + /// If the provided represents a known, built in scalar + /// the representing the associated + /// is returned. If the is not a built in scalar, + /// null is returned. + /// + /// + /// e.g. if is provided, then + /// is returned. + /// + /// The type to check. + /// The concrete type that represents the scalar. This type + /// is guaranteed to implement . + public static Type FindBuiltInScalarType(Type typeToCheck) + { + if (typeToCheck == null) + return null; + + if (typeToCheck.IsNullableOfT()) + { + typeToCheck = GraphValidation.EliminateWrappersFromCoreType( + typeToCheck, + eliminateEnumerables: false, + eliminateTask: false, + eliminateNullableT: true); + } + + if (_scalarGraphTypeTypesByConcreteType.TryGetValue(typeToCheck, out Type type)) + return type; + + if (_allBuiltInScalars.Contains(typeToCheck)) + return typeToCheck; + + return null; + } + + /// + /// Determines whether the provided type represents a known, globally available scalar. + /// + /// The type to check. + /// true if the type is a built in scalar; otherwise, false. + public static bool IsBuiltInScalar(Type typeToCheck) + { + return FindBuiltInScalarType(typeToCheck) != null; + } + + /// + /// Determines whether the assigned name is the name of a known global sclar. This check is not + /// case-sensitive. + /// + /// Name of the scalar. + /// true if the name is a known global scalar; otherwise, false. + internal static bool IsBuiltInScalar(string scalarName) + { + if (scalarName == null) + return false; + + return _scalarsByName.ContainsKey(scalarName); + } + + /// + /// Determines whether the scalar matching the required name can be reformatted or re-cased to a different name. + /// + /// + /// The five specification-defined scalars (Int, Float, String, Boolean, ID) cannot be renamed and are used + /// as part of the introspection system. All other internal scalars can be renamed or "re-cased" to match any rules + /// for a target schema. + /// + /// Name of the scalar. + /// true the name can be reformatted, otherwise false. + public static bool CanBeRenamed(string scalarName) + { + // meh, its not a built in scalar, doesnt really matter + if (scalarName == null) + return true; + + // if the name represents a globally defined scalar + // and if that scalar is declared as a fixed name + // then don't allow it to be renamed named + if (_scalarsByName.TryGetValue(scalarName, out Type scalarType)) + { + return !_fixedNamedScalars.Contains(scalarType); + } + + return true; + } + + /// + /// Validates that the supplied type can be used to build a scalar instance + /// that is usable by a schema. + /// + /// The type representing an . + public static void ValidateScalarTypeOrThrow(Type scalarType) + { + CreateAndValidateScalarType(scalarType, true); + } + + /// + /// Validates that the supplied scalar instance is valid and could be used by a schema + /// instance. + /// + /// The graph type instance to check. + public static void ValidateScalarTypeOrThrow(IScalarGraphType graphType) + { + ValidateScalarType(graphType, true); + } + + /// + /// Validates that the supplied type can be used to build a scalar instance + /// that is usable by a schema. + /// + /// The type representing an . + /// if set to true this method will throw a + /// if the sclar is not valid. + /// System.ValueTuple<System.Boolean, IScalarGraphType>. + private static (bool IsValid, IScalarGraphType Instance) CreateAndValidateScalarType(Type scalarType, bool shouldThrow = true) + { + if (scalarType == null) + { + if (!shouldThrow) + return (false, null); + + throw new GraphTypeDeclarationException("~null~ is an invalid scalar type"); + } + + if (!Validation.IsCastable(scalarType)) + { + if (!shouldThrow) + return (false, null); + + throw new GraphTypeDeclarationException( + $"The scalar must implement the interface '{typeof(IScalarGraphType).FriendlyName()}'."); + } + + var paramlessConstructor = scalarType.GetConstructor(new Type[0]); + if (paramlessConstructor == null) + { + if (!shouldThrow) + return (false, null); + + throw new GraphTypeDeclarationException( + "The scalar must declare a public, parameterless constructor."); + } + + var graphType = InstanceFactory.CreateInstance(scalarType) as IScalarGraphType; + return ValidateScalarType(graphType, shouldThrow); + } + + /// + /// Validates that the supplied scalar instance is valid and could be used by a schema + /// instance. + /// + /// The graph type instance to check. + /// if set to true this method will throw a + /// if the sclar is not valid. + private static (bool IsValid, IScalarGraphType Instance) ValidateScalarType(IScalarGraphType graphType, bool shouldThrow) + { + if (string.IsNullOrWhiteSpace(graphType.Name)) + { + if (!shouldThrow) + return (false, null); + throw new GraphTypeDeclarationException( + "The scalar must supply a name that is not null or whitespace."); + } + + if (!GraphValidation.IsValidGraphName(graphType.Name)) + { + if (!shouldThrow) + return (false, null); + throw new GraphTypeDeclarationException( + $"The scalar {graphType.GetType().FriendlyName()} must supply a name that that conforms to the standard rules for GraphQL. (Regex: {Constants.RegExPatterns.NameRegex})"); + } + + if (graphType.Kind != TypeKind.SCALAR) + { + if (!shouldThrow) + return (false, null); + throw new GraphTypeDeclarationException( + $"The '{graphType.Name}' scalar's type kind must be set to '{nameof(TypeKind.SCALAR)}'."); + } + + if (graphType.ObjectType == null) + { + if (!shouldThrow) + return (false, null); + throw new GraphTypeDeclarationException( + $"The scalar '{graphType.Name}' must supply a value for '{nameof(graphType.ObjectType)}', is cannot be null."); + } + + if (Validation.IsNullableOfT(graphType.ObjectType)) + { + if (!shouldThrow) + return (false, null); + throw new GraphTypeDeclarationException( + $"The scalar '{graphType.Name}' must supply the root,non-nullable type derivation for '{nameof(graphType.ObjectType)}' (e.g. 'int' not 'int?'). " + + $" The current value of {nameof(IScalarGraphType.ObjectType)} is a nullable type derivation."); + } + + if (graphType.SourceResolver == null) + { + if (!shouldThrow) + return (false, null); + throw new GraphTypeDeclarationException( + $"The scalar must supply a value for '{nameof(graphType.SourceResolver)}' that can convert data from a " + + $"query into the primary object type of '{graphType.ObjectType.FriendlyName()}'."); + } + + if (graphType.ValueType == ScalarValueType.Unknown) + { + if (!shouldThrow) + return (false, null); + throw new GraphTypeDeclarationException( + $"The scalar must supply a value for '{nameof(graphType.ValueType)}'. This lets the validation engine " + + "know what data types submitted on a user query could be parsed into a value for this scale."); + } + + if (graphType.AppliedDirectives == null || graphType.AppliedDirectives.Parent != graphType) + { + if (!shouldThrow) + return (false, null); + throw new GraphTypeDeclarationException( + $"Custom scalars must supply a value for '{nameof(graphType.AppliedDirectives)}', it cannot be null. " + + $"The '{nameof(IAppliedDirectiveCollection.Parent)}' property of the directive collection must also be set to the scalar itself."); + } + + return (true, graphType); + } + + /// + /// Determines whether the provided type represents an object that is a properly constructed scalar graph type. + /// + /// The type to check. + /// true if the type represents a valid scalar; otherwise, false. + public static bool IsValidScalarType(Type typeToCheck) + { + return CreateAndValidateScalarType(typeToCheck, false).IsValid; + } + + /// + /// Creates a new instance of the scalar. If the supplied type cannot be created + /// as a valid an exception is thrown. + /// + /// The scalar type to create. + /// IScalarGraphType. + public static IScalarGraphType CreateScalarInstanceOrThrow(Type scalarType) + { + scalarType = GraphValidation.EliminateNextWrapperFromCoreType(scalarType); + scalarType = FindBuiltInScalarType(scalarType) ?? scalarType; + + var (isValid, instance) = CreateAndValidateScalarType(scalarType, true); + return isValid ? instance : null; + } + + /// + /// Creates a new instance of the scalar. If the supplied type cannot be created + /// a valid null is returned. + /// + /// The scalar type to create. + /// IScalarGraphType. + public static IScalarGraphType CreateScalarInstance(Type scalarType) + { + scalarType = GraphValidation.EliminateNextWrapperFromCoreType(scalarType); + scalarType = FindBuiltInScalarType(scalarType) ?? scalarType; + + var (isValid, instance) = CreateAndValidateScalarType(scalarType, false); + return isValid ? instance : null; + } + + /// + /// Gets the list of concrete types that represent all internally defined, global scalars. + /// + /// The set of concrete types for all the global scalars. + public static IEnumerable ScalarConcreteTypes => _scalarGraphTypeTypesByConcreteType.Keys; + + /// + /// Gets the types that represent the objects for all internally defined, global scalars. + /// + /// The set of scalar instance types for all global scalars. + public static IEnumerable ScalarInstanceTypes => _scalarGraphTypeTypesByConcreteType.Values; + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/TypeSystem/GlobalTypes_Unions.cs b/src/graphql-aspnet/Schemas/TypeSystem/GlobalTypes_Unions.cs new file mode 100644 index 000000000..c2226e635 --- /dev/null +++ b/src/graphql-aspnet/Schemas/TypeSystem/GlobalTypes_Unions.cs @@ -0,0 +1,132 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.TypeSystem +{ + using System; + using System.Reflection; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Common.Generics; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// A map of .NET types and their related built in scalar types and unions. + /// + public static partial class GlobalTypes + { + /// + /// Validates that the type can be instantiated as a union proxy. Throws an exception if it cannot. + /// + /// The Type of the proxy to test. + public static void ValidateUnionProxyOrThrow(Type proxyType) + { + CreateAndValidateUnionProxyType(proxyType, true); + } + + /// + /// Validates that the provided union proxy is valid. If it is not, an exception is thrown. + /// + /// The union proxy instance to test. + public static void ValidateUnionProxyOrThrow(IGraphUnionProxy unionProxy) + { + ValidateUnionProxy(unionProxy, true); + } + + /// + /// Validates that the union proxy type provided is usable as a union within an object graph. + /// + /// The proxy type to validate. + /// if set to true this method should an exception when + /// an invalid proxy is checked. If set to false then false is returned when an invalid proxy is checked. + /// true if the proxy is valid, false otherwise. + private static (bool IsValid, IGraphUnionProxy Instance) CreateAndValidateUnionProxyType(Type proxyType, bool shouldThrow) + { + if (proxyType == null) + { + if (!shouldThrow) + return (false, null); + + throw new GraphTypeDeclarationException("~null~ is an invalid union proxy type."); + } + + if (!Validation.IsCastable(proxyType)) + { + if (!shouldThrow) + return (false, null); + + throw new GraphTypeDeclarationException( + $"The type {proxyType.FriendlyGraphTypeName()} does not implement {nameof(IGraphUnionProxy)}. All " + + $"types being used as a declaration of a union must implement {nameof(IGraphUnionProxy)}."); + } + + var paramlessConstructor = proxyType.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); + if (paramlessConstructor == null) + { + if (!shouldThrow) + return (false, null); + throw new GraphTypeDeclarationException( + $"The union proxy type '{proxyType.FriendlyName()}' could not be instantiated. " + + "All union proxy types must declare a parameterless constructor."); + } + + var proxy = InstanceFactory.CreateInstance(proxyType) as IGraphUnionProxy; + return ValidateUnionProxy(proxy, shouldThrow); + } + + private static (bool IsValid, IGraphUnionProxy Instance) ValidateUnionProxy(IGraphUnionProxy proxy, bool shouldThrow) + { + Validation.ThrowIfNull(proxy, nameof(proxy)); + + if (!GraphValidation.IsValidGraphName(proxy.Name)) + { + if (!shouldThrow) + return (false, null); + throw new GraphTypeDeclarationException( + $"The union proxy {proxy.GetType().FriendlyName()} must supply a name that that conforms to the standard rules for GraphQL. (Regex: {Constants.RegExPatterns.NameRegex})"); + } + + if (proxy.Types == null || proxy.Types.Count < 1) + { + if (!shouldThrow) + return (false, null); + + throw new GraphTypeDeclarationException( + $"The union proxy {proxy.GetType().FriendlyName()} must declare at least one valid Type to be a part of the union."); + } + + return (true, proxy); + } + + /// + /// Determines whether the provided type represents an object that is a properly constructed scalar graph type. + /// + /// The type to check. + /// true if the type represents a valid scalar; otherwise, false. + public static bool IsValidUnionProxyType(Type typeToCheck) + { + return CreateAndValidateUnionProxyType(typeToCheck, false).IsValid; + } + + /// + /// Attempts to instnatiate the provided type as a union proxy. If the proxy type is invalid, null is returned. + /// + /// + /// Use to check for correctness. + /// + /// Type of the proxy to create. + /// IGraphUnionProxy. + public static IGraphUnionProxy CreateUnionProxyFromType(Type proxyType) + { + var (isValid, result) = CreateAndValidateUnionProxyType(proxyType, false); + return isValid ? result : null; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/TypeSystem/GraphArgumentModifiers.cs b/src/graphql-aspnet/Schemas/TypeSystem/GraphArgumentModifiers.cs index de90d1639..c6d6e825a 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/GraphArgumentModifiers.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/GraphArgumentModifiers.cs @@ -12,31 +12,63 @@ namespace GraphQL.AspNet.Schemas.TypeSystem using System; /// - /// A set of modifiers and flags that can be assigned to individual arguments on graph fields to modify their behavior + /// A set of modifiers that can be assigned to individual arguments on graph fields to modify their behavior /// during execution. /// - [Flags] public enum GraphArgumentModifiers { + // implementation note, this used to be a [Flags] enum + // kept numbering of previous usage to prevent clashing in other libraries. + /// /// No special modifications are needed. /// None = 0, /// - /// This parameter is internal to the server environment and will not be exposed on the object graph. + /// This parameter is declared to contain the result of the value returned from the parent field's resolver. Applicable to + /// type extension and batch extension action methods. /// - Internal = 1, + ParentFieldResult, /// - /// This parameter is declared to contain the result of the resolved parent field. + /// This parameter is declared to be a cancellation token + /// governing the request or the default token if none was supplied on said request. /// - ParentFieldResult = 2, + CancellationToken, /// - /// This parameter is declared to be populated with the overall cancellation token - /// governing the request or the default token if none was supplied on said request. + /// This parameter is declared, in developer source code, to be resolved via dependency injection. It will NEVER be exposed + /// on the object graph. If the type represented by this parameter is not servable via a scoped + /// instance, an exception will occur and the target query will not be resolved. + /// + ExplicitInjected, + + /// + /// This parameter does not conform to the requirements of a graphql + /// argument (e.g. interfaces) and therefore must be resolved via dependency injection even + /// though it was not explicitly declared as such. It will NEVER be exposed + /// on the object graph. If the type represented by this parameter is not servable via a scoped + /// instance, an exception will occur and the target query will not be resolved. + /// + ImplicitInjected, + + /// + /// This parameter is declared to be resolved as an argument to a graph field. It will ALWAYS be + /// exposed on the object graph. If the type represented by this parameter cannot be served from the object graph + /// an exception will occur and the schema will fail to generate. This can occur, for instance, if it is an interface, + /// or if the type was explicitly excluded from the graph via attributions. + /// + ExplicitSchemaItem, + + /// + /// This parameter is declared to be resolved as the active resolution context being processed by a controller action. + /// + ResolutionContext, + + /// + /// This parameter is declared to be resolved as the active http context responsible for the original query. /// - CancellationToken = 4, + HttpContext, } } \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/TypeSystem/GraphArgumentModifiersExtensions.cs b/src/graphql-aspnet/Schemas/TypeSystem/GraphArgumentModifiersExtensions.cs index 81a92ada2..1c445d876 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/GraphArgumentModifiersExtensions.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/GraphArgumentModifiersExtensions.cs @@ -6,67 +6,89 @@ // -- // License: MIT // ************************************************************* + namespace GraphQL.AspNet.Schemas.TypeSystem { - using System.Runtime.CompilerServices; - /// /// Extension helper methods for . /// public static class GraphArgumentModifiersExtensions { /// - /// Determines whether the modifers indicate the argument is to contain the source data value supplied to the resolver for the field. + /// Determines whether the modifers indicate the argument is to contain the context of + /// the directive or field being resolved by the target resolver. /// - /// The modifiers set to check. - /// true if the modifers set declares the parent field reslt modifer. - public static bool IsSourceParameter(this GraphArgumentModifiers modifiers) + /// The modifiers to check. + /// true if the parameters represent the resolver context; otherwise, false. + public static bool IsResolverContext(this GraphArgumentModifiers modifiers) { - return modifiers.HasFlag(GraphArgumentModifiers.ParentFieldResult); + return modifiers == GraphArgumentModifiers.ResolutionContext; } /// - /// Determines whether the modifers indicate the argument is internal and not part of the graph. + /// Determines whether the modifers indicate the argument is to contain the source data value supplied to the resolver for the field. /// - /// The modifiers set to check. - /// true if the modifers set declares the internal modifer. - public static bool IsInternalParameter(this GraphArgumentModifiers modifiers) + /// The modifier set to check. + /// true if the modifers set declares the parent field reslt modifer. + public static bool IsSourceParameter(this GraphArgumentModifiers modifier) { - return modifiers.HasFlag(GraphArgumentModifiers.Internal); + return modifier == GraphArgumentModifiers.ParentFieldResult; } /// /// Determines whether the modifers indicate the argument is a reference /// to the cancellation token governing the overall request. /// - /// The modifiers set to check. + /// The modifier set to check. /// true if the modifers set declares the internal modifer. - public static bool IsCancellationToken(this GraphArgumentModifiers modifiers) + public static bool IsCancellationToken(this GraphArgumentModifiers modifier) { - return modifiers.HasFlag(GraphArgumentModifiers.CancellationToken); + return modifier == GraphArgumentModifiers.CancellationToken; } /// - /// Determines whether the modifiers indicate that the argument is, for one reason or another, - /// not part of the externally exposed schema. This method cannot determine - /// what special type of argument is represented, only that it is special. + /// Determines whether the modifier indicate that the argument is included in a + /// an externally exposed schema. /// - /// The modifiers to check. - /// true if the modifiers indicate the argument is not part of the schema; otherwise, false. - public static bool IsNotPartOfTheSchema(this GraphArgumentModifiers modifiers) + /// The modifier to check. + /// true if the modifier indicate the argument is part of the schema; otherwise, false. + public static bool CouldBePartOfTheSchema(this GraphArgumentModifiers modifier) { - return !modifiers.IsPartOfTheSchema(); + return modifier == GraphArgumentModifiers.None || + modifier.IsExplicitlyPartOfTheSchema(); } /// - /// Determines whether the modifiers indicate that the argument is included in a + /// Determines whether the modifier indicate that the argument is explicitly declared that it MUST be included in a /// an externally exposed schema. /// - /// The modifiers to check. - /// true if the modifiers indicate the argument is part of the schema; otherwise, false. - public static bool IsPartOfTheSchema(this GraphArgumentModifiers modifiers) + /// The modifier to check. + /// true if the modifier indicate the argument is explicitly declared to be a part of the schema; otherwise, false. + public static bool IsExplicitlyPartOfTheSchema(this GraphArgumentModifiers modifier) + { + return modifier == GraphArgumentModifiers.ExplicitSchemaItem; + } + + /// + /// Determines whether the modifier indicate that the argument is to be resolved from a DI + /// container as opposed to being passed ona query + /// + /// The modifier to check. + /// true if the modifier indicate the argument is to be resolved from a DI continer; otherwise, false. + public static bool IsInjected(this GraphArgumentModifiers modifier) + { + return modifier == GraphArgumentModifiers.ExplicitInjected || + modifier == GraphArgumentModifiers.ImplicitInjected; + } + + /// + /// Determines whether the modifier indicate that the argument is to be populated with the http context for the request. + /// + /// The modifier to check. + /// true if the parameters represent the global http context for the request; otherwise, false. + public static bool IsHttpContext(this GraphArgumentModifiers modifier) { - return modifiers == GraphArgumentModifiers.None; + return modifier == GraphArgumentModifiers.HttpContext; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/TypeSystem/GraphFieldArgument.cs b/src/graphql-aspnet/Schemas/TypeSystem/GraphFieldArgument.cs index b42e276d7..18b9c102f 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/GraphFieldArgument.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/GraphFieldArgument.cs @@ -30,11 +30,10 @@ public class GraphFieldArgument : IGraphArgument /// /// The parent schema item that owns this argument. /// Name of the argument. + /// The internal name identifiying this argument. + /// Name of the parameter as it is declared in the source code. /// The type expression. /// The route path that identifies this argument. - /// The modifiers. - /// Name of the parameter as it is declared in the source code. - /// The fully qualified internal name identifiying this argument. /// The concrete type of the object representing this argument. /// if set to true indicates that this /// argument has a default value assigned, even if that argument is null. @@ -45,11 +44,10 @@ public class GraphFieldArgument : IGraphArgument public GraphFieldArgument( ISchemaItem parent, string argumentName, + string internalName, + string parameterName, GraphTypeExpression typeExpression, SchemaItemPath route, - GraphArgumentModifiers modifiers, - string parameterName, - string internalName, Type objectType, bool hasDefaultValue, object defaultValue = null, @@ -63,7 +61,6 @@ public GraphFieldArgument( this.ParameterName = Validation.ThrowIfNullWhiteSpaceOrReturn(parameterName, nameof(parameterName)); this.TypeExpression = Validation.ThrowIfNullOrReturn(typeExpression, nameof(typeExpression)); this.ObjectType = Validation.ThrowIfNullOrReturn(objectType, nameof(objectType)); - this.ArgumentModifiers = modifiers; // by definition (rule 5.4.2.1) a nullable type expression on an argument implies // an optional field. that is to say it has an implicit default value of 'null' @@ -82,11 +79,10 @@ public IGraphArgument Clone(ISchemaItem parent) return new GraphFieldArgument( parent, this.Name, + this.InternalName, + this.ParameterName, this.TypeExpression.Clone(), parent.Route.CreateChild(this.Name), - this.ArgumentModifiers, - this.ParameterName, - this.InternalName, this.ObjectType, this.HasDefaultValue, this.DefaultValue, @@ -95,14 +91,11 @@ public IGraphArgument Clone(ISchemaItem parent) } /// - public string Name { get; set; } + public string Name { get; } /// public string Description { get; set; } - /// - public GraphArgumentModifiers ArgumentModifiers { get; } - /// public GraphTypeExpression TypeExpression { get; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/GraphOperation.cs b/src/graphql-aspnet/Schemas/TypeSystem/GraphOperation.cs index 492a55fc5..f63be460b 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/GraphOperation.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/GraphOperation.cs @@ -35,6 +35,7 @@ public GraphOperation( IAppliedDirectiveCollection directives = null) : base( Constants.ReservedNames.FindOperationTypeNameByType(operationType), + $"{nameof(GraphOperation)}.{Constants.ReservedNames.FindOperationTypeNameByType(operationType)}", new SchemaItemPath(SchemaItemCollections.Types, Constants.ReservedNames.FindOperationTypeNameByType(operationType)), directives) { @@ -65,8 +66,5 @@ public IGraphField Extend(IGraphField newField) /// public Type ObjectType => typeof(GraphOperation); - - /// - public string InternalName => $"{typeof(GraphOperation).FriendlyName()}.{this.OperationType}"; } } \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/TypeSystem/GraphUnionProxy.cs b/src/graphql-aspnet/Schemas/TypeSystem/GraphUnionProxy.cs index 4bd85a7a2..c9edd43ff 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/GraphUnionProxy.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/GraphUnionProxy.cs @@ -12,8 +12,9 @@ namespace GraphQL.AspNet.Schemas.TypeSystem using System; using System.Collections.Generic; using System.Diagnostics; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; /// /// An basic implementation of that can be @@ -23,17 +24,26 @@ namespace GraphQL.AspNet.Schemas.TypeSystem public class GraphUnionProxy : IGraphUnionProxy { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// Name of the union. - /// The types to include. - public GraphUnionProxy(string unionName, IEnumerable typesToInclude) + /// The name of the union as it appears in the graph. + /// The internal name of the union definition, usually the proxy class, + /// if one exists. + /// The types to include in the union. + public GraphUnionProxy( + string unionName, + string internalName, + IEnumerable typesToInclude) { this.Name = unionName?.Trim(); + this.InternalName = internalName?.Trim(); if (string.IsNullOrWhiteSpace(this.Name)) this.Name = this.GetType().FriendlyGraphTypeName(); + if (string.IsNullOrWhiteSpace(this.InternalName)) + this.InternalName = this.GetType().FriendlyName(); + this.Description = null; this.Types = new HashSet(typesToInclude); this.Publish = true; @@ -42,19 +52,31 @@ public GraphUnionProxy(string unionName, IEnumerable typesToInclude) /// /// Initializes a new instance of the class. /// - /// Name of the union as it should appear in the schema. - /// The types to include. + /// Name of the union as it appears in the schema. + /// The internal name of the union definition, usually the proxy class, + /// if one exists. + /// The types to include in the union. + public GraphUnionProxy(string unionName, string internalName, params Type[] typesToInclude) + : this(unionName, internalName, typesToInclude as IEnumerable) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Name of the union as it appears in the schema. + /// The types to include in the union. public GraphUnionProxy(string unionName, params Type[] typesToInclude) - : this(unionName, typesToInclude as IEnumerable) + : this(unionName, null, typesToInclude as IEnumerable) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The types to include. - protected GraphUnionProxy(params Type[] typesToInclude) - : this(null, typesToInclude as IEnumerable) + /// The types to include in the union. + public GraphUnionProxy(params Type[] typesToInclude) + : this(null, null, typesToInclude as IEnumerable) { } @@ -77,6 +99,9 @@ public virtual Type MapType(Type runtimeObjectType) /// public virtual string Name { get; set; } + /// + public string InternalName { get; set; } + /// public virtual string Description { get; set; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/InputGraphField.cs b/src/graphql-aspnet/Schemas/TypeSystem/InputGraphField.cs index 584be400b..aae48aa7b 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/InputGraphField.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/InputGraphField.cs @@ -36,29 +36,34 @@ public class InputGraphField : IInputGraphField /// Initializes a new instance of the class. /// /// Name of the field in the graph. + /// The internal name of this input field. Usually this is the property name, but it + /// can be changed by the developer. /// The meta data describing the type of data this field returns. /// The formal route to this field in the object graph. - /// The name of the property as it was declared on a (its internal name). /// The .NET type of the item or items that represent the graph type returned by this field. + /// The name of the property as it was declared on a (its internal name). /// The .NET type as it was declared on the property which generated this field.. /// if set to true this field was explicitly marked as being required being it has no - /// explicitly declared default value. The value passsed on will be ignored. Note that + /// explicitly declared default value. The value passsed on will be ignored. Note that /// the field will only truely be marked as required if it is has a non-nullable type expression. - /// When is false, represents + /// When is false, represents /// the value that should be used for this field when its not declared on a query document. /// The directives to apply to this field when its added to a schema. public InputGraphField( string fieldName, + string internalName, GraphTypeExpression typeExpression, SchemaItemPath route, - string declaredPropertyName, Type objectType, + string declaredPropertyName, Type declaredReturnType, bool isRequired, object defaultValue = null, IAppliedDirectiveCollection directives = null) { this.Name = Validation.ThrowIfNullWhiteSpaceOrReturn(fieldName, nameof(fieldName)); + this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalName, nameof(internalName)); + this.DeclaredName = Validation.ThrowIfNullWhiteSpaceOrReturn(declaredPropertyName, nameof(declaredPropertyName)); this.TypeExpression = Validation.ThrowIfNullOrReturn(typeExpression, nameof(typeExpression)); this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); @@ -67,7 +72,6 @@ public InputGraphField( this.AppliedDirectives = directives?.Clone(this) ?? new AppliedDirectiveCollection(this); - this.InternalName = declaredPropertyName; this.HasDefaultValue = !isRequired; this.IsRequired = isRequired && this.TypeExpression.IsNonNullable; this.DefaultValue = defaultValue; @@ -108,13 +112,12 @@ public void AssignParent(IGraphType parent) /// public bool Publish { get; set; } - /// - /// Gets a fully qualified name of the type as it exists on the server (i.e. Namespace.ClassName.PropertyName). This name - /// is used in many exceptions and internal error messages. - /// - /// The fully qualified name of the proeprty this field was created from. + /// public string InternalName { get; } + /// + public string DeclaredName { get; } + /// public object DefaultValue { get; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/InputObjectGraphType.cs b/src/graphql-aspnet/Schemas/TypeSystem/InputObjectGraphType.cs index 8500732e7..ce20c9635 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/InputObjectGraphType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/InputObjectGraphType.cs @@ -29,19 +29,21 @@ public class InputObjectGraphType : IInputObjectGraphType /// Initializes a new instance of the class. /// /// The name of the graph type. + /// The internal name assigned to this graph type in source code. /// Type of the object. /// The route path that identifies this object in the schema. /// The directives to apply to this input /// object when its added to a schema. public InputObjectGraphType( string name, + string internalName, Type objectType, SchemaItemPath route, IAppliedDirectiveCollection directives = null) { this.Name = Validation.ThrowIfNullWhiteSpaceOrReturn(name, nameof(name)); this.ObjectType = Validation.ThrowIfNullOrReturn(objectType, nameof(objectType)); - this.InternalName = this.ObjectType.FriendlyName(); + this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalName, nameof(internalName)); this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); this.AppliedDirectives = directives?.Clone(this) ?? new AppliedDirectiveCollection(this); this.Publish = true; diff --git a/src/graphql-aspnet/Schemas/TypeSystem/InterfaceGraphType.cs b/src/graphql-aspnet/Schemas/TypeSystem/InterfaceGraphType.cs index 7fd4bb213..b95815fe9 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/InterfaceGraphType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/InterfaceGraphType.cs @@ -30,12 +30,14 @@ public class InterfaceGraphType : IInterfaceGraphType /// Initializes a new instance of the class. /// /// The name of the interface as it will appear in the schema. + /// The internal name of the interface as defined in source code. /// The concrete type representing this interface. /// The route path that identifies this interface. /// The directives to apply to this type /// when its added to a schema. public InterfaceGraphType( string name, + string internalName, Type concreteType, SchemaItemPath route, IAppliedDirectiveCollection directives = null) @@ -43,7 +45,7 @@ public InterfaceGraphType( this.Name = Validation.ThrowIfNullOrReturn(name, nameof(name)); this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); this.ObjectType = Validation.ThrowIfNullOrReturn(concreteType, nameof(concreteType)); - this.InternalName = this.ObjectType.FriendlyName(); + this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalName, nameof(internalName)); this.InterfaceNames = new HashSet(); _fieldSet = new GraphFieldCollection(this); diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/BaseIntrospectionObjectType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/BaseIntrospectionObjectType.cs index 6aad89827..0555b7439 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/BaseIntrospectionObjectType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/BaseIntrospectionObjectType.cs @@ -10,7 +10,6 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.Introspection { using System; - using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas.Structural; @@ -23,11 +22,12 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.Introspection internal abstract class BaseIntrospectionObjectType : ObjectGraphTypeBase, IObjectGraphType, IInternalSchemaItem { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The name of the graph type as it is displayed in the __type information. - protected BaseIntrospectionObjectType(string name) - : base(name, new GraphIntrospectionFieldPath(name)) + /// The internal name of the introspected graph type as its defined for the target graph type. + protected BaseIntrospectionObjectType(string name, string internalName) + : base(name, internalName, new GraphIntrospectionFieldPath(name)) { } @@ -45,8 +45,5 @@ public IGraphField Extend(IGraphField newField) /// public virtual Type ObjectType => this.GetType(); - - /// - public virtual string InternalName => this.ObjectType.FriendlyName(); } } \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Fields/Introspection_SchemaField.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Fields/Introspection_SchemaField.cs index add3b51c9..6b7ef163b 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Fields/Introspection_SchemaField.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Fields/Introspection_SchemaField.cs @@ -14,8 +14,8 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.Introspection.Fields using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.Resolvers; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Schemas.TypeSystem.Introspection.Model; @@ -37,8 +37,11 @@ internal class Introspection_SchemaField : MethodGraphField public Introspection_SchemaField(IntrospectedSchema schema) : base( Constants.ReservedNames.SCHEMA_FIELD, + nameof(Introspection_SchemaField), new GraphTypeExpression(Constants.ReservedNames.SCHEMA_TYPE), FIELD_PATH, + typeof(IntrospectedSchema), + typeof(IntrospectedSchema), resolver: new FunctionGraphFieldResolver((x) => schema.AsCompletedTask())) { this.IntrospectedSchema = Validation.ThrowIfNullOrReturn(schema, nameof(schema)); diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Fields/Introspection_TypeGraphField.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Fields/Introspection_TypeGraphField.cs index d97787adb..b2351b0c7 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Fields/Introspection_TypeGraphField.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Fields/Introspection_TypeGraphField.cs @@ -12,8 +12,8 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.Introspection.Fields using System; using System.Diagnostics; using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Execution.Resolvers.Introspeection; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.Resolvers.Introspeection; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Schemas.TypeSystem.Introspection.Model; @@ -35,8 +35,11 @@ internal class Introspection_TypeGraphField : MethodGraphField public Introspection_TypeGraphField(IntrospectedSchema schema) : base( Constants.ReservedNames.TYPE_FIELD, + nameof(Introspection_TypeGraphField), new GraphTypeExpression(Constants.ReservedNames.TYPE_TYPE), FIELD_PATH, + declaredReturnType: typeof(IntrospectedType), + objectType: typeof(IntrospectedType), mode: FieldResolutionMode.PerSourceItem, resolver: new Schema_TypeGraphFieldResolver(schema)) { diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Fields/Introspection_TypeNameMetaField.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Fields/Introspection_TypeNameMetaField.cs index 8e57c44e3..34babe115 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Fields/Introspection_TypeNameMetaField.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Fields/Introspection_TypeNameMetaField.cs @@ -14,8 +14,8 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.Introspection.Fields using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.Resolvers; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; @@ -37,8 +37,12 @@ public class Introspection_TypeNameMetaField : MethodGraphField public Introspection_TypeNameMetaField(string graphTypeName) : base( Constants.ReservedNames.TYPENAME_FIELD, + nameof(Introspection_TypeNameMetaField), new GraphTypeExpression(Constants.ScalarNames.STRING, MetaGraphTypes.IsNotNull), - FIELD_PATH) + FIELD_PATH, + typeof(string), + typeof(string), + FieldResolutionMode.PerSourceItem) { Validation.ThrowIfNull(graphTypeName, nameof(graphTypeName)); this.UpdateResolver(new FunctionGraphFieldResolver((obj) => graphTypeName.AsCompletedTask()), FieldResolutionMode.PerSourceItem); diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_DirectiveLocationType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_DirectiveLocationType.cs index e52702577..8ff11eb91 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_DirectiveLocationType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_DirectiveLocationType.cs @@ -30,6 +30,7 @@ internal class Introspection_DirectiveLocationType : EnumGraphType, IInternalSch public Introspection_DirectiveLocationType() : base( Constants.ReservedNames.DIRECTIVE_LOCATION_ENUM, + nameof(Introspection_DirectiveLocationType), typeof(DirectiveLocation), new GraphIntrospectionFieldPath(Constants.ReservedNames.DIRECTIVE_LOCATION_ENUM)) { @@ -44,10 +45,11 @@ public Introspection_DirectiveLocationType() var option = new EnumValue( this, name, + name, description, this.Route.CreateChild(name), value, - name); + fi.Name); this.AddOption(option); } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_DirectiveType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_DirectiveType.cs index 4d1737887..dea608a3e 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_DirectiveType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_DirectiveType.cs @@ -27,7 +27,7 @@ internal class Introspection_DirectiveType : BaseIntrospectionObjectType /// Initializes a new instance of the class. /// public Introspection_DirectiveType() - : base(Constants.ReservedNames.DIRECTIVE_TYPE) + : base(Constants.ReservedNames.DIRECTIVE_TYPE, nameof(Introspection_DirectiveType)) { // "__Directive" type definition // https://graphql.github.io/graphql-spec/October2021/#sec-Introspection diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_EnumValueType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_EnumValueType.cs index 82d36c441..600c114a2 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_EnumValueType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_EnumValueType.cs @@ -26,7 +26,7 @@ internal class Introspection_EnumValueType : BaseIntrospectionObjectType /// Initializes a new instance of the class. /// public Introspection_EnumValueType() - : base(Constants.ReservedNames.ENUM_VALUE_TYPE) + : base(Constants.ReservedNames.ENUM_VALUE_TYPE, nameof(Introspection_EnumValueType)) { // "__EnumValue" type definition // https://graphql.github.io/graphql-spec/October2021/#sec-Introspection diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_FieldType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_FieldType.cs index c9bfc15b5..0fb89369a 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_FieldType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_FieldType.cs @@ -27,7 +27,7 @@ internal class Introspection_FieldType : BaseIntrospectionObjectType /// Initializes a new instance of the class. /// public Introspection_FieldType() - : base(Constants.ReservedNames.FIELD_TYPE) + : base(Constants.ReservedNames.FIELD_TYPE, nameof(Introspection_FieldType)) { // "__Field" type definition // https://graphql.github.io/graphql-spec/October2021/#sec-Introspection diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_InputValueType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_InputValueType.cs index e21cbb08d..1250f4cb4 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_InputValueType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_InputValueType.cs @@ -26,7 +26,7 @@ internal class Introspection_InputValueType : BaseIntrospectionObjectType /// Initializes a new instance of the class. /// public Introspection_InputValueType() - : base(Constants.ReservedNames.INPUT_VALUE_TYPE) + : base(Constants.ReservedNames.INPUT_VALUE_TYPE, nameof(Introspection_InputValueType)) { // "__InputValue" type definition // https://graphql.github.io/graphql-spec/October2021/#sec-Introspection diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_SchemaType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_SchemaType.cs index cfd1427f1..365b6c807 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_SchemaType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_SchemaType.cs @@ -28,7 +28,7 @@ internal class Introspection_SchemaType : BaseIntrospectionObjectType /// Initializes a new instance of the class. /// public Introspection_SchemaType() - : base(Constants.ReservedNames.SCHEMA_TYPE) + : base(Constants.ReservedNames.SCHEMA_TYPE, nameof(Introspection_SchemaType)) { // "__Schema" type definition // https://graphql.github.io/graphql-spec/October2021/#sec-Introspection diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_TypeKindType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_TypeKindType.cs index 20ed5187d..6456a2605 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_TypeKindType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_TypeKindType.cs @@ -30,6 +30,7 @@ internal class Introspection_TypeKindType : EnumGraphType, IInternalSchemaItem public Introspection_TypeKindType() : base( Constants.ReservedNames.TYPE_KIND_ENUM, + nameof(Introspection_TypeKindType), typeof(TypeKind), new GraphIntrospectionFieldPath(Constants.ReservedNames.TYPE_KIND_ENUM)) { @@ -44,10 +45,11 @@ public Introspection_TypeKindType() var option = new EnumValue( this, name, + name, description, this.Route.CreateChild(name), value, - name); + fi.Name); this.AddOption(option); } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_TypeType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_TypeType.cs index 0f6066601..6b2ff5f5b 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_TypeType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_TypeType.cs @@ -13,7 +13,7 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.Introspection using System.Diagnostics; using System.Threading.Tasks; using GraphQL.AspNet.Execution; - using GraphQL.AspNet.Internal.Resolvers.Introspeection; + using GraphQL.AspNet.Execution.Resolvers.Introspeection; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Schemas.TypeSystem.Introspection.Model; @@ -28,7 +28,7 @@ internal class Introspection_TypeType : BaseIntrospectionObjectType /// Initializes a new instance of the class. /// public Introspection_TypeType() - : base(Constants.ReservedNames.TYPE_TYPE) + : base(Constants.ReservedNames.TYPE_TYPE, nameof(Introspection_TypeType)) { // "__Type" type definition // https://graphql.github.io/graphql-spec/October2021/#sec-Introspection @@ -57,8 +57,11 @@ public Introspection_TypeType() // fields var fieldsField = new MethodGraphField( "fields", + "Introspection_TypeType_Fields", new GraphTypeExpression(Constants.ReservedNames.FIELD_TYPE, MetaGraphTypes.IsList, MetaGraphTypes.IsNotNull), new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "fields"), + declaredReturnType: typeof(IEnumerable), + objectType: typeof(IEnumerable), mode: FieldResolutionMode.PerSourceItem, resolver: new Type_TypeGraphFieldResolver()) { @@ -93,8 +96,11 @@ public Introspection_TypeType() // enumValues var enumValuesField = new MethodGraphField( "enumValues", + "Introspection_TypeType_EnumValues", new GraphTypeExpression(Constants.ReservedNames.ENUM_VALUE_TYPE, MetaGraphTypes.IsList, MetaGraphTypes.IsNotNull), new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, Constants.ReservedNames.ENUM_VALUE_TYPE), + declaredReturnType: typeof(IEnumerable), + objectType: typeof(IEnumerable), mode: FieldResolutionMode.PerSourceItem, resolver: new Type_EnumValuesGraphFieldResolver()) { diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedDirective.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedDirective.cs index 0a0617507..34e01c5e4 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedDirective.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedDirective.cs @@ -37,7 +37,7 @@ public IntrospectedDirective(IDirective directiveType) public override void Initialize(IntrospectedSchema introspectedSchema) { var list = new List(); - var directiveArguments = this.Directive.Arguments.Where(x => !x.ArgumentModifiers.IsInternalParameter()); + var directiveArguments = this.Directive.Arguments; foreach (var arg in directiveArguments) { var introspectedType = introspectedSchema.FindIntrospectedType(arg.TypeExpression.TypeName); diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedField.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedField.cs index 76fd04be0..55c4e3f87 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedField.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedField.cs @@ -11,10 +11,8 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.Introspection.Model { using System.Collections.Generic; using System.Diagnostics; - using System.Linq; using GraphQL.AspNet.Common; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Schemas.TypeSystem; /// /// A model object containing data for a '__Field' type @@ -42,7 +40,7 @@ public IntrospectedField(IGraphField field, IntrospectedType introspectedFieldOw public override void Initialize(IntrospectedSchema introspectedSchema) { var list = new List(); - foreach (var arg in _field.Arguments.Where(x => !x.ArgumentModifiers.HasFlag(GraphArgumentModifiers.Internal))) + foreach (var arg in _field.Arguments) { var introspectedType = introspectedSchema.FindIntrospectedType(arg.TypeExpression.TypeName); introspectedType = Introspection.WrapBaseTypeWithModifiers(introspectedType, arg.TypeExpression); diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedInputValueType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedInputValueType.cs index feed02fc3..e524487f3 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedInputValueType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedInputValueType.cs @@ -15,7 +15,6 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.Introspection.Model using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; using GraphQL.AspNet.Schemas.Structural; /// diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedItem.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedItem.cs index e7134a16d..fe0cad71e 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedItem.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedItem.cs @@ -30,6 +30,7 @@ public IntrospectedItem(ISchemaItem item) { _item = Validation.ThrowIfNullOrReturn(item, nameof(item)); this.Name = _item.Name; + this.InternalName = _item.InternalName; this.Description = _item.Description; this.Route = _item.Route.ReParent(Constants.Routing.INTROSPECTION_ROOT); this.AppliedDirectives = new AppliedDirectiveCollection(this); @@ -55,6 +56,9 @@ public virtual void Initialize(IntrospectedSchema introspectedSchema) /// public virtual string Name { get; set; } + /// + public string InternalName { get; } + /// public virtual string Description { get; set; } } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/MethodGraphField.cs b/src/graphql-aspnet/Schemas/TypeSystem/MethodGraphField.cs index f46b05e7c..ca6e09c7d 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/MethodGraphField.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/MethodGraphField.cs @@ -12,13 +12,11 @@ namespace GraphQL.AspNet.Schemas.TypeSystem using System; using System.Collections.Generic; using System.Diagnostics; - using System.Linq; using GraphQL.AspNet.Common; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Security; @@ -35,20 +33,22 @@ public class MethodGraphField : IGraphField /// Initializes a new instance of the class. /// /// Name of the field in the graph. + /// The internal name that represents the method this field respresents. /// The meta data describing the type of data this field returns. /// The formal route to this field in the object graph. - /// The .NET type of the item or items that represent the graph type returned by this field. /// The .NET type as it was declared on the property which generated this field.. + /// The .NET type of the item or items that represent the graph type returned by this field. /// The mode in which the runtime will process this field. /// The resolver to be invoked to produce data when this field is called. /// The security policies that apply to this field. /// The directives to apply to this field when its added to a schema. public MethodGraphField( string fieldName, + string internalName, GraphTypeExpression typeExpression, SchemaItemPath route, - Type objectType = null, - Type declaredReturnType = null, + Type declaredReturnType, + Type objectType, FieldResolutionMode mode = FieldResolutionMode.PerSourceItem, IGraphFieldResolver resolver = null, IEnumerable securityPolicies = null, @@ -58,8 +58,9 @@ public MethodGraphField( this.TypeExpression = Validation.ThrowIfNullOrReturn(typeExpression, nameof(typeExpression)); this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); this.Arguments = new GraphFieldArgumentCollection(this); - this.ObjectType = objectType; - this.DeclaredReturnType = declaredReturnType; + this.ObjectType = Validation.ThrowIfNullOrReturn(objectType, nameof(objectType)); + this.DeclaredReturnType = Validation.ThrowIfNullOrReturn(declaredReturnType, nameof(declaredReturnType)); + this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalName, nameof(internalName)); this.AppliedDirectives = directives?.Clone(this) ?? new AppliedDirectiveCollection(this); @@ -75,9 +76,6 @@ public void UpdateResolver(IGraphFieldResolver newResolver, FieldResolutionMode? this.Resolver = newResolver; if (mode.HasValue) this.Mode = mode.Value; - - var unrwrappedType = GraphValidation.EliminateWrappersFromCoreType(this.Resolver?.ObjectType); - this.IsLeaf = this.Resolver?.ObjectType != null && GraphQLProviders.ScalarProvider.IsLeaf(unrwrappedType); } /// @@ -148,10 +146,11 @@ protected virtual MethodGraphField CreateNewInstance(IGraphType parent) { return new MethodGraphField( this.Name, + this.InternalName, this.TypeExpression.Clone(), parent.Route.CreateChild(this.Name), - this.ObjectType, this.DeclaredReturnType, + this.ObjectType, this.Mode, this.Resolver, this.SecurityGroups, @@ -191,9 +190,6 @@ protected virtual MethodGraphField CreateNewInstance(IGraphType parent) /// public FieldResolutionMode Mode { get; protected set; } - /// - public bool IsLeaf { get; protected set; } - /// public bool IsDeprecated { get; set; } @@ -214,5 +210,8 @@ protected virtual MethodGraphField CreateNewInstance(IGraphType parent) /// public IAppliedDirectiveCollection AppliedDirectives { get; } + + /// + public string InternalName { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/TypeSystem/ObjectGraphType.cs b/src/graphql-aspnet/Schemas/TypeSystem/ObjectGraphType.cs index 2a35272b3..d7f6a010a 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/ObjectGraphType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/ObjectGraphType.cs @@ -28,20 +28,20 @@ public class ObjectGraphType : ObjectGraphTypeBase, IObjectGraphType /// Initializes a new instance of the class. /// /// The name of the graph type. + /// The defined internal name for this graph type. /// The concrete type that this graphtype is made from. /// The route path that identifies this object in the schema.. /// The directives applied to this object /// when its added to a schema. public ObjectGraphType( string name, + string internalName, Type objectType, SchemaItemPath route, IAppliedDirectiveCollection directives = null) - : base(name, route, directives) + : base(name, internalName, route, directives) { this.ObjectType = Validation.ThrowIfNullOrReturn(objectType, nameof(objectType)); - this.InternalName = this.ObjectType.FriendlyName(); - this.GraphFieldCollection.AddField(new Introspection_TypeNameMetaField(name)); } @@ -59,8 +59,5 @@ public override bool ValidateObject(object item) /// public Type ObjectType { get; } - - /// - public string InternalName { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/TypeSystem/ObjectGraphTypeBase.cs b/src/graphql-aspnet/Schemas/TypeSystem/ObjectGraphTypeBase.cs index ed5dee2f7..3d57efba2 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/ObjectGraphTypeBase.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/ObjectGraphTypeBase.cs @@ -28,15 +28,18 @@ public abstract class ObjectGraphTypeBase : IGraphType /// Initializes a new instance of the class. /// /// The name of the graph type as it is displayed in the __type information. + /// The defined internal name for this graph type. /// The route path of this object. /// The directives applied to this schema item /// when its added to a schema. protected ObjectGraphTypeBase( string name, + string internalName, SchemaItemPath route, IAppliedDirectiveCollection directives = null) { this.Name = Validation.ThrowIfNullWhiteSpaceOrReturn(name, nameof(name)); + this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalName, nameof(internalName)); this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); _graphFields = new GraphFieldCollection(this); this.InterfaceNames = new HashSet(); @@ -70,6 +73,9 @@ protected ObjectGraphTypeBase( /// public virtual string Name { get; set; } + /// + public string InternalName { get; } + /// public virtual string Description { get; set; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/PropertyGraphField.cs b/src/graphql-aspnet/Schemas/TypeSystem/PropertyGraphField.cs deleted file mode 100644 index 699056d5e..000000000 --- a/src/graphql-aspnet/Schemas/TypeSystem/PropertyGraphField.cs +++ /dev/null @@ -1,82 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Schemas.TypeSystem -{ - using System; - using System.Collections.Generic; - using GraphQL.AspNet.Execution; - using GraphQL.AspNet.Interfaces.Execution; - using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Schemas.Structural; - using GraphQL.AspNet.Security; - - /// - /// A representation of a field as it would be defined in an object graph that originated - /// from a .NET property getter. - /// - public class PropertyGraphField : MethodGraphField, ITypedSchemaItem - { - /// - /// Initializes a new instance of the class. - /// - /// Name of the field in the public graph. - /// The type expression declaring what type of data this field returns. - /// The route to this field in the graph. - /// The name of the property as it was declared on the (its internal name). - /// The .NET type of the item or items that represent the graph type returned by this field. - /// The .NET type as it was declared on the property which generated this field.. - /// The mode in which the runtime will process this field. - /// The resolver to be invoked to produce data when this field is called. - /// The security policies that apply to this field. - /// The directives to apply to this field when its added to a schema. - public PropertyGraphField( - string fieldName, - GraphTypeExpression typeExpression, - SchemaItemPath route, - string declaredPropertyName, - Type objectType = null, - Type declaredReturnType = null, - FieldResolutionMode mode = FieldResolutionMode.PerSourceItem, - IGraphFieldResolver resolver = null, - IEnumerable securityPolicies = null, - IAppliedDirectiveCollection directives = null) - : base(fieldName, typeExpression, route, objectType, declaredReturnType, mode, resolver, securityPolicies, directives) - { - this.InternalName = declaredPropertyName; - } - - /// - /// Creates a new instance of a graph field from this type. - /// - /// The item to assign as the parent of the new field. - /// IGraphField. - protected override MethodGraphField CreateNewInstance(IGraphType parent) - { - return new PropertyGraphField( - this.Name, - this.TypeExpression.Clone(), - parent.Route.CreateChild(this.Name), - this.InternalName, - this.ObjectType, - this.DeclaredReturnType, - this.Mode, - this.Resolver, - this.SecurityGroups, - this.AppliedDirectives); - } - - /// - /// Gets a fully qualified name of the type as it exists on the server (i.e. Namespace.ClassName.PropertyName). This name - /// is used in many exceptions and internal error messages. - /// - /// The fully qualified name of the proeprty this field was created from. - public string InternalName { get; } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/BooleanScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/BooleanScalarType.cs index 73cd3db72..54f8e0c3f 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/BooleanScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/BooleanScalarType.cs @@ -27,7 +27,6 @@ public BooleanScalarType() : base(Constants.ScalarNames.BOOLEAN, typeof(bool)) { this.Description = "A boolean value (Expressed as: true | false)"; - this.OtherKnownTypes = new TypeCollection(typeof(bool?)); } /// @@ -55,9 +54,6 @@ public override string SerializeToQueryLanguage(object item) return Constants.QueryLanguage.NULL; } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.Boolean; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ByteScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ByteScalarType.cs index c4e271a28..625ad0ee7 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ByteScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ByteScalarType.cs @@ -27,7 +27,6 @@ public ByteScalarType() : base(Constants.ScalarNames.BYTE, typeof(byte)) { this.Description = $"A unsigned byte. (Min: {byte.MinValue}, Max: {byte.MaxValue})"; - this.OtherKnownTypes = new TypeCollection(typeof(byte?)); } /// @@ -39,9 +38,6 @@ public override object Resolve(ReadOnlySpan data) throw new UnresolvedValueException(data, typeof(byte)); } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.Number; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DateOnlyScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DateOnlyScalarType.cs index be828a665..546d4ca1b 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DateOnlyScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DateOnlyScalarType.cs @@ -31,7 +31,6 @@ public DateOnlyScalarType() : base(Constants.ScalarNames.DATEONLY, typeof(DateOnly)) { this.Description = "A calendar date that does not include a time component."; - this.OtherKnownTypes = new TypeCollection(typeof(DateOnly?)); } /// @@ -60,9 +59,6 @@ public override string SerializeToQueryLanguage(object item) return Constants.QueryLanguage.NULL; } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.StringOrNumber; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DateTimeOffsetScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DateTimeOffsetScalarType.cs index be1ae97a6..0a60c51f6 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DateTimeOffsetScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DateTimeOffsetScalarType.cs @@ -28,7 +28,6 @@ public DateTimeOffsetScalarType() : base(Constants.ScalarNames.DATETIMEOFFSET, typeof(DateTimeOffset)) { this.Description = "A point in time relative to Coordinated Universal Time (UTC)."; - this.OtherKnownTypes = new TypeCollection(typeof(DateTimeOffset?)); } /// @@ -60,9 +59,6 @@ public override string SerializeToQueryLanguage(object item) return Constants.QueryLanguage.NULL; } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.StringOrNumber; diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DateTimeScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DateTimeScalarType.cs index b9a9c3744..43d8d9ebd 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DateTimeScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DateTimeScalarType.cs @@ -28,7 +28,6 @@ public DateTimeScalarType() : base(Constants.ScalarNames.DATETIME, typeof(DateTime)) { this.Description = "A calendar date that does include a time component."; - this.OtherKnownTypes = new TypeCollection(typeof(DateTime?)); } /// @@ -60,9 +59,6 @@ public override string SerializeToQueryLanguage(object item) return Constants.QueryLanguage.NULL; } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.StringOrNumber; diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DecimalScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DecimalScalarType.cs index ad863c064..c588b36c9 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DecimalScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DecimalScalarType.cs @@ -30,7 +30,6 @@ public DecimalScalarType() this.Description = "A 128-bit, floating point value that offers greater local " + "precision, with a smaller range, than other floating-point types. " + $"(Min: {decimal.MinValue}, Max: {decimal.MaxValue})"; - this.OtherKnownTypes = new TypeCollection(typeof(decimal?)); } /// @@ -42,9 +41,6 @@ public override object Resolve(ReadOnlySpan data) throw new UnresolvedValueException(data, typeof(decimal)); } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.Number; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DoubleScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DoubleScalarType.cs index 7dd030430..8e9711f32 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DoubleScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/DoubleScalarType.cs @@ -28,7 +28,6 @@ public DoubleScalarType() : base(Constants.ScalarNames.DOUBLE, typeof(double)) { this.Description = $"A 64-bit, floating-point value. (Min: {double.MinValue}, Max: {double.MaxValue})"; - this.OtherKnownTypes = new TypeCollection(typeof(double?)); } /// @@ -40,9 +39,6 @@ public override object Resolve(ReadOnlySpan data) throw new UnresolvedValueException(data, typeof(double)); } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.Number; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/FloatScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/FloatScalarType.cs index a758f6a19..549d1a86e 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/FloatScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/FloatScalarType.cs @@ -28,7 +28,6 @@ public FloatScalarType() : base(Constants.ScalarNames.FLOAT, typeof(float)) { this.Description = $"A 32-bit, floating-point value. (Min: {float.MinValue}, Max: {float.MaxValue})"; - this.OtherKnownTypes = new TypeCollection(typeof(float?)); } /// @@ -40,9 +39,6 @@ public override object Resolve(ReadOnlySpan data) throw new UnresolvedValueException(data, typeof(float)); } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.Number; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/GraphIdScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/GraphIdScalarType.cs index 30968981c..b73b1d59c 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/GraphIdScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/GraphIdScalarType.cs @@ -28,7 +28,6 @@ public GraphIdScalarType() : base(Constants.ScalarNames.ID, typeof(GraphId)) { this.Description = "The id scalar type represents a unique identifier in graphql."; - this.OtherKnownTypes = TypeCollection.Empty; } /// @@ -78,9 +77,6 @@ public override string SerializeToQueryLanguage(object item) return Constants.QueryLanguage.NULL; } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.StringOrNumber; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/GuidScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/GuidScalarType.cs index 5e7df9a14..2e78a6d12 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/GuidScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/GuidScalarType.cs @@ -27,7 +27,6 @@ public GuidScalarType() : base(Constants.ScalarNames.GUID, typeof(Guid)) { this.Description = "A standard guid (e.g. '6dd43342-ffe6-4964-bb6f-e31c8e50ec86')."; - this.OtherKnownTypes = TypeCollection.Empty; } /// @@ -48,9 +47,6 @@ public override object Serialize(object item) return ((Guid)item).ToString(); } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.String; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/IntScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/IntScalarType.cs index 9f6a027c7..f2510335b 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/IntScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/IntScalarType.cs @@ -27,12 +27,8 @@ public IntScalarType() : base(Constants.ScalarNames.INT, typeof(int)) { this.Description = $"A 32-bit integer. (Min: {int.MinValue}, Max: {int.MaxValue})"; - this.OtherKnownTypes = new TypeCollection(typeof(int?)); } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.Number; diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/LongScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/LongScalarType.cs index 5ee6a2239..020ade3ad 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/LongScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/LongScalarType.cs @@ -27,7 +27,6 @@ public LongScalarType() : base(Constants.ScalarNames.LONG, typeof(long)) { this.Description = $"A 64-bit integer. (Min: {long.MinValue}, Max: {long.MaxValue})"; - this.OtherKnownTypes = new TypeCollection(typeof(long?)); } /// @@ -39,9 +38,6 @@ public override object Resolve(ReadOnlySpan data) throw new UnresolvedValueException(data, typeof(long)); } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.Number; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/SByteScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/SByteScalarType.cs index 9ffdb33b1..a18177689 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/SByteScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/SByteScalarType.cs @@ -27,7 +27,6 @@ public SByteScalarType() : base(Constants.ScalarNames.SIGNED_BYTE, typeof(sbyte)) { this.Description = $"A signed byte. (Min: {sbyte.MinValue}, Max: {sbyte.MaxValue})"; - this.OtherKnownTypes = new TypeCollection(typeof(sbyte?)); } /// @@ -39,9 +38,6 @@ public override object Resolve(ReadOnlySpan data) throw new UnresolvedValueException(data, typeof(sbyte)); } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.Number; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ScalarGraphTypeBase.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ScalarGraphTypeBase.cs index 1a1987855..d5810aab8 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ScalarGraphTypeBase.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ScalarGraphTypeBase.cs @@ -15,7 +15,7 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.Scalars using GraphQL.AspNet.Execution; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; /// @@ -77,7 +77,7 @@ public virtual bool ValidateObject(object item) return true; var itemType = item.GetType(); - return itemType == this.ObjectType || this.OtherKnownTypes.Contains(itemType); + return itemType == this.ObjectType; } /// @@ -100,7 +100,21 @@ public virtual string SerializeToQueryLanguage(object item) } /// - public virtual string Name { get; set; } + public virtual IScalarGraphType Clone(string newName) + { + newName = Validation.ThrowIfNullWhiteSpaceOrReturn(newName, nameof(newName)); + var newInstance = GlobalTypes.CreateScalarInstanceOrThrow(this.GetType()) as ScalarGraphTypeBase; + + // some built in scalars (defined against this class) + // should never be renameable (string, int, float, id, boolean) + if (GlobalTypes.CanBeRenamed(this.Name)) + newInstance.Name = newName; + + return newInstance; + } + + /// + public virtual string Name { get; protected set; } /// public virtual Type ObjectType { get; } @@ -123,9 +137,6 @@ public virtual string SerializeToQueryLanguage(object item) /// public virtual ILeafValueResolver SourceResolver { get; set; } - /// - public abstract TypeCollection OtherKnownTypes { get; } - /// public virtual bool IsVirtual => false; diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ScalarReference.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ScalarReference.cs index 9388c62fa..6414f79f5 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ScalarReference.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ScalarReference.cs @@ -41,11 +41,6 @@ public static ScalarReference Create(IScalarGraphType graphType, Type instanceTy reference.InstanceType = instanceType; reference.PrimaryType = graphType.ObjectType; - if (graphType.OtherKnownTypes.Count > 0) - reference.OtherKnownTypes = graphType.OtherKnownTypes.ToList(); - else - reference.OtherKnownTypes = new List(); - reference.Name = graphType.Name; return reference; } @@ -70,13 +65,6 @@ private ScalarReference() /// The type of the primary. public Type PrimaryType { get; private set; } - /// - /// Gets a list of known alternate .NET types that can be - /// handled by this scalar (e.g. int?, long? etc.) - /// - /// The other known types. - public IReadOnlyList OtherKnownTypes { get; private set; } - /// /// Gets the name of this scalar as it has been declared. /// diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ShortScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ShortScalarType.cs index 184a24bc1..3c30a3c05 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ShortScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ShortScalarType.cs @@ -27,12 +27,8 @@ public ShortScalarType() : base(Constants.ScalarNames.SHORT, typeof(short)) { this.Description = $"A 16-bit integer. (Min: {short.MinValue}, Max: {short.MaxValue})"; - this.OtherKnownTypes = new TypeCollection(typeof(short?)); } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.Number; diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/StringScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/StringScalarType.cs index 1978954ed..728dde6e0 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/StringScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/StringScalarType.cs @@ -28,7 +28,6 @@ public StringScalarType() : base(Constants.ScalarNames.STRING, typeof(string)) { this.Description = "A UTF-8 encoded string of characters."; - this.OtherKnownTypes = TypeCollection.Empty; } /// @@ -52,9 +51,6 @@ public override string SerializeToQueryLanguage(object item) return GraphQLStrings.Escape(item.ToString()).AsQuotedString(); } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.String; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/TimeOnlyScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/TimeOnlyScalarType.cs index aad30b0be..4e3719b22 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/TimeOnlyScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/TimeOnlyScalarType.cs @@ -31,7 +31,6 @@ public TimeOnlyScalarType() : base(Constants.ScalarNames.TIMEONLY, typeof(TimeOnly)) { this.Description = "A time of day that does not include a date component."; - this.OtherKnownTypes = new TypeCollection(typeof(TimeOnly?)); } /// @@ -60,9 +59,6 @@ public override string SerializeToQueryLanguage(object item) return Constants.QueryLanguage.NULL; } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.String; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/UIntScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/UIntScalarType.cs index 29313e441..8421cd2d6 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/UIntScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/UIntScalarType.cs @@ -27,7 +27,6 @@ public UIntScalarType() : base(Constants.ScalarNames.UINT, typeof(uint)) { this.Description = $"A 32-bit, unsigned integer. (Min: {uint.MinValue}, Max: {uint.MaxValue})"; - this.OtherKnownTypes = new TypeCollection(typeof(uint?)); } /// @@ -39,9 +38,6 @@ public override object Resolve(ReadOnlySpan data) throw new UnresolvedValueException(data, typeof(uint)); } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.Number; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ULongScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ULongScalarType.cs index 6730194b9..f00413c75 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ULongScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/ULongScalarType.cs @@ -27,7 +27,6 @@ public ULongScalarType() : base(Constants.ScalarNames.ULONG, typeof(ulong)) { this.Description = $"A 64-bit, unsigned integer. (Min: {ulong.MinValue}, Max: {ulong.MaxValue})"; - this.OtherKnownTypes = new TypeCollection(typeof(ulong?)); } /// @@ -39,9 +38,6 @@ public override object Resolve(ReadOnlySpan data) throw new UnresolvedValueException(data, typeof(ulong)); } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.Number; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/UShortScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/UShortScalarType.cs index 6dc0557f5..76c81a2b1 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/UShortScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/UShortScalarType.cs @@ -27,12 +27,8 @@ public UShortScalarType() : base(Constants.ScalarNames.USHORT, typeof(ushort)) { this.Description = $"A 16-bit unsigned integer. (Min: {ushort.MinValue}, Max: {ushort.MaxValue})"; - this.OtherKnownTypes = new TypeCollection(typeof(ushort?)); } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.Number; diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/UriScalarType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/UriScalarType.cs index ea9641319..1c8f716e2 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Scalars/UriScalarType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Scalars/UriScalarType.cs @@ -28,7 +28,6 @@ public UriScalarType() : base(Constants.ScalarNames.URI, typeof(Uri)) { this.Description = "A uri pointing to a location on the web (a.k.a. URL)."; - this.OtherKnownTypes = TypeCollection.Empty; } /// @@ -67,9 +66,6 @@ public override string SerializeToQueryLanguage(object item) return Constants.QueryLanguage.NULL; } - /// - public override TypeCollection OtherKnownTypes { get; } - /// public override ScalarValueType ValueType => ScalarValueType.String; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/SchemaExtensions.cs b/src/graphql-aspnet/Schemas/TypeSystem/SchemaExtensions.cs index 843378a19..b3a1bf4d4 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/SchemaExtensions.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/SchemaExtensions.cs @@ -10,6 +10,7 @@ namespace GraphQL.AspNet.Schemas.TypeSystem { using System.Collections.Generic; using System.Linq; + using GraphQL.AspNet.Common; using GraphQL.AspNet.Interfaces.Schema; /// @@ -26,51 +27,49 @@ public static class SchemaExtensions /// IEnumerable<ISchemaItem>. public static IEnumerable AllSchemaItems(this ISchema schema, bool includeDirectives = false) { + Validation.ThrowIfNull(schema, nameof(schema)); + // schema first yield return schema; - // all declared operations - foreach (var operationEntry in schema.Operations) - yield return operationEntry.Value; - - // process each graph item except directives + // process each graph item except directives unless allowed var graphTypesToProcess = schema.KnownTypes.Where(x => - (includeDirectives || x.Kind != TypeKind.DIRECTIVE) - && !(x is IGraphOperation)); // dont let operations get included twice + (includeDirectives || x.Kind != TypeKind.DIRECTIVE)); foreach (var graphType in graphTypesToProcess) { yield return graphType; - if (graphType is IEnumGraphType enumType) - { - // each option on each enum - foreach (var option in enumType.Values) - yield return option.Value; - } - else if (graphType is IInputObjectGraphType inputObject) - { - // each input field - foreach (var inputField in inputObject.Fields) - yield return inputField; - } - else if (graphType is IGraphFieldContainer fieldContainer) + switch (graphType) { - // each field on OBJECT and INTERFACE graph type - foreach (var field in fieldContainer.Fields) - { - yield return field; + case IEnumGraphType enumType: + // each option on each enum + foreach (var option in enumType.Values) + yield return option.Value; + break; - // each argument on each field - foreach (var argument in field.Arguments) + case IInputObjectGraphType inputObject: + foreach (var inputField in inputObject.Fields) + yield return inputField; + break; + + // object graph types and interface graph types + case IGraphFieldContainer fieldContainer: + foreach (var field in fieldContainer.Fields) + { + yield return field; + + // each argument on each field + foreach (var argument in field.Arguments) + yield return argument; + } + + break; + + case IDirective directive: + foreach (var argument in directive.Arguments) yield return argument; - } - } - else if (graphType is IDirective directive) - { - // directive arguments - foreach (var argument in directive.Arguments) - yield return argument; + break; } } } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/TypeCollections/ConcreteTypeCollection.cs b/src/graphql-aspnet/Schemas/TypeSystem/TypeCollections/ConcreteTypeCollection.cs index ea2caaf5d..8eb21d580 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/TypeCollections/ConcreteTypeCollection.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/TypeCollections/ConcreteTypeCollection.cs @@ -12,12 +12,15 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.TypeCollections using System; using System.Collections.Concurrent; using System.Collections.Generic; + using System.Linq; using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Directives; using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Execution.RulesEngine.RuleSets.DocumentValidation.QueryFragmentSteps; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; + using Microsoft.Extensions.DependencyInjection; /// /// A colleciton of and their associated concrete .NET . @@ -41,17 +44,25 @@ public ConcreteTypeCollection() /// method will perform a coersion if possible. /// /// The concrete type to search with. - /// The kind of graph type to search for. If not supplied the schema will attempt to automatically - /// resolve the correct kind from the given . + /// An optional kind of graph type to search for. Only used in a tie breaker scenario + /// such as if a concrete type is registered as both an OBJECT and INPUT_OBJECT. /// IGraphType. public IGraphType FindGraphType(Type type, TypeKind? kind = null) { Validation.ThrowIfNull(type, nameof(type)); - type = GraphQLProviders.ScalarProvider.EnsureBuiltInTypeReference(type); - var resolvedKind = GraphValidation.ResolveTypeKind(type, kind); + type = GraphValidation.EliminateNextWrapperFromCoreType(type); + if (_graphTypesByConcreteType.TryGetValue(type, out var typeSet)) { + if (typeSet.Count == 1) + { + var value = typeSet.First().Value; + if (!kind.HasValue || value.Kind.IsLeafKind()) + return value; + } + + var resolvedKind = kind ?? TypeKind.OBJECT; if (typeSet.TryGetValue(resolvedKind, out var graphType)) return graphType; } @@ -90,20 +101,9 @@ public Type FindType(IGraphType graphType) /// passed when adding a . public IGraphType EnsureRelationship(IGraphType graphType, Type concreteType) { - // ensure a type association for scalars to its root type - concreteType = GraphQLProviders.ScalarProvider.EnsureBuiltInTypeReference(concreteType); - if (graphType.Kind == TypeKind.SCALAR) - { - concreteType = concreteType ?? GraphQLProviders.ScalarProvider.RetrieveConcreteType(graphType.Name); - - // if a type was provided make sure it COULD be a scalar type - if (!GraphQLProviders.ScalarProvider.IsScalar(concreteType)) - { - throw new GraphTypeDeclarationException( - $"The scalar '{graphType.Name}' attempted to associate itself to a concrete type of {concreteType.FriendlyName()}. " + - "Scalars cannot be associated with non scalar concrete types."); - } - } + // the registered concrete etype of a scalar must always be the primary declaration + if (graphType is IScalarGraphType scalarType) + concreteType = concreteType ?? scalarType.ObjectType; this.EnsureGraphTypeToConcreteTypeAssociationOrThrow(graphType, concreteType); if (concreteType == null) @@ -153,16 +153,17 @@ public IGraphType EnsureRelationship(IGraphType graphType, Type concreteType) /// true if a graph type exists, false otherwise. public bool Contains(Type type, TypeKind? kind = null) { - type = GraphQLProviders.ScalarProvider.EnsureBuiltInTypeReference(type); + Validation.ThrowIfNull(type, nameof(type)); + if (_graphTypesByConcreteType.TryGetValue(type, out var typeSet)) { - var resolvedKind = kind ?? GraphValidation.ResolveTypeKind(type); - return typeSet.ContainsKey(resolvedKind); - } - else - { - return false; + if (!kind.HasValue) + return true; + + return typeSet.ContainsKey(kind.Value); } + + return false; } /// @@ -175,12 +176,15 @@ private void EnsureGraphTypeToConcreteTypeAssociationOrThrow(IGraphType graphTyp { // scalars must be assigned to their pre-defined and accepted concrete type // instances of scalar graph types must be their pre-defined instance as well - if (graphType.Kind == TypeKind.SCALAR) + if (graphType is IScalarGraphType scalarType) { - if (associatedType == null || GraphQLProviders.ScalarProvider.RetrieveScalarName(associatedType) != graphType.Name) + if (associatedType == null || + (associatedType != scalarType.ObjectType)) { throw new GraphTypeDeclarationException( - $"The scalar type '{graphType.Name}' cannot be added and associated to the concrete type '{associatedType?.FriendlyName() ?? "-null-"}' it is not an approved scalar type."); + $"The scalar type '{graphType.Name}' cannot be associated to the concrete type '{associatedType?.FriendlyName() ?? "-null-"}' " + + $"on the target schema. Only explicitly declared concrete types on the scalar declaration are " + + $"allowed to represent the to be used for the schema."); } } @@ -199,8 +203,9 @@ private void EnsureGraphTypeToConcreteTypeAssociationOrThrow(IGraphType graphTyp $"The concrete type '{associatedType.FriendlyName()}' is an enum. It cannot be associated to a graph type of '{graphType.Kind.ToString()}'."); } - // directives must be assigned to a concrete type and it must inherit from GraphDirective. - if (graphType.Kind == TypeKind.DIRECTIVE && (associatedType == null || !Validation.IsCastable(associatedType))) + // if a directive is assigned to a concrete type + // it must inherit from GraphDirective. + if (graphType.Kind == TypeKind.DIRECTIVE && associatedType != null && !Validation.IsCastable(associatedType)) { throw new GraphTypeDeclarationException( $"The directive type '{graphType.Name}' cannnot be associated to the concrete type '{associatedType.FriendlyName()}'. Directive graph types " + diff --git a/src/graphql-aspnet/Schemas/TypeSystem/TypeCollections/SchemaTypeCollection.cs b/src/graphql-aspnet/Schemas/TypeSystem/TypeCollections/SchemaTypeCollection.cs index bd42f8ff8..39997dadb 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/TypeCollections/SchemaTypeCollection.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/TypeCollections/SchemaTypeCollection.cs @@ -20,7 +20,6 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.TypeCollections using GraphQL.AspNet.Directives; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; using GraphQL.AspNet.Schemas.Structural; /// @@ -162,7 +161,10 @@ public IGraphType FindGraphType(object data) return null; if (data is Type type) - return this.FindGraphType(type, TypeKind.OBJECT); + { + this.DenySearchForListAndKVP(type); + return _concreteTypes.FindGraphType(type, null); + } if (data is VirtualResolvedObject virtualFieldObject) { @@ -251,10 +253,7 @@ private void DenySearchForListAndKVP(Type concreteType) if (concreteType == null) return; - if (GraphQLProviders.ScalarProvider.IsScalar(concreteType)) - return; - - if (concreteType != typeof(string) && typeof(IEnumerable).IsAssignableFrom(concreteType)) + if (concreteType != typeof(string) && Validation.IsCastable(concreteType, typeof(IEnumerable))) { throw new GraphTypeDeclarationException( $"Schema Type Collection search, graph type mismatch, {concreteType.FriendlyName()}. Collections and KeyValuePair enumerable types " + diff --git a/src/graphql-aspnet/Schemas/TypeSystem/TypeKindExtensions.cs b/src/graphql-aspnet/Schemas/TypeSystem/TypeKindExtensions.cs index fe2120d3f..10fef7ebb 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/TypeKindExtensions.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/TypeKindExtensions.cs @@ -14,33 +14,6 @@ namespace GraphQL.AspNet.Schemas.TypeSystem /// public static class TypeKindExtensions { - /// - /// Determines whether this is allowed to be transformed into the provided kind. - /// - /// The kind to check. - /// The kind to become. - /// true if this instance can become the specified kind to become; otherwise, false. - internal static bool CanBecome(this TypeKind kind, TypeKind kindToBecome) - { - switch (kind) - { - case TypeKind.INTERFACE: - return kindToBecome == TypeKind.ENUM || kindToBecome == TypeKind.SCALAR || kindToBecome == TypeKind.OBJECT; - - case TypeKind.OBJECT: - return kindToBecome == TypeKind.ENUM || kindToBecome == TypeKind.SCALAR || kindToBecome == TypeKind.INTERFACE; - - case TypeKind.INPUT_OBJECT: - return kindToBecome == TypeKind.ENUM || kindToBecome == TypeKind.SCALAR; - - case TypeKind.NONE: - return true; - - default: - return false; - } - } - /// /// Determines whether the given kind of graph type is a valid leaf. /// diff --git a/src/graphql-aspnet/Schemas/TypeSystem/UnionGraphType.cs b/src/graphql-aspnet/Schemas/TypeSystem/UnionGraphType.cs index 4354c4aca..17f54aef4 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/UnionGraphType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/UnionGraphType.cs @@ -34,6 +34,7 @@ public class UnionGraphType : IUnionGraphType /// Initializes a new instance of the class. /// /// The name of the union as it appears in the target schema (case sensitive). + /// The defined internal name for this graph type. /// The type resolver used to match field resolve values with /// expected graph types in this union. /// The unique route of this item. @@ -41,11 +42,13 @@ public class UnionGraphType : IUnionGraphType /// to execute against this union when it is added to a schema. public UnionGraphType( string name, + string internalName, IUnionGraphTypeMapper typeResolver, SchemaItemPath route, IAppliedDirectiveCollection directives = null) { this.Name = Validation.ThrowIfNullWhiteSpaceOrReturn(name, nameof(name)); + this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalName, nameof(internalName)); this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); this.TypeMapper = typeResolver; this.Publish = true; @@ -88,6 +91,9 @@ public virtual void AddPossibleGraphType(string graphTypeName, Type concreteType /// public virtual string Name { get; set; } + /// + public string InternalName { get; } + /// public virtual string Description { get; set; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/UnregisteredGraphField.cs b/src/graphql-aspnet/Schemas/TypeSystem/UnregisteredGraphField.cs index 8df0f369f..68e4ecb4c 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/UnregisteredGraphField.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/UnregisteredGraphField.cs @@ -68,7 +68,7 @@ public override int GetHashCode() /// A comparer to evaluate the equality of two to prevent /// duplicate registrations of extension fields. /// - public class UnregisteredGraphFieldComparer : IEqualityComparer + private class UnregisteredGraphFieldComparer : IEqualityComparer { /// /// Determines whether the specified objects are equal. diff --git a/src/graphql-aspnet/Schemas/TypeSystem/VirtualGraphField.cs b/src/graphql-aspnet/Schemas/TypeSystem/VirtualGraphField.cs index 9416c357d..5a2ce5cad 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/VirtualGraphField.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/VirtualGraphField.cs @@ -16,11 +16,11 @@ namespace GraphQL.AspNet.Schemas.TypeSystem using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.Resolvers; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Security; @@ -66,6 +66,7 @@ public VirtualGraphField( this.TypeExpression = new GraphTypeExpression(parentTypeName); this.Arguments = new GraphFieldArgumentCollection(this); this.Resolver = new GraphControllerRouteFieldResolver(new VirtualResolvedObject(this.TypeExpression.TypeName)); + this.InternalName = $"VirtualField_{this.Name}"; // fields made from controller route parameters have no policies directly unto themselves // any controller class level policies are individually added to fields they declare @@ -154,9 +155,6 @@ public virtual bool CanResolveForGraphType(IGraphType graphType) /// The depreciation reason. public string DepreciationReason { get; set; } - /// - public bool IsLeaf => false; - /// public bool IsDeprecated { get; set; } @@ -192,5 +190,8 @@ public virtual bool CanResolveForGraphType(IGraphType graphType) /// public IAppliedDirectiveCollection AppliedDirectives { get; } + + /// + public string InternalName { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/TypeSystem/VirtualGraphFieldArgument.cs b/src/graphql-aspnet/Schemas/TypeSystem/VirtualGraphFieldArgument.cs index 0023939ae..fa9329e18 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/VirtualGraphFieldArgument.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/VirtualGraphFieldArgument.cs @@ -27,33 +27,30 @@ public class VirtualGraphFieldArgument : IGraphArgument /// /// The parent graph type that owns this virutal field. /// The name of this field in the object graph. - /// The name of this field as it exists in the .NET code. + /// The fully qualified name of this field as it exists in the .NET code. /// The graph type expression representing this field. /// The route path for this argument. /// The concrete graph type in the server code that this argument is mapped to. /// if set to true indicates that this /// argument has a default value, even if its null. - /// The default value. - /// The argument modifiers. + /// The default value of this argument when not supplied, if any. public VirtualGraphFieldArgument( ISchemaItem parent, string name, - string internalName, + string internalFullName, GraphTypeExpression typeExpression, SchemaItemPath route, Type concreteType, bool hasDefaultValue, - object defaultValue = null, - GraphArgumentModifiers argModifiers = GraphArgumentModifiers.None) + object defaultValue = null) { this.Parent = Validation.ThrowIfNullOrReturn(parent, nameof(parent)); this.ObjectType = Validation.ThrowIfNullOrReturn(concreteType, nameof(concreteType)); - this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalName, nameof(internalName)); + this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalFullName, nameof(internalFullName)); this.Name = Validation.ThrowIfNullWhiteSpaceOrReturn(name, nameof(name)); this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); this.ParameterName = this.Name; this.TypeExpression = Validation.ThrowIfNullOrReturn(typeExpression, nameof(typeExpression)); - this.ArgumentModifiers = argModifiers; // by definition (rule 5.4.2.1) a nullable type expression on an argument implies // an optional field. that is to say it has an implicit default value of 'null' @@ -84,9 +81,6 @@ public IGraphArgument Clone(ISchemaItem parent) /// public object DefaultValue { get; } - /// - public GraphArgumentModifiers ArgumentModifiers { get; } - /// public GraphTypeExpression TypeExpression { get; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/VirtualObjectGraphType.cs b/src/graphql-aspnet/Schemas/TypeSystem/VirtualObjectGraphType.cs index 1f9bdeca2..487dbeb87 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/VirtualObjectGraphType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/VirtualObjectGraphType.cs @@ -40,6 +40,7 @@ public class VirtualObjectGraphType : ObjectGraphTypeBase, IObjectGraphType, IIn public VirtualObjectGraphType(string name) : base( name, + $"{nameof(VirtualObjectGraphType)}_{name}", new SchemaItemPath(SchemaItemCollections.Types, name)) { // add the __typename as a field for this virtual object @@ -63,8 +64,5 @@ public IGraphField Extend(IGraphField newField) /// public Type ObjectType => typeof(VirtualObjectGraphType); - - /// - public string InternalName => typeof(VirtualObjectGraphType).FriendlyName(); } } \ No newline at end of file diff --git a/src/graphql-aspnet/ServerExtensions/MultipartRequests/Model/FileUpload.cs b/src/graphql-aspnet/ServerExtensions/MultipartRequests/Model/FileUpload.cs index caafd83f4..d24e20987 100644 --- a/src/graphql-aspnet/ServerExtensions/MultipartRequests/Model/FileUpload.cs +++ b/src/graphql-aspnet/ServerExtensions/MultipartRequests/Model/FileUpload.cs @@ -15,7 +15,6 @@ namespace GraphQL.AspNet.ServerExtensions.MultipartRequests.Model using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Common; using GraphQL.AspNet.ServerExtensions.MultipartRequests.Interfaces; - using GraphQL.AspNet.ServerExtensions.MultipartRequests.Schema; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; @@ -27,7 +26,6 @@ namespace GraphQL.AspNet.ServerExtensions.MultipartRequests.Model /// will result in a schema failure. This class cannot be used as a regular INPUT_OBJECT. See documentation /// for details. /// - [GraphSkip] [GraphType(PreventAutoInclusion = true)] public class FileUpload { diff --git a/src/graphql-aspnet/ServerExtensions/MultipartRequests/MultipartRequestServerExtension.cs b/src/graphql-aspnet/ServerExtensions/MultipartRequests/MultipartRequestServerExtension.cs index df3a0d471..02f1b1ed4 100644 --- a/src/graphql-aspnet/ServerExtensions/MultipartRequests/MultipartRequestServerExtension.cs +++ b/src/graphql-aspnet/ServerExtensions/MultipartRequests/MultipartRequestServerExtension.cs @@ -94,11 +94,7 @@ public virtual void Configure(SchemaOptions options) } // register a scalar that represents the file - var isRegisteredScalar = GraphQLProviders.ScalarProvider.IsScalar(typeof(FileUpload)); - if (!isRegisteredScalar) - { - GraphQLProviders.ScalarProvider.RegisterCustomScalar(typeof(FileUploadScalarGraphType)); - } + options.AddGraphType(); // register the config options for the schema var configurationServiceType = typeof(IMultipartRequestConfiguration<>).MakeGenericType(options.SchemaType); diff --git a/src/graphql-aspnet/ServerExtensions/MultipartRequests/Schema/FileUploadScalarGraphType.cs b/src/graphql-aspnet/ServerExtensions/MultipartRequests/Schema/FileUploadScalarGraphType.cs index c40df426c..59c0ec4fd 100644 --- a/src/graphql-aspnet/ServerExtensions/MultipartRequests/Schema/FileUploadScalarGraphType.cs +++ b/src/graphql-aspnet/ServerExtensions/MultipartRequests/Schema/FileUploadScalarGraphType.cs @@ -10,6 +10,7 @@ namespace GraphQL.AspNet.ServerExtensions.MultipartRequests.Schema { using System; + using System.Diagnostics; using GraphQL.AspNet.Common; using GraphQL.AspNet.Schemas.TypeSystem.Scalars; using GraphQL.AspNet.ServerExtensions.MultipartRequests.Model; @@ -19,6 +20,7 @@ namespace GraphQL.AspNet.ServerExtensions.MultipartRequests.Schema /// requirements of the multi-part request specification: /// . /// + [DebuggerDisplay("SCALAR: {Name}")] public class FileUploadScalarGraphType : ScalarGraphTypeBase { /// @@ -53,9 +55,6 @@ public override string SerializeToQueryLanguage(object item) /// public override ScalarValueType ValueType => ScalarValueType.Boolean | ScalarValueType.String | ScalarValueType.Number; - /// - public override TypeCollection OtherKnownTypes => TypeCollection.Empty; - /// public override object Resolve(ReadOnlySpan data) { diff --git a/src/graphql-aspnet/ServerExtensions/MultipartRequests/Web/MultiPartHttpFormPayloadParser.cs b/src/graphql-aspnet/ServerExtensions/MultipartRequests/Web/MultiPartHttpFormPayloadParser.cs index bfcd2c209..17b1e9340 100644 --- a/src/graphql-aspnet/ServerExtensions/MultipartRequests/Web/MultiPartHttpFormPayloadParser.cs +++ b/src/graphql-aspnet/ServerExtensions/MultipartRequests/Web/MultiPartHttpFormPayloadParser.cs @@ -18,6 +18,7 @@ namespace GraphQL.AspNet.ServerExtensions.MultipartRequests.Web using System.Threading.Tasks; using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Common.JsonNodes; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.ServerExtensions.MultipartRequests.Configuration; using GraphQL.AspNet.ServerExtensions.MultipartRequests.Exceptions; diff --git a/src/graphql-aspnet/ServerExtensions/MultipartRequests/Web/MultiPartHttpFormPayloadParser_Json.cs b/src/graphql-aspnet/ServerExtensions/MultipartRequests/Web/MultiPartHttpFormPayloadParser_Json.cs index 7fc366eb5..e7c38d1ba 100644 --- a/src/graphql-aspnet/ServerExtensions/MultipartRequests/Web/MultiPartHttpFormPayloadParser_Json.cs +++ b/src/graphql-aspnet/ServerExtensions/MultipartRequests/Web/MultiPartHttpFormPayloadParser_Json.cs @@ -15,6 +15,7 @@ namespace GraphQL.AspNet.ServerExtensions.MultipartRequests.Web using System.Text.Json; using System.Text.Json.Nodes; using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Common.JsonNodes; using GraphQL.AspNet.ServerExtensions.MultipartRequests.Configuration; using GraphQL.AspNet.ServerExtensions.MultipartRequests.Exceptions; using GraphQL.AspNet.ServerExtensions.MultipartRequests.Model; diff --git a/src/graphql-aspnet/graphql-aspnet.csproj b/src/graphql-aspnet/graphql-aspnet.csproj index 273d01a39..f8b4e8493 100644 --- a/src/graphql-aspnet/graphql-aspnet.csproj +++ b/src/graphql-aspnet/graphql-aspnet.csproj @@ -1,4 +1,4 @@ - + @@ -9,13 +9,13 @@ - + + + - - - - - + + + \ No newline at end of file diff --git a/src/library-common.props b/src/library-common.props index 1f5fff554..a862415af 100644 --- a/src/library-common.props +++ b/src/library-common.props @@ -1,7 +1,7 @@ - net8.0;net7.0;net6.0;netstandard2.0; + net8.0;net7.0;net6.0; latest $(NoWarn);1701;1702;1705;1591;NU1603;IDE0019;IDE0017;RCS1146;RCS1194; @@ -50,6 +50,8 @@ + + @@ -62,20 +64,4 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Configuration/ConfigurationSetupTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Configuration/ConfigurationSetupTests.cs index d1d9532f4..7a815f461 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Configuration/ConfigurationSetupTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Configuration/ConfigurationSetupTests.cs @@ -10,9 +10,7 @@ namespace GraphQL.AspNet.Tests.Configuration { using System; - using GraphQL.AspNet; using GraphQL.AspNet.Configuration; - using GraphQL.AspNet.Engine; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Interfaces.Subscriptions; using GraphQL.AspNet.Schemas; @@ -21,7 +19,6 @@ namespace GraphQL.AspNet.Tests.Configuration using GraphQL.AspNet.SubscriptionServer; using GraphQL.AspNet.SubscriptionServer.BackgroundServices; using GraphQL.AspNet.SubscriptionServer.Exceptions; - using GraphQL.AspNet.Tests.Framework; using GraphQL.AspNet.Tests.Configuration.ConfigurationTestData; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -36,11 +33,6 @@ public void AddSubscriptions_RegistrationChecks() { using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); - // ensure the runtime is in a default state (just in case the statics got messed up) - GraphQLProviders.TemplateProvider = new DefaultTypeTemplateProvider(); - GraphQLProviders.GraphTypeMakerProvider = new DefaultGraphTypeMakerProvider(); - GraphQLSchemaBuilderExtensions.Clear(); - var serviceCollection = new ServiceCollection(); var returned = serviceCollection.AddGraphQL(options => { @@ -61,11 +53,6 @@ public void ExplicitDeclarationOfPerFieldAuthorizationFailsServerCreation() // setup the server with a hard declaration of nothing using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); - // ensure the runtime is in a default state (just in case the statics got messed up) - GraphQLProviders.TemplateProvider = new DefaultTypeTemplateProvider(); - GraphQLProviders.GraphTypeMakerProvider = new DefaultGraphTypeMakerProvider(); - GraphQLSchemaBuilderExtensions.Clear(); - var serviceCollection = new ServiceCollection(); var schemaBuilder = serviceCollection.AddGraphQL(options => { @@ -88,10 +75,6 @@ public void ExplicitDeclarationOfPerRequestAuthorizationAddsServerSuccessfully() using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); // ensure the runtime is in a default state (just in case the statics got messed up) - GraphQLProviders.TemplateProvider = new DefaultTypeTemplateProvider(); - GraphQLProviders.GraphTypeMakerProvider = new DefaultGraphTypeMakerProvider(); - GraphQLSchemaBuilderExtensions.Clear(); - var serviceCollection = new ServiceCollection(); var returned = serviceCollection.AddGraphQL(options => { @@ -109,11 +92,6 @@ public void NonExplicitDeclarationResultsInPerRequestAndAddsServerSuccessfully() // setup the server with a hard declaration of nothing using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); - // ensure the runtime is in a default state (just in case the statics got messed up) - GraphQLProviders.TemplateProvider = new DefaultTypeTemplateProvider(); - GraphQLProviders.GraphTypeMakerProvider = new DefaultGraphTypeMakerProvider(); - GraphQLSchemaBuilderExtensions.Clear(); - SchemaOptions optionsSaved = null; var serviceCollection = new ServiceCollection(); var returned = serviceCollection.AddGraphQL(options => @@ -132,12 +110,6 @@ public void NonExplicitDeclarationResultsInPerRequestAndAddsServerSuccessfully() public void AddSubscriptionServer_RegistrationChecks() { using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); - - // ensure the runtime is in a default state (just in case the statics got messed up) - GraphQLProviders.TemplateProvider = new DefaultTypeTemplateProvider(); - GraphQLProviders.GraphTypeMakerProvider = new DefaultGraphTypeMakerProvider(); - GraphQLSchemaBuilderExtensions.Clear(); - var serviceCollection = new ServiceCollection(); var returned = serviceCollection.AddGraphQL(options => { @@ -160,10 +132,6 @@ private void EnsureSubscriptionServerRegistrations(IServiceProvider sp) // ensure router is registered Assert.IsNotNull(sp.GetService(typeof(ISubscriptionEventRouter))); - - // ensure the template provider for the runtime is swapped - Assert.IsTrue(GraphQLProviders.TemplateProvider is SubscriptionEnabledTypeTemplateProvider); - Assert.IsTrue(GraphQLProviders.GraphTypeMakerProvider is SubscriptionEnabledGraphTypeMakerProvider); } [Test] @@ -172,9 +140,6 @@ public void AddSubscriptionPublishing_RegistrationChecks() using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); // ensure the runtime is in a default state (just in case the statics got messed up) - GraphQLProviders.TemplateProvider = new DefaultTypeTemplateProvider(); - GraphQLProviders.GraphTypeMakerProvider = new DefaultGraphTypeMakerProvider(); - var serviceCollection = new ServiceCollection(); // the internal publisher (added by default) @@ -196,22 +161,10 @@ public void AddSubscriptionPublishing_RegistrationChecks() private void EnsureSubscriptionPublishingRegistrations(ServiceProvider sp) { - var controller = sp.GetService(typeof(FanController)); - Assert.IsNotNull(controller); - - // ensure schema operation type is/was allowed to be injected to the schema - var schema = sp.GetService(typeof(GraphSchema)) as ISchema; - Assert.IsNotNull(schema); - Assert.IsTrue(schema.Operations.ContainsKey(GraphOperationType.Subscription)); - // ensure registered services for subscription server Assert.IsNotNull(sp.GetService(typeof(ISubscriptionEventPublisher))); Assert.IsNotNull(sp.GetService(typeof(SubscriptionEventPublishingQueue))); Assert.IsNotNull(sp.GetService(typeof(IHostedService)) as SubscriptionPublicationService); - - // ensure the template provider for the runtime is swapped - Assert.IsTrue(GraphQLProviders.TemplateProvider is SubscriptionEnabledTypeTemplateProvider); - Assert.IsTrue(GraphQLProviders.GraphTypeMakerProvider is SubscriptionEnabledGraphTypeMakerProvider); } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Controllers/ControllerExtensionTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Controllers/ControllerExtensionTests.cs index be4925e7a..7fb242438 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Controllers/ControllerExtensionTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Controllers/ControllerExtensionTests.cs @@ -16,10 +16,13 @@ namespace GraphQL.AspNet.Tests.Controllers using GraphQL.AspNet.Controllers.ActionResults; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.SubscriptionServer; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Tests.Controllers.ControllerTestData; using NUnit.Framework; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution.Contexts; + using GraphQL.AspNet.Controllers; [TestFixture] public class ControllerExtensionTests @@ -31,7 +34,7 @@ public async Task PublishSubEvent_PublishesEventWithCorrectData() .AddGraphController() .Build(); - var fieldContextBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = server.CreateFieldContextBuilder( nameof(InvokableController.MutationRaisesSubEvent)); var arg1Value = "random string"; @@ -40,11 +43,11 @@ public async Task PublishSubEvent_PublishesEventWithCorrectData() var resolutionContext = fieldContextBuilder.CreateResolutionContext(); var controller = new InvokableController(); - var result = await controller.InvokeActionAsync(fieldContextBuilder.GraphMethod, resolutionContext); + var result = await controller.InvokeActionAsync(fieldContextBuilder.ResolverMetaData, resolutionContext); // ensure the method executed completely Assert.IsNotNull(result); - Assert.IsTrue(result is ObjectReturnedGraphActionResult); + Assert.IsTrue(result is OperationCompleteGraphActionResult); // ensure the event collection was created on the context Assert.IsTrue(resolutionContext.Session.Items.ContainsKey(SubscriptionConstants.ContextDataKeys.RAISED_EVENTS_COLLECTION)); @@ -69,7 +72,7 @@ public void PublishSubEvent_NoDataThrowsException() .AddController() .Build(); - var fieldContextBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = server.CreateFieldContextBuilder( nameof(InvokableController.MutationRaisesSubEventNoData)); var arg1Value = "random string"; @@ -81,7 +84,7 @@ public void PublishSubEvent_NoDataThrowsException() Assert.ThrowsAsync(async () => { - var result = await controller.InvokeActionAsync(fieldContextBuilder.GraphMethod, resolutionContext); + var result = await controller.InvokeActionAsync(fieldContextBuilder.ResolverMetaData, resolutionContext); }); } @@ -92,7 +95,7 @@ public async Task PublishSubEvent_ExistingEventCollectionisAppendedTo() .AddController() .Build(); - var fieldContextBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = server.CreateFieldContextBuilder( nameof(InvokableController.MutationRaisesSubEvent)); var arg1Value = "random string"; @@ -103,11 +106,11 @@ public async Task PublishSubEvent_ExistingEventCollectionisAppendedTo() resolutionContext.Session.Items.TryAdd(SubscriptionConstants.ContextDataKeys.RAISED_EVENTS_COLLECTION, eventCollection); var controller = new InvokableController(); - var result = await controller.InvokeActionAsync(fieldContextBuilder.GraphMethod, resolutionContext); + var result = await controller.InvokeActionAsync(fieldContextBuilder.ResolverMetaData, resolutionContext); // ensure the method executed completely Assert.IsNotNull(result); - Assert.IsTrue(result is ObjectReturnedGraphActionResult); + Assert.IsTrue(result is OperationCompleteGraphActionResult); Assert.AreEqual(1, eventCollection.Count); } @@ -119,7 +122,7 @@ public void PublishSubEvent_UnusableListForSubscriptionEvents_ThrowsException() .AddController() .Build(); - var fieldContextBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = server.CreateFieldContextBuilder( nameof(InvokableController.MutationRaisesSubEvent)); var arg1Value = "random string"; @@ -136,7 +139,7 @@ public void PublishSubEvent_UnusableListForSubscriptionEvents_ThrowsException() var controller = new InvokableController(); Assert.ThrowsAsync(async () => { - var result = await controller.InvokeActionAsync(fieldContextBuilder.GraphMethod, resolutionContext); + var result = await controller.InvokeActionAsync(fieldContextBuilder.ResolverMetaData, resolutionContext); }); } @@ -147,7 +150,7 @@ public void PublishSubEvent_NoEventNameFailsTheResolver_BubblesExceptionUp() .AddController() .Build(); - var fieldContextBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = server.CreateFieldContextBuilder( nameof(InvokableController.MutationRaiseSubEventWithNoEventName)); var arg1Value = "random string"; @@ -158,8 +161,48 @@ public void PublishSubEvent_NoEventNameFailsTheResolver_BubblesExceptionUp() Assert.ThrowsAsync(async () => { - var result = await controller.InvokeActionAsync(fieldContextBuilder.GraphMethod, resolutionContext); + var result = await controller.InvokeActionAsync(fieldContextBuilder.ResolverMetaData, resolutionContext); }); } + + [Test] + public async Task PublishSubEvent_OnRuntimeFIeld_ExistingEventCollectionisAppendedTo() + { + var server = new TestServerBuilder(TestOptions.UseCodeDeclaredNames) + .AddGraphQL((o) => + { + o.MapMutation("field1") + .WithInternalName("Mutation1") + .AddPossibleTypes(typeof(string)) + .AddResolver( + (FieldResolutionContext context, string arg1) => + { + SubscriptionEvents.PublishSubscriptionEvent(context, "event1", new TwoPropertyObject() + { + Property1 = arg1, + }); + return GraphActionResult.Ok("data result"); + }); + }) + .Build(); + + var fieldContextBuilder = server.CreateFieldContextBuilder("Mutation1"); + + var arg1Value = "random string"; + fieldContextBuilder.AddInputArgument("arg1", arg1Value); + + var resolutionContext = fieldContextBuilder.CreateResolutionContext(); + var eventCollection = new List(); + resolutionContext.Session.Items.TryAdd(SubscriptionConstants.ContextDataKeys.RAISED_EVENTS_COLLECTION, eventCollection); + + var controller = new RuntimeFieldExecutionController(); + var result = await controller.InvokeActionAsync(fieldContextBuilder.ResolverMetaData, resolutionContext); + + // ensure the method executed completely + Assert.IsNotNull(result); + Assert.IsTrue(result is OperationCompleteGraphActionResult); + + Assert.AreEqual(1, eventCollection.Count); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Controllers/ControllerTestData/InvokableController.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Controllers/ControllerTestData/InvokableController.cs index 5308f2267..c687a2464 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Controllers/ControllerTestData/InvokableController.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Controllers/ControllerTestData/InvokableController.cs @@ -13,7 +13,7 @@ namespace GraphQL.AspNet.Tests.Controllers.ControllerTestData using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphRoute("invoke")] public class InvokableController : GraphController diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Engine/DefaultEventRouterTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Engine/DefaultEventRouterTests.cs index 463122c02..1bf31ef36 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Engine/DefaultEventRouterTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Engine/DefaultEventRouterTests.cs @@ -16,7 +16,7 @@ namespace GraphQL.AspNet.Tests.Engine using GraphQL.AspNet.Interfaces.Subscriptions; using GraphQL.AspNet.Schemas; using GraphQL.AspNet.SubscriptionServer; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using NSubstitute; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Engine/SubscriptionEnabledFieldFieldMakerTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Engine/SubscriptionEnabledFieldFieldMakerTests.cs index 26696077b..b51b6300e 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Engine/SubscriptionEnabledFieldFieldMakerTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Engine/SubscriptionEnabledFieldFieldMakerTests.cs @@ -10,12 +10,13 @@ namespace GraphQL.AspNet.Tests.Engine { using System.Linq; - using GraphQL.AspNet.Engine.TypeMakers; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; - using GraphQL.AspNet.Tests.Framework; + using GraphQL.AspNet.Schemas.TypeMakers; using GraphQL.AspNet.Tests.Engine.TestData; + using GraphQL.AspNet.Tests.Framework; using NSubstitute; using NUnit.Framework; @@ -26,7 +27,7 @@ public class SubscriptionEnabledFieldFieldMakerTests public void SubscriptionActionField_TransfersDirectives() { var mockController = Substitute.For(); - mockController.InternalFullName.Returns(typeof(SubscriptionTestController).Name); + mockController.InternalName.Returns(typeof(SubscriptionTestController).Name); mockController.Route.Returns(new SchemaItemPath("path0")); mockController.Name.Returns("path0"); mockController.ObjectType.Returns(typeof(SubscriptionTestController)); @@ -38,7 +39,7 @@ public void SubscriptionActionField_TransfersDirectives() var schema = new TestServerBuilder().Build().Schema; - var maker = new SubscriptionEnabledGraphFieldMaker(schema); + var maker = new SubscriptionEnabledGraphFieldMaker(schema, new GraphArgumentMaker(schema)); var field = maker.CreateField(actionTemplate).Field; diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/ExecutionDirectiveTestData/DirectiveTestController.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/ExecutionDirectiveTestData/DirectiveTestController.cs index abd07dc4e..b05636594 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/ExecutionDirectiveTestData/DirectiveTestController.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/ExecutionDirectiveTestData/DirectiveTestController.cs @@ -14,7 +14,7 @@ namespace GraphQL.AspNet.Tests.Execution.ExecutionDirectiveTestData using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class DirectiveTestController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/SubscriptionQueryExecutionData/SubQueryController.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/SubscriptionQueryExecutionData/SubQueryController.cs index 240be6dda..67242ac98 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/SubscriptionQueryExecutionData/SubQueryController.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/SubscriptionQueryExecutionData/SubQueryController.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Execution.SubscriptionQueryExecutionData using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphRoute("subscriptionData")] public class SubQueryController : GraphController diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/SubscriptionQueryExecutionDirectiveTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/SubscriptionQueryExecutionDirectiveTests.cs index 9c4927a20..e2785fad8 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/SubscriptionQueryExecutionDirectiveTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/SubscriptionQueryExecutionDirectiveTests.cs @@ -10,8 +10,8 @@ namespace GraphQL.AspNet.Tests.Execution { using System.Threading.Tasks; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Tests.Execution.ExecutionDirectiveTestData; using GraphQL.AspNet.Tests.Execution.SubscriptionQueryExecutionData; using GraphQL.AspNet.Tests.Mocks; diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/SubscriptionQueryExecutionTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/SubscriptionQueryExecutionTests.cs index 8cd83d4cb..3d46cabad 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/SubscriptionQueryExecutionTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/SubscriptionQueryExecutionTests.cs @@ -12,12 +12,15 @@ namespace GraphQL.AspNet.Tests.Execution using System.Security.Cryptography; using System.Threading.Tasks; using GraphQL.AspNet; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Tests.Execution.SubscriptionQueryExecutionData; using GraphQL.AspNet.Tests.Mocks; using NuGet.Frameworks; using NUnit.Framework; + using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Schemas.TypeSystem; [TestFixture] public class SubscriptionQueryExecutionTests @@ -187,5 +190,87 @@ public async Task ExecutionOfQuery_WithCompleteEventResult_AddsError() Assert.IsNull(context.Result.Data); Assert.AreEqual(Constants.ErrorCodes.INVALID_ACTION_RESULT, context.Messages[0].Code); } + + [Test] + public async Task Execution_FromMinimalApi_ExecutesAsExpected() + { + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapSubscription("retrieveObject", (TwoPropertyObject sourceArg) => sourceArg) + .WithEventName("RetrieveObject") + .WithInternalName("RetrieveObject"); + }) + .AddSubscriptionServer() + .Build(); + + var operation = server.Schema.Operations[GraphOperationType.Subscription]; + var field = operation.Fields.FindField("retrieveObject"); + + var sourceObject = new TwoPropertyObject() + { + Property1 = "testA", + Property2 = 5, + }; + + var builder = server.CreateQueryContextBuilder() + .AddQueryText("subscription { retrieveObject { property1 } }") + .AddDefaultValue(field.Route, sourceObject); + + var result = await server.RenderResult(builder); + var expectedOutput = + @"{ + ""data"" : { + ""retrieveObject"" : { + ""property1"" : ""testA"" + } + } + }"; + + CommonAssertions.AreEqualJsonStrings(expectedOutput, result); + } + + [Test] + public async Task Execution_FromMinimalApi_AgainstAsyncResolver_ExecutesAsExpected() + { + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapSubscription("retrieveObject", async (TwoPropertyObject sourceArg) => + { + await Task.Yield(); + return sourceArg; + }) + .WithEventName("RetrieveObject") + .WithInternalName("RetrieveObject"); + }) + .AddSubscriptionServer() + .Build(); + + var operation = server.Schema.Operations[GraphOperationType.Subscription]; + var field = operation.Fields.FindField("retrieveObject"); + + var sourceObject = new TwoPropertyObject() + { + Property1 = "testA", + Property2 = 5, + }; + + var builder = server.CreateQueryContextBuilder() + .AddQueryText("subscription { retrieveObject { property1 } }") + .AddDefaultValue(field.Route, sourceObject); + + var result = await server.RenderResult(builder); + var expectedOutput = + @"{ + ""data"" : { + ""retrieveObject"" : { + ""property1"" : ""testA"" + } + } + }"; + + CommonAssertions.AreEqualJsonStrings(expectedOutput, result); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/GraphQLGlobalSubscriptionRestorePoint.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/GraphQLGlobalSubscriptionRestorePoint.cs index 5948bc2d3..4127f198d 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/GraphQLGlobalSubscriptionRestorePoint.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/GraphQLGlobalSubscriptionRestorePoint.cs @@ -9,6 +9,7 @@ namespace GraphQL.AspNet.Tests { + using System; using GraphQL.AspNet.SubscriptionServer; using GraphQL.AspNet.Tests.Framework; @@ -17,13 +18,12 @@ namespace GraphQL.AspNet.Tests /// that were present just before this object was created. Used in conjunction with NUnit to undo any changes to /// the global static providers in between tests. /// - public class GraphQLGlobalSubscriptionRestorePoint : GraphQLGlobalRestorePoint + public class GraphQLGlobalSubscriptionRestorePoint : IDisposable { private readonly int? _maxSubConnectedClient; private readonly int _maxSubConcurrentReceiver; public GraphQLGlobalSubscriptionRestorePoint() - : base() { _maxSubConnectedClient = GraphQLSubscriptionServerSettings.MaxConnectedClientCount; _maxSubConcurrentReceiver = GraphQLSubscriptionServerSettings.MaxConcurrentSubscriptionReceiverCount; @@ -32,15 +32,10 @@ public GraphQLGlobalSubscriptionRestorePoint() } /// - protected override void Dispose(bool disposing) + public void Dispose() { - base.Dispose(disposing); - - if (disposing) - { - GraphQLSubscriptionServerSettings.MaxConnectedClientCount = _maxSubConnectedClient; - GraphQLSubscriptionServerSettings.MaxConcurrentSubscriptionReceiverCount = _maxSubConcurrentReceiver; - } + GraphQLSubscriptionServerSettings.MaxConnectedClientCount = _maxSubConnectedClient; + GraphQLSubscriptionServerSettings.MaxConcurrentSubscriptionReceiverCount = _maxSubConcurrentReceiver; } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Middleware/PublishRaisedSubscriptionEventsMiddlewareTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Middleware/PublishRaisedSubscriptionEventsMiddlewareTests.cs index 3c90472ae..2a9e4d151 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Middleware/PublishRaisedSubscriptionEventsMiddlewareTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Middleware/PublishRaisedSubscriptionEventsMiddlewareTests.cs @@ -19,8 +19,8 @@ namespace GraphQL.AspNet.Tests.Middleware using GraphQL.AspNet.Middleware.QueryExecution.Components; using GraphQL.AspNet.Schemas; using GraphQL.AspNet.SubscriptionServer; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Mocks/SubscriptionContextBuilder.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Mocks/SubscriptionContextBuilder.cs index 4f631ea34..5f8083b30 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Mocks/SubscriptionContextBuilder.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Mocks/SubscriptionContextBuilder.cs @@ -20,7 +20,7 @@ namespace GraphQL.AspNet.Tests.Mocks using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Interfaces.Security; using GraphQL.AspNet.Interfaces.Subscriptions; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; using NSubstitute; diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/MappedSubscriptionGroupTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/MappedSubscriptionGroupTests.cs new file mode 100644 index 000000000..73025caeb --- /dev/null +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/MappedSubscriptionGroupTests.cs @@ -0,0 +1,145 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation +{ + using System.Linq; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Schemas; + using Microsoft.AspNetCore.Authorization; + using Microsoft.Extensions.DependencyInjection; + using NSubstitute; + using NUnit.Framework; + + [TestFixture] + public class MappedSubscriptionGroupTests + { + [Test] + public void MapSubscriptionGroup_WhenAllowAnonymousAdded_AddsAnonymousAttribute() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapSubscriptionGroup("/path1/path2"); + + field.AllowAnonymous(); + + Assert.AreEqual(1, field.Attributes.Count()); + Assert.IsNotNull(field.Attributes.FirstOrDefault(x => x is AllowAnonymousAttribute)); + } + + [Test] + public void MapSubscriptionGroup_WhenRequireAuthAdded_AddsAuthAttribute() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapSubscriptionGroup("/path1/path2"); + + field.RequireAuthorization("policy1", "roles1"); + + Assert.AreEqual(1, field.Attributes.Count()); + var attrib = field.Attributes.FirstOrDefault(x => x is AuthorizeAttribute) as AuthorizeAttribute; + Assert.IsNotNull(attrib); + Assert.AreEqual("policy1", attrib.Policy); + Assert.AreEqual("roles1", attrib.Roles); + } + + [Test] + public void MapSubscriptionGroup_WhenUnresolvedChildFieldIsAdded_PathIsCorrect() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapSubscriptionGroup("/path1/path2"); + + var childField = field.MapChildGroup("/path3/path4"); + + Assert.AreEqual("[subscription]/path1/path2/path3/path4", childField.Route.Path); + } + + [Test] + public void MapSubscriptionGroup_WhenAllowAnonymousAdded_ThenResolvedField_AddsAnonymousAttributeToField() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapSubscriptionGroup("/path1/path2"); + + field.AllowAnonymous(); + + var chidlField = field.MapField("/path3/path4", (string a) => 1); + + Assert.AreEqual(2, chidlField.Attributes.Count()); + Assert.IsNotNull(chidlField.Attributes.FirstOrDefault(x => x is AllowAnonymousAttribute)); + Assert.IsNotNull(chidlField.Attributes.FirstOrDefault(x => x is SubscriptionRootAttribute)); + } + + [Test] + public void MapSubscriptionGroup_WhenResolvedChildFieldIsAdded_PathIsCorrect() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapSubscriptionGroup("/path1/path2"); + + var childField = field.MapField("/path3/path4", (string a) => 1); + + Assert.AreEqual("[subscription]/path1/path2/path3/path4", childField.Route.Path); + Assert.AreEqual(1, childField.Attributes.Count(x => x is SubscriptionRootAttribute)); + } + + [Test] + public void MapSubscriptionGroup_WhenResolvedChildFieldIsAddedToUnresolvedChildField_PathIsCorrect() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapSubscriptionGroup("/path1/path2"); + var childField = field.MapChildGroup("/path3/path4"); + var resolvedField = childField.MapField("/path5/path6", (string a) => 1); + + Assert.AreEqual("[subscription]/path1/path2/path3/path4/path5/path6", resolvedField.Route.Path); + Assert.AreEqual(1, resolvedField.Attributes.Count(x => x is SubscriptionRootAttribute)); + Assert.IsNotNull(resolvedField.Resolver); + } + + [Test] + public void MapSubscriptionGroup_WhenResolvedChildFieldIsAdded_AndParentPathIsChanged_PathIsCorrect() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapSubscriptionGroup("/path1/path2"); + + var childField = field.MapField("/path3/path4", (string a) => 1); + + Assert.AreEqual("[subscription]/path1/path2/path3/path4", childField.Route.Path); + Assert.AreEqual(1, childField.Attributes.Count(x => x is SubscriptionRootAttribute)); + } + + [Test] + public void MapField_FromSchemaBuilder_WithNoResovler_FieldIsMade_ResolverIsNotSet() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var childField = builderMock.MapSubscriptionGroup("/path1/path2") + .MapField("myField"); + + Assert.IsNull(childField.Resolver); + Assert.AreEqual(1, childField.Attributes.Count(x => x is SubscriptionRootAttribute)); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/MappedSubscriptionTemplateTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/MappedSubscriptionTemplateTests.cs new file mode 100644 index 000000000..dd40202c7 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/MappedSubscriptionTemplateTests.cs @@ -0,0 +1,167 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation +{ + using System.Linq; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas; + using Microsoft.Extensions.DependencyInjection; + using NSubstitute; + using NUnit.Framework; + + [TestFixture] + public class MappedSubscriptionTemplateTests + { + public int TestDelegate(string a) + { + return 0; + } + + [Test] + public void MapSubscription_FromSchemaOptions_WithDelegate_DoesAddFieldToSchema() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapSubscription("/path1/path2", TestDelegate); + + Assert.IsNotNull(field); + Assert.AreEqual("[subscription]/path1/path2", field.Route.Path); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeResolvedFieldDefinition), field); + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + + Assert.AreEqual(1, field.Attributes.Count()); + var SubscriptionRootAttrib = field.Attributes.FirstOrDefault(x => x.GetType() == typeof(SubscriptionRootAttribute)); + Assert.IsNotNull(SubscriptionRootAttrib); + } + + [Test] + public void MapSubscription_FromBuilder_WithDelegate_DoesAddFieldToSchema() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var field = builderMock.MapSubscription("/path1/path2", TestDelegate); + + Assert.IsNotNull(field); + Assert.AreEqual("[subscription]/path1/path2", field.Route.Path); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeResolvedFieldDefinition), field); + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + Assert.IsNotNull(field.Resolver); + + Assert.AreEqual(1, field.Attributes.Count()); + var SubscriptionRootAttrib = field.Attributes.FirstOrDefault(x => x.GetType() == typeof(SubscriptionRootAttribute)); + Assert.IsNotNull(SubscriptionRootAttrib); + } + + [Test] + public void MapSubscription_FromBuilder_WithNoDelegate_DoesAddFieldToSchema() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var field = builderMock.MapSubscription("/path1/path2"); + + Assert.IsNotNull(field); + Assert.AreEqual("[subscription]/path1/path2", field.Route.Path); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeResolvedFieldDefinition), field); + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + + Assert.AreEqual(1, field.Attributes.Count()); + var SubscriptionRootAttrib = field.Attributes.FirstOrDefault(x => x.GetType() == typeof(SubscriptionRootAttribute)); + Assert.IsNotNull(SubscriptionRootAttrib); + } + + [Test] + public void MapSubscription_FromBuilder_WithUnionName0_AddsUnionNameToType() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var field = builderMock.MapSubscription("myField", "myUnion", (string a) => 1); + + var attrib = field.Attributes.OfType().SingleOrDefault(); + + Assert.AreEqual("myUnion", attrib.UnionName); + Assert.AreEqual(1, field.Attributes.Count(x => x is SubscriptionRootAttribute)); + } + + [Test] + public void MapSubscription_WithUnionNameSetToNull_AddsUnionNameToType() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapSubscription("myField", null, (string a) => 1); + + var attrib = field.Attributes.OfType().SingleOrDefault(); + + Assert.IsNull(attrib); + Assert.AreEqual(1, field.Attributes.Count(x => x is SubscriptionRootAttribute)); + } + + [Test] + public void MapSubscription_WithUnionName0_AddsUnionNameToType() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapSubscription("myField", "myUnion", (string a) => 1); + + var attrib = field.Attributes.OfType().SingleOrDefault(); + + Assert.AreEqual("myUnion", attrib.UnionName); + Assert.AreEqual(1, field.Attributes.Count(x => x is SubscriptionRootAttribute)); + } + + [Test] + public void MapSubscription_WithNoResolver_IsCreated() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapSubscription("myField"); + + Assert.IsNull(field.Resolver); + Assert.IsNull(field.ReturnType); + + Assert.IsTrue(options.RuntimeTemplates.Contains(field)); + Assert.AreEqual(1, field.Attributes.Count(x => x is SubscriptionRootAttribute)); + } + + [Test] + public void MapSubscription_WithResolver_AndUnion_IsCreated() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapSubscription("myField", "myUnion", () => 1); + + Assert.IsNotNull(field.Resolver); + + Assert.AreEqual(1, field.Attributes.OfType().Count()); + Assert.AreEqual("myUnion", field.Attributes.OfType().Single().UnionName); + Assert.IsTrue(options.RuntimeTemplates.Contains(field)); + Assert.AreEqual(1, field.Attributes.Count(x => x is SubscriptionRootAttribute)); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeSubscriptionEnabledResolvedFieldDefinitionTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeSubscriptionEnabledResolvedFieldDefinitionTests.cs new file mode 100644 index 000000000..fcb1f3ae8 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeSubscriptionEnabledResolvedFieldDefinitionTests.cs @@ -0,0 +1,52 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.RuntimeSchemaItemDefinitions +{ + using System.IO; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.RuntimeSchemaItemDefinitions; + using Microsoft.Extensions.DependencyInjection; + using NUnit.Framework; + + [TestFixture] + public class RuntimeSubscriptionEnabledResolvedFieldDefinitionTests + { + [TestCase("field1", "field1")] + [TestCase("field1/field2", "field1_field2")] + [TestCase("/field1/field2", "field1_field2")] + [TestCase("/field1/field2/", "field1_field2")] + [TestCase("/field1/field2/field3/field4/field5", "field1_field2_field3_field4_field5")] + [TestCase("field1/field_2_/field_3", "field1_field_2__field_3")] + public void NoEventNameSupplied_EventNameIsSetToFieldName(string path, string expectedEventName) + { + var collection = new ServiceCollection(); + var options = new SchemaOptions(collection); + + var field = new RuntimeSubscriptionEnabledFieldGroupTemplate(options, path); + var resolvedField = RuntimeSubscriptionEnabledResolvedFieldDefinition.FromFieldTemplate(field); + + Assert.AreEqual(expectedEventName, resolvedField.EventName); + } + + [Test] + public void EventNameSupplied_IsSetToProvidedName() + { + var collection = new ServiceCollection(); + var options = new SchemaOptions(collection); + + var field = new RuntimeSubscriptionEnabledFieldGroupTemplate(options, "field1"); + var resolvedField = RuntimeSubscriptionEnabledResolvedFieldDefinition.FromFieldTemplate(field); + resolvedField.EventName = "theEventName"; + + Assert.AreEqual("theEventName", resolvedField.EventName); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Internal/Templating/ActionTestData/OneMethodSubscriptionController.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/TypeTemplates/ActionTestData/OneMethodSubscriptionController.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-subscriptions-tests/Internal/Templating/ActionTestData/OneMethodSubscriptionController.cs rename to src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/TypeTemplates/ActionTestData/OneMethodSubscriptionController.cs index 5da209c80..1b73d9376 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Internal/Templating/ActionTestData/OneMethodSubscriptionController.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/TypeTemplates/ActionTestData/OneMethodSubscriptionController.cs @@ -7,12 +7,12 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { using System.ComponentModel; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class OneMethodSubscriptionController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Internal/Templating/ActionTestData/SubscriptionMethodController.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/TypeTemplates/ActionTestData/SubscriptionMethodController.cs similarity index 93% rename from src/unit-tests/graphql-aspnet-subscriptions-tests/Internal/Templating/ActionTestData/SubscriptionMethodController.cs rename to src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/TypeTemplates/ActionTestData/SubscriptionMethodController.cs index fda682edc..0f91683e2 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Internal/Templating/ActionTestData/SubscriptionMethodController.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/TypeTemplates/ActionTestData/SubscriptionMethodController.cs @@ -7,12 +7,12 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { using System.ComponentModel; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class SubscriptionMethodController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Internal/Templating/ControllerTestData/SimpleSubscriptionController.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/TypeTemplates/ControllerTestData/SimpleSubscriptionController.cs similarity index 88% rename from src/unit-tests/graphql-aspnet-subscriptions-tests/Internal/Templating/ControllerTestData/SimpleSubscriptionController.cs rename to src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/TypeTemplates/ControllerTestData/SimpleSubscriptionController.cs index 91b3de616..70f5b83e2 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Internal/Templating/ControllerTestData/SimpleSubscriptionController.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/TypeTemplates/ControllerTestData/SimpleSubscriptionController.cs @@ -7,13 +7,13 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData { using System.Threading.Tasks; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using NUnit.Framework; public class SimpleSubscriptionController : GraphController diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Internal/Templating/GraphActionTemplateTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/TypeTemplates/GraphActionTemplateTests.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-subscriptions-tests/Internal/Templating/GraphActionTemplateTests.cs rename to src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/TypeTemplates/GraphActionTemplateTests.cs index 39d110d9f..61a0e9f91 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Internal/Templating/GraphActionTemplateTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/TypeTemplates/GraphActionTemplateTests.cs @@ -7,19 +7,18 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates { using System.Linq; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - using GraphQL.AspNet.Tests.Internal.Templating.ActionTestData; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData; using NSubstitute; using NUnit.Framework; @@ -30,7 +29,7 @@ private SubscriptionControllerActionGraphFieldTemplate CreateActionTemplate(); - mockController.InternalFullName.Returns(typeof(TControllerType).Name); + mockController.InternalName.Returns(typeof(TControllerType).Name); mockController.Route.Returns(new SchemaItemPath("path0")); mockController.Name.Returns("path0"); mockController.ObjectType.Returns(typeof(TControllerType)); @@ -48,14 +47,15 @@ public void ActionTemplate_Parse_BasicPropertySets() { var methodInfo = typeof(OneMethodSubscriptionController).GetMethod(nameof(OneMethodSubscriptionController.SingleMethod)); var action = this.CreateActionTemplate(nameof(OneMethodSubscriptionController.SingleMethod)); + var metaData = action.CreateResolverMetaData(); Assert.AreEqual("SubDescription", action.Description); Assert.AreEqual(typeof(TwoPropertyObject), action.SourceObjectType); Assert.AreEqual(typeof(OneMethodSubscriptionController), action.Parent.ObjectType); Assert.AreEqual(SchemaItemCollections.Subscription, action.Route.RootCollection); Assert.AreEqual("[subscription]/path0/path1", action.Route.Path); - Assert.AreEqual($"{nameof(OneMethodSubscriptionController)}.{nameof(OneMethodSubscriptionController.SingleMethod)}", action.InternalFullName); - Assert.AreEqual(methodInfo.ReflectedType, ((IGraphFieldResolverMethod)action).Parent.ObjectType); + Assert.AreEqual($"{action.Parent.InternalName}.{nameof(OneMethodSubscriptionController.SingleMethod)}", action.InternalName); + Assert.AreEqual(methodInfo.ReflectedType, metaData.ParentObjectType); Assert.AreEqual("path0", action.Parent.Name); Assert.AreEqual(methodInfo, action.Method); Assert.AreEqual(1, action.Arguments.Count); @@ -78,8 +78,7 @@ public void ActionTemplate_Parse_ParameterSameAsReturnTypeIsMarkedSource() var action = this.CreateActionTemplate(nameof(SubscriptionMethodController.SingleMethod)); Assert.AreEqual(1, action.Arguments.Count); - Assert.IsTrue(action.Arguments[0].ArgumentModifiers.HasFlag(GraphArgumentModifiers.Internal)); - Assert.IsTrue(action.Arguments[0].ArgumentModifiers.HasFlag(GraphArgumentModifiers.ParentFieldResult)); + Assert.IsTrue(action.Arguments[0].ArgumentModifier.HasFlag(GraphArgumentModifiers.ParentFieldResult)); } [Test] @@ -88,8 +87,7 @@ public void ActionTemplate_Parse_ExplicitlyDeclaredSourceIsAttributedCorrectly() var action = this.CreateActionTemplate(nameof(SubscriptionMethodController.ExplicitSourceReference)); Assert.AreEqual(1, action.Arguments.Count); - Assert.IsTrue(action.Arguments[0].ArgumentModifiers.HasFlag(GraphArgumentModifiers.Internal)); - Assert.IsTrue(action.Arguments[0].ArgumentModifiers.HasFlag(GraphArgumentModifiers.ParentFieldResult)); + Assert.IsTrue(action.Arguments[0].ArgumentModifier.HasFlag(GraphArgumentModifiers.ParentFieldResult)); } [Test] @@ -99,7 +97,7 @@ public void ActionTemplate_Parse_ExplicitlyDeclaredSourceIsAttributedCorrectly_W Assert.AreEqual(2, action.Arguments.Count); - var sourceDataParam = action.Arguments.SingleOrDefault(x => x.ArgumentModifiers.HasFlag(GraphArgumentModifiers.ParentFieldResult)); + var sourceDataParam = action.Arguments.SingleOrDefault(x => x.ArgumentModifier.HasFlag(GraphArgumentModifiers.ParentFieldResult)); Assert.IsNotNull(sourceDataParam); Assert.AreEqual(typeof(TwoPropertyObjectV2), sourceDataParam.DeclaredArgumentType); } @@ -111,7 +109,7 @@ public void ActionTemplate_Parse_ExplicitlyDeclaredSourceIsAttributedCorrectly_W Assert.AreEqual(2, action.Arguments.Count); - var sourceDataParam = action.Arguments.SingleOrDefault(x => x.ArgumentModifiers.HasFlag(GraphArgumentModifiers.ParentFieldResult)); + var sourceDataParam = action.Arguments.SingleOrDefault(x => x.ArgumentModifier.HasFlag(GraphArgumentModifiers.ParentFieldResult)); Assert.IsNotNull(sourceDataParam); Assert.AreEqual(typeof(TwoPropertyObjectV2), sourceDataParam.DeclaredArgumentType); } diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Internal/Templating/GraphControllerTemplateTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/TypeTemplates/GraphControllerTemplateTests.cs similarity index 57% rename from src/unit-tests/graphql-aspnet-subscriptions-tests/Internal/Templating/GraphControllerTemplateTests.cs rename to src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/TypeTemplates/GraphControllerTemplateTests.cs index 50c3bd21d..224107e8d 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Internal/Templating/GraphControllerTemplateTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/Generation/TypeTemplates/GraphControllerTemplateTests.cs @@ -7,29 +7,28 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates { using System.Linq; - using GraphQL.AspNet.Engine; - using GraphQL.AspNet.Interfaces.Engine; - using GraphQL.AspNet.Internal.TypeTemplates; - using GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData; using NUnit.Framework; [TestFixture] public class GraphControllerTemplateTests { - public IGraphTypeTemplateProvider SubscriptionTemplateProvider => new SubscriptionEnabledTypeTemplateProvider(); - [Test] public void Parse_SingleSubscriptionRoute_CreatesCorrectActionTemplate() { - var template = this.SubscriptionTemplateProvider.ParseType() as GraphControllerTemplate; + var template = new SubscriptionGraphControllerTemplate(typeof(SimpleSubscriptionController)) as GraphControllerTemplate; + template.Parse(); + template.ValidateOrThrow(); + Assert.IsNotNull(template); Assert.AreEqual(1, template.FieldTemplates.Count()); Assert.AreEqual(1, template.Actions.Count()); - Assert.IsTrue(template.FieldTemplates.Any(x => x.Route.Path == $"[subscription]/SimpleSubscription/WidgetWatcher")); + Assert.IsTrue(template.FieldTemplates.Any(x => x.Route.ToString() == $"[subscription]/SimpleSubscription/WidgetWatcher")); } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/GraphSchemaManagerTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/GraphSchemaManagerTests.cs deleted file mode 100644 index 0e07e48df..000000000 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/GraphSchemaManagerTests.cs +++ /dev/null @@ -1,81 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Tests.Schemas -{ - using System; - using GraphQL.AspNet; - using GraphQL.AspNet.Engine; - using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Schemas; - using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Schemas.SchemaTestData; - using NUnit.Framework; - - [TestFixture] - public class GraphSchemaManagerTests - { - [Test] - public void AddSingleSubscriptionAction_AllDefaults_EnsureFieldStructure() - { - using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); - - GraphQLProviders.TemplateProvider = new SubscriptionEnabledTypeTemplateProvider(); - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - schema.Configuration.DeclarationOptions.AllowedOperations.Add(GraphOperationType.Subscription); - - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(); - - // query always exists - // subscription root was found via the method parsed - // mutation was not provided - Assert.IsTrue(schema.Operations.ContainsKey(GraphOperationType.Query)); - Assert.IsFalse(schema.Operations.ContainsKey(GraphOperationType.Mutation)); - Assert.IsTrue(schema.Operations.ContainsKey(GraphOperationType.Subscription)); - - // field for the controller exists - var topFieldName = nameof(SimpleMethodController).Replace(Constants.CommonSuffix.CONTROLLER_SUFFIX, string.Empty); - Assert.IsTrue(schema.Operations[GraphOperationType.Subscription].Fields.ContainsKey(topFieldName)); - - // ensure the field on the subscription operation is the right name (i.e. the controller name) - var topField = schema.Operations[GraphOperationType.Subscription][topFieldName]; - Assert.IsNotNull(topField); - - var type = schema.KnownTypes.FindGraphType(topField) as IObjectGraphType; - - var action = GraphQLTemplateHelper.CreateFieldTemplate(nameof(SimpleMethodController.TestActionMethod)); - - // ensure the action was put into the field collection of the controller operation - Assert.IsTrue(type.Fields.ContainsKey(action.Route.Name)); - } - - [Test] - public void AddASubscriptionAction_WithoutUpdatingTheConfiguration_ThrowsDeclarationException() - { - using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); - - GraphQLProviders.TemplateProvider = new SubscriptionEnabledTypeTemplateProvider(); - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - - // do not tell the schema to allow the subscription operation type - // schema.SetSubscriptionAllowances(); - - // attempt to add a controller with a subscription - Assert.Throws(() => - { - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(); - }); - } - } -} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/RuntimeFieldGeneralTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/RuntimeFieldGeneralTests.cs new file mode 100644 index 000000000..92c629645 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/RuntimeFieldGeneralTests.cs @@ -0,0 +1,134 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas +{ + using System; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Schema.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Framework; + using GraphQL.AspNet.Tests.Mocks; + using NUnit.Framework; + + [TestFixture] + public class RuntimeFieldGeneralTests + { + [Test] + public void General_SubscriptionField_IsRegistered() + { + using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapSubscription("field1", (TwoPropertyObject source, int param1) => source); + }) + .AddSubscriptionServer() + .Build(); + + var operation = server.Schema.Operations[GraphOperationType.Subscription]; + var field = operation.Fields.FindField("field1") as ISubscriptionGraphField; + Assert.IsNotNull(field); + Assert.AreEqual("field1", field.Name); + Assert.AreEqual("field1", field.EventName); + } + + [Test] + public void General_SubscriptionField_WithCustomEventName_IsRegistered() + { + using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapSubscription("field1", (TwoPropertyObject source, int param1) => source) + .WithEventName("myEvent"); + }) + .AddSubscriptionServer() + .Build(); + + var operation = server.Schema.Operations[GraphOperationType.Subscription]; + var field = operation.Fields.FindField("field1") as ISubscriptionGraphField; + Assert.IsNotNull(field); + Assert.AreEqual("field1", field.Name); + Assert.AreEqual("myEvent", field.EventName); + } + + [Test] + public void General_SubscriptionField_WithCustomInternalName_IsRegistered() + { + using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapSubscription("field1", (TwoPropertyObject source, int param1) => source) + .WithInternalName("myInternalName"); + }) + .AddSubscriptionServer() + .Build(); + + var operation = server.Schema.Operations[GraphOperationType.Subscription]; + var field = operation.Fields.FindField("field1") as ISubscriptionGraphField; + Assert.IsNotNull(field); + Assert.AreEqual("field1", field.Name); + Assert.AreEqual("myInternalName", field.InternalName); + } + + [Test] + public void AddSubscriptionField_WithoutRegistereingSubscriptionServer_ThrowsException() + { + using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); + var serverBuilder = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapSubscription("field1", (TwoPropertyObject source, int param1) => source); + }); + + Assert.Throws(() => + { + serverBuilder.Build(); + }); + } + + [Test] + public void AddSubscriptionField_NoApplicableSourceObject_ThrownException() + { + using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); + var serverBuilder = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapSubscription("field1", () => 0); + }) + .AddSubscriptionServer(); + + Assert.Throws(() => + { + serverBuilder.Build(); + }); + } + + [Test] + public void AddSubscriptionField_ViaBuilder_BeforeSubscriptionServer_RendersField() + { + using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); + var serverBuilder = new TestServerBuilder(); + + var schemaBuilder = serverBuilder.AddGraphQL(); + schemaBuilder.MapSubscription("field1", (int arg1) => 0); + schemaBuilder.AddSubscriptions(); + + var server = serverBuilder.Build(); + + var operation = server.Schema.Operations[GraphOperationType.Subscription]; + var field = operation.Fields.FindField("field1") as ISubscriptionGraphField; + Assert.IsNotNull(field); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/SchemaTestData/DuplicateEventNameController.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/SchemaTestData/DuplicateEventNameController.cs index fad8b2a2a..0044b1654 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/SchemaTestData/DuplicateEventNameController.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/SchemaTestData/DuplicateEventNameController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class DuplicateEventNameController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/SchemaTestData/OneFieldMapController.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/SchemaTestData/OneFieldMapController.cs index 1ae37d7a7..65202f018 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/SchemaTestData/OneFieldMapController.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/SchemaTestData/OneFieldMapController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class OneFieldMapController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/SchemaTestData/OneFieldMapWithEventNameController.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/SchemaTestData/OneFieldMapWithEventNameController.cs index 790745cd3..a3268cb4f 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/SchemaTestData/OneFieldMapWithEventNameController.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/SchemaTestData/OneFieldMapWithEventNameController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class OneFieldMapWithEventNameController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/SchemaTestData/SimpleMethodController.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/SchemaTestData/SimpleMethodController.cs index ee752fd11..d9fcd495a 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/SchemaTestData/SimpleMethodController.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Schemas/SchemaTestData/SimpleMethodController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class SimpleMethodController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionPublisherExtensionTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionPublisherExtensionTests.cs index 93b5614f5..379defc85 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionPublisherExtensionTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionPublisherExtensionTests.cs @@ -28,7 +28,6 @@ public void GeneralPropertyCheck() { using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); - GraphQLProviders.TemplateProvider = null; var collection = new ServiceCollection(); var primaryOptions = new SchemaOptions(collection); @@ -38,7 +37,6 @@ public void GeneralPropertyCheck() extension.Configure(primaryOptions); Assert.IsTrue(primaryOptions.DeclarationOptions.AllowedOperations.Contains(GraphOperationType.Subscription)); - Assert.IsTrue(GraphQLProviders.TemplateProvider is SubscriptionEnabledTypeTemplateProvider); } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionReceiverExtensionTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionReceiverExtensionTests.cs index e47b57758..8dd93e92c 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionReceiverExtensionTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionReceiverExtensionTests.cs @@ -72,7 +72,6 @@ public void ServiceCollection_VerifyDefaultInjectedObjects() using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); var serviceCollection = new ServiceCollection(); - GraphQLProviders.TemplateProvider = null; var primaryOptions = new SchemaOptions(serviceCollection); var subscriptionOptions = new SubscriptionServerOptions(); @@ -84,10 +83,11 @@ public void ServiceCollection_VerifyDefaultInjectedObjects() Assert.IsTrue(primaryOptions.DeclarationOptions.AllowedOperations.Contains(GraphOperationType.Subscription)); - Assert.AreEqual(8, primaryOptions.ServiceCollection.Count); + Assert.AreEqual(9, primaryOptions.ServiceCollection.Count); // primary server objects Assert.IsNotNull(primaryOptions.ServiceCollection.SingleOrDefault(x => x.ServiceType == typeof(SubscriptionServerOptions))); + Assert.IsNotNull(primaryOptions.ServiceCollection.SingleOrDefault(x => x.ImplementationType == typeof(SubscriptionEnabledGraphQLSchemaFactory))); Assert.IsNotNull(primaryOptions.ServiceCollection.SingleOrDefault(x => x.ServiceType == typeof(ISubscriptionServerClientFactory))); Assert.IsNotNull(primaryOptions.ServiceCollection.SingleOrDefault(x => x.ServiceType == typeof(IGlobalSubscriptionClientProxyCollection))); Assert.IsNotNull(primaryOptions.ServiceCollection.SingleOrDefault(x => x.ServiceType == typeof(ISubscriptionEventDispatchQueue))); @@ -99,8 +99,6 @@ public void ServiceCollection_VerifyDefaultInjectedObjects() // legacy graphql-ws objects Assert.IsNotNull(primaryOptions.ServiceCollection.SingleOrDefault(x => x.ImplementationType == typeof(GraphqlWsLegacySubscriptionClientProxyFactory))); Assert.IsNotNull(primaryOptions.ServiceCollection.SingleOrDefault(x => x.ImplementationType == typeof(GraphqlWsLegacySubscriptionClientProxyFactoryAlternate))); - - Assert.IsTrue(GraphQLProviders.TemplateProvider is SubscriptionEnabledTypeTemplateProvider); } [Test] @@ -135,7 +133,6 @@ public void GeneralPropertyCheck() using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); var serviceCollection = new ServiceCollection(); - GraphQLProviders.TemplateProvider = null; var primaryOptions = new SchemaOptions(serviceCollection); var subscriptionOptions = new SubscriptionServerOptions(); diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/ClientSubscriptionTestData/ClientSubscriptionTestController.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/ClientSubscriptionTestData/ClientSubscriptionTestController.cs index 44d53b8ed..e7b0d44fc 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/ClientSubscriptionTestData/ClientSubscriptionTestController.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/ClientSubscriptionTestData/ClientSubscriptionTestController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.SubscriptionServer.ClientSubscriptionTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ClientSubscriptionTestController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GqltwsClientAsserts.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GqltwsClientAsserts.cs index 9cd2488b7..a5b72f3b8 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GqltwsClientAsserts.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GqltwsClientAsserts.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlTransportWs using System.Text; using System.Text.Json; using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlTransportWs.Messaging; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Mocks; using GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlTransportWs.GraphqlTransportWsData; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GqltwsClientProxyTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GqltwsClientProxyTests.cs index 31b2cdfc0..395c03e6c 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GqltwsClientProxyTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GqltwsClientProxyTests.cs @@ -21,8 +21,8 @@ namespace GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlTransportWs using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlTransportWs; using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlTransportWs.Messaging; using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlTransportWs.Messaging.Messages; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Web; using GraphQL.AspNet.Tests.Mocks; using GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlTransportWs.GraphqlTransportWsData; @@ -746,5 +746,83 @@ public async Task Deserialize_InvalidMessage_ProcessesUnknownMessage() connection.AssertServerClosedConnection((ConnectionCloseStatus)GqltwsConstants.CustomCloseEventIds.InvalidMessageType); graphqlWsClient.Dispose(); } + + [Test] + public async Task ReceiveEvent_OnStartedSubscription_AgainstMinimalApiSubscription_YieldsNEXTMessage() + { + using var restorePoint = new GraphQLGlobalSubscriptionRestorePoint(); + + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapSubscription("watchForPropObject") + .WithEventName("watchForPropObject") + .AddResolver(async (TwoPropertyObject obj) => + { + await Task.Yield(); + return obj; + }); + }) + .AddSubscriptionServer((options) => + { + options.ConnectionKeepAliveInterval = TimeSpan.FromMinutes(15); + options.AuthenticatedRequestsOnly = false; + }) + .Build(); + + var router = Substitute.For(); + + var connection = server.CreateClientConnection(GqltwsConstants.PROTOCOL_NAME); + var serverOptions = server.ServiceProvider.GetRequiredService>(); + + var subClient = new GqltwsClientProxy( + connection, + server.Schema, + router, + server.ServiceProvider.GetService>()); + + var startMessage = new GqltwsClientSubscribeMessage() + { + Id = "abc", + Payload = new GraphQueryData() + { + Query = "subscription { watchForPropObject { property1 } } ", + }, + }; + + await connection.OpenAsync(GqltwsConstants.PROTOCOL_NAME); + await subClient.ProcessMessageAsync(startMessage); + + // mimic new data for the registered subscription being processed by some + // other mutation + var evt = new SubscriptionEvent() + { + Id = Guid.NewGuid().ToString(), + DataTypeName = typeof(TwoPropertyObject).Name, + Data = new TwoPropertyObject() + { + Property1 = "value1", + Property2 = 33, + }, + EventName = "watchForPropObject", + SchemaTypeName = new GraphSchema().FullyQualifiedSchemaTypeName(), + }; + + await subClient.ReceiveEventAsync(evt); + + // the connection should receive a data package + connection.AssertGqltwsResponse( + GqltwsMessageType.NEXT, + "abc", + @"{ + ""data"" : { + ""watchForPropObject"" : { + ""property1"" : ""value1"", + } + } + }"); + + subClient.Dispose(); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GqltwsConverterTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GqltwsConverterTests.cs index f92bffd54..2360e85cb 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GqltwsConverterTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GqltwsConverterTests.cs @@ -21,8 +21,8 @@ namespace GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlTransportWs using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlTransportWs.Messaging.Common; using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlTransportWs.Messaging.Converters; using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlTransportWs.Messaging.Messages; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Tests.Mocks; using GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlTransportWs.GraphqlTransportWsData; using Microsoft.Extensions.DependencyInjection; diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GraphqlTransportWsData/GqltwsDataMessageController.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GraphqlTransportWsData/GqltwsDataMessageController.cs index e9ad4981b..399abb88b 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GraphqlTransportWsData/GqltwsDataMessageController.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GraphqlTransportWsData/GqltwsDataMessageController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlTransportWs.G { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class GqltwsDataMessageController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GraphqlTransportWsData/GqltwsResponseMessageConverter.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GraphqlTransportWsData/GqltwsResponseMessageConverter.cs index 66f7c1983..ffdb3ee7f 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GraphqlTransportWsData/GqltwsResponseMessageConverter.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GraphqlTransportWsData/GqltwsResponseMessageConverter.cs @@ -14,7 +14,7 @@ namespace GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlTransportWs.G using System.Text.Json.Serialization; using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlTransportWs; using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlTransportWs.Messaging; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; internal class GqltwsResponseMessageConverter : JsonConverter { diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GraphqlTransportWsData/GqltwsSubscriptionController.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GraphqlTransportWsData/GqltwsSubscriptionController.cs index 0784783f6..d6a3fd130 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GraphqlTransportWsData/GqltwsSubscriptionController.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlTransportWs/GraphqlTransportWsData/GqltwsSubscriptionController.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlTransportWs.G using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class GqltwsSubscriptionController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyClientAsserts.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyClientAsserts.cs index ca3609107..84f0242cd 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyClientAsserts.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyClientAsserts.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlWsLegacy using System.Text; using System.Text.Json; using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlWsLegacy.Messaging; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Mocks; using GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlWsLegacy.GraphqlWsLegacyData; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyClientProxyTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyClientProxyTests.cs index 4bf1a0865..b9ba3e8e1 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyClientProxyTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyClientProxyTests.cs @@ -20,8 +20,8 @@ namespace GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlWsLegacy using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlWsLegacy; using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlWsLegacy.Messaging; using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlWsLegacy.Messaging.Messages; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Tests.Mocks; using GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlWsLegacy.GraphqlWsLegacyData; using Microsoft.Extensions.DependencyInjection; diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyConverterTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyConverterTests.cs index c2ad46c18..41a9d7652 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyConverterTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyConverterTests.cs @@ -17,7 +17,6 @@ namespace GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlWsLegacy using GraphQL.AspNet.Interfaces.Engine; using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Common.Extensions; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; @@ -27,6 +26,7 @@ namespace GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlWsLegacy using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlWsLegacy.Messaging.Converters; using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlWsLegacy.Messaging; using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlWsLegacy.Messaging.Common; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [TestFixture] public class GraphqlWsLegacyConverterTests diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyData/GraphqlWsLegacyDataMessageController.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyData/GraphqlWsLegacyDataMessageController.cs index 4ea41140a..7ea13f9a7 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyData/GraphqlWsLegacyDataMessageController.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyData/GraphqlWsLegacyDataMessageController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlWsLegacy.Grap { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class GraphqlWsLegacyDataMessageController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyData/GraphqlWsLegacyResponseMessageConverter.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyData/GraphqlWsLegacyResponseMessageConverter.cs index 3077979f0..33d94835d 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyData/GraphqlWsLegacyResponseMessageConverter.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyData/GraphqlWsLegacyResponseMessageConverter.cs @@ -14,7 +14,7 @@ namespace GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlWsLegacy.Grap using System.Text.Json.Serialization; using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlWsLegacy; using GraphQL.AspNet.SubscriptionServer.Protocols.GraphqlWsLegacy.Messaging; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; internal class GraphqlWsLegacyResponseMessageConverter : JsonConverter { diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyData/GraphqlWsLegacySubscriptionController.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyData/GraphqlWsLegacySubscriptionController.cs index e946bb77c..11bd7d66f 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyData/GraphqlWsLegacySubscriptionController.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/Protocols/GraphqlWsLegacy/GraphqlWsLegacyData/GraphqlWsLegacySubscriptionController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.SubscriptionServer.Protocols.GraphqlWsLegacy.Grap { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class GraphqlWsLegacySubscriptionController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/SubscriptionClientDispatchQueueAlerterTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/SubscriptionClientDispatchQueueAlerterTests.cs index a749f2f9e..45e992c8e 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/SubscriptionClientDispatchQueueAlerterTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/SubscriptionClientDispatchQueueAlerterTests.cs @@ -11,7 +11,6 @@ namespace GraphQL.AspNet.Tests.SubscriptionServer { using System; using System.Threading.Tasks; - using GraphQL.AspNet.Internal; using GraphQL.AspNet.Logging; using GraphQL.AspNet.Logging.SubscriptionEvents; using GraphQL.AspNet.SubscriptionServer; diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/SubscriptionClientDispatchQueueTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/SubscriptionClientDispatchQueueTests.cs index 318284034..30a137ea1 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/SubscriptionClientDispatchQueueTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/SubscriptionClientDispatchQueueTests.cs @@ -14,10 +14,8 @@ namespace GraphQL.AspNet.Tests.SubscriptionServer using System.Linq; using System.Threading; using System.Threading.Tasks; - using GraphQL.AspNet.Common; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Subscriptions; - using GraphQL.AspNet.Internal; using GraphQL.AspNet.SubscriptionServer; using Microsoft.Extensions.Logging; using NSubstitute; diff --git a/src/unit-tests/graphql-aspnet-testframework-tests/TestServerBuilderTests.cs b/src/unit-tests/graphql-aspnet-testframework-tests/TestServerBuilderTests.cs index ea427215a..982a0fb89 100644 --- a/src/unit-tests/graphql-aspnet-testframework-tests/TestServerBuilderTests.cs +++ b/src/unit-tests/graphql-aspnet-testframework-tests/TestServerBuilderTests.cs @@ -26,11 +26,14 @@ public void ParallelBuildSameControllerTest() Task.Run(BuildServer), Task.Run(BuildServer), Task.Run(BuildServer), + Task.Run(BuildServer), + Task.Run(BuildServer), + Task.Run(BuildServer), Task.Run(BuildServer), Task.Run(BuildServer)); } - [GraphRoute("with-param")] + [GraphRoute("withParam")] public class AppController : GraphController { [Query("get")] diff --git a/src/unit-tests/graphql-aspnet-testframework/GraphQLGlobalRestorePoint.cs b/src/unit-tests/graphql-aspnet-testframework/GraphQLGlobalRestorePoint.cs deleted file mode 100644 index 864e1545c..000000000 --- a/src/unit-tests/graphql-aspnet-testframework/GraphQLGlobalRestorePoint.cs +++ /dev/null @@ -1,81 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Tests.Framework -{ - using System; - using GraphQL.AspNet; - using GraphQL.AspNet.Engine; - using GraphQL.AspNet.Interfaces.Configuration; - using GraphQL.AspNet.Interfaces.Engine; - using GraphQL.AspNet.Schemas; - using Microsoft.Extensions.DependencyInjection; - - /// - /// A marker to a point in time that, when disposed, will reset the the global settings to the values - /// that were present just before this object was created. Used in conjunction with NUnit to undo any changes to - /// the global static providers in between tests. - /// - /// - /// If your test suite is configured to execute more than 1 test concurrently within the - /// same app space this could cause unexpected results. - /// - public class GraphQLGlobalRestorePoint : IDisposable - { - private readonly IGraphTypeTemplateProvider _templateProvider; - private readonly IScalarGraphTypeProvider _scalarTypeProvider; - private readonly IGraphTypeMakerProvider _makerProvider; - private readonly ServiceLifetime _controllerServiceLifetime; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true all providers will - /// be immediately reset to their default implementation until this restore point is disposed. - public GraphQLGlobalRestorePoint(bool resetAllProviders = false) - { - _templateProvider = GraphQLProviders.TemplateProvider; - _scalarTypeProvider = GraphQLProviders.ScalarProvider; - _makerProvider = GraphQLProviders.GraphTypeMakerProvider; - - _controllerServiceLifetime = GraphQLServerSettings.ControllerServiceLifeTime; - - if (resetAllProviders) - { - GraphQLProviders.TemplateProvider = new DefaultTypeTemplateProvider(); - GraphQLProviders.ScalarProvider = new DefaultScalarGraphTypeProvider(); - GraphQLProviders.GraphTypeMakerProvider = new DefaultGraphTypeMakerProvider(); - } - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - this.Dispose(true); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - GraphQLProviders.TemplateProvider = _templateProvider; - GraphQLProviders.ScalarProvider = _scalarTypeProvider; - GraphQLProviders.GraphTypeMakerProvider = _makerProvider; - - GraphQLServerSettings.ControllerServiceLifeTime = _controllerServiceLifetime; - } - } - } -} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-testframework/GraphQLTemplateHelper.cs b/src/unit-tests/graphql-aspnet-testframework/GraphQLTemplateHelper.cs index 04ccf6b03..3da9f4491 100644 --- a/src/unit-tests/graphql-aspnet-testframework/GraphQLTemplateHelper.cs +++ b/src/unit-tests/graphql-aspnet-testframework/GraphQLTemplateHelper.cs @@ -11,11 +11,19 @@ namespace GraphQL.AspNet.Tests.Framework { using System; using System.Linq; + using System.Runtime.CompilerServices; + using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Directives; using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; using GraphQL.AspNet.Tests.Framework.PipelineContextBuilders; /// @@ -31,8 +39,7 @@ public static class GraphQLTemplateHelper public static IGraphControllerTemplate CreateControllerTemplate() where TController : GraphController { - GraphQLProviders.TemplateProvider.Clear(); - return GraphQLProviders.TemplateProvider.ParseType() as IGraphControllerTemplate; + return CreateGraphTypeTemplate(TypeKind.CONTROLLER) as IGraphControllerTemplate; } /// @@ -49,7 +56,9 @@ public static IGraphFieldTemplate CreateActionMethodTemplate(string var template = new SingleMethodGraphControllerTemplate(methodName); template.Parse(); template.ValidateOrThrow(); - return template.FieldTemplates.FirstOrDefault(x => x.InternalName.Equals(methodName, StringComparison.OrdinalIgnoreCase)); + return template + .FieldTemplates + .FirstOrDefault(x => x.DeclaredName.Equals(methodName, StringComparison.OrdinalIgnoreCase)); } /// @@ -62,7 +71,20 @@ public static IGraphFieldTemplate CreateActionMethodTemplate(string /// IGraphTypeFieldTemplate. public static IGraphFieldTemplate CreateFieldTemplate(string fieldOrMethodName) { - var template = CreateGraphTypeTemplate() as IGraphTypeFieldTemplateContainer; + return CreateFieldTemplate(typeof(TType), fieldOrMethodName); + } + + /// + /// Helper method to create a field template for a controller or object method/property. This method will search both the + /// names of the fields as they would exist in an object graph as well as the declared names of methods/properties. THe first + /// found match is returned. + /// + /// Type entity that owns the field or method. + /// Name of the field as defined in the object graph or the name of the method/property. + /// IGraphTypeFieldTemplate. + public static IGraphFieldTemplate CreateFieldTemplate(Type ownerEntityType, string fieldOrMethodName) + { + var template = CreateGraphTypeTemplate(ownerEntityType, TypeKind.OBJECT) as IGraphTypeFieldTemplateContainer; // bit of a hack but it solves a lot of schema configuration differences that // can occur when setting up a test do to references occuring out of process @@ -75,7 +97,7 @@ public static IGraphFieldTemplate CreateFieldTemplate(string fieldOrMetho return kvp; } - throw new ArgumentOutOfRangeException(nameof(fieldOrMethodName), $"Test Setup Error. No field,method or property named '{fieldOrMethodName}' was found on the template of type '{typeof(TType).FriendlyName()}'."); + throw new ArgumentOutOfRangeException(nameof(fieldOrMethodName), $"Test Setup Error. No field,method or property named '{fieldOrMethodName}' was found on the template of type '{ownerEntityType.FriendlyName()}'."); } /// @@ -83,11 +105,51 @@ public static IGraphFieldTemplate CreateFieldTemplate(string fieldOrMetho /// /// The graph type to template. /// The kind. + /// if set to true the template will be parsed and validated before + /// being returned. Exceptions may be thrown if it does not parse correctly. /// IGraphItemTemplate. - public static ISchemaItemTemplate CreateGraphTypeTemplate(TypeKind? kind = null) + public static IGraphTypeTemplate CreateGraphTypeTemplate(TypeKind? kind = null, bool autoParse = true) { - GraphQLProviders.TemplateProvider.CacheTemplates = false; - return GraphQLProviders.TemplateProvider.ParseType(kind); + return CreateGraphTypeTemplate(typeof(TType), kind, autoParse); + } + + /// + /// Generates a schema template for a give type and kind combination. + /// + /// The graph type to template. + /// The kind. + /// if set to true the template will be parsed and validated before + /// being returned. Exceptions may be thrown if it does not parse correctly. + /// IGraphItemTemplate. + public static IGraphTypeTemplate CreateGraphTypeTemplate(Type objectType, TypeKind? kind = null, bool autoParse = true) + { + objectType = GlobalTypes.FindBuiltInScalarType(objectType) ?? objectType; + + IGraphTypeTemplate template; + if (Validation.IsCastable(objectType)) + template = new ScalarGraphTypeTemplate(objectType); + else if (Validation.IsCastable(objectType)) + template = new UnionGraphTypeTemplate(objectType); + else if (objectType.IsEnum) + template = new EnumGraphTypeTemplate(objectType); + else if (objectType.IsInterface) + template = new InterfaceGraphTypeTemplate(objectType); + else if (Validation.IsCastable(objectType)) + template = new GraphDirectiveTemplate(objectType); + else if (Validation.IsCastable(objectType)) + template = new GraphControllerTemplate(objectType); + else if (kind.HasValue && kind.Value == TypeKind.INPUT_OBJECT) + template = new InputObjectGraphTypeTemplate(objectType); + else + template = new ObjectGraphTypeTemplate(objectType); + + if (autoParse) + { + template.Parse(); + template.ValidateOrThrow(); + } + + return template; } /// @@ -96,7 +158,6 @@ public static ISchemaItemTemplate CreateGraphTypeTemplate(TypeKind? kind /// The type to create a template of. /// IObjectGraphTypeTemplate. public static IObjectGraphTypeTemplate CreateObjectTemplate() - where TObject : class { return CreateGraphTypeTemplate(TypeKind.OBJECT) as IObjectGraphTypeTemplate; } @@ -107,7 +168,6 @@ public static IObjectGraphTypeTemplate CreateObjectTemplate() /// The type to create a template of. /// IInputObjectGraphTypeTemplate. public static IInputObjectGraphTypeTemplate CreateInputObjectTemplate() - where TObject : class { return CreateGraphTypeTemplate(TypeKind.INPUT_OBJECT) as IInputObjectGraphTypeTemplate; } diff --git a/src/unit-tests/graphql-aspnet-testframework/Interfaces/ITestServerBuilder{TSchema}.cs b/src/unit-tests/graphql-aspnet-testframework/Interfaces/ITestServerBuilder{TSchema}.cs index 938ed6259..eccf25c9d 100644 --- a/src/unit-tests/graphql-aspnet-testframework/Interfaces/ITestServerBuilder{TSchema}.cs +++ b/src/unit-tests/graphql-aspnet-testframework/Interfaces/ITestServerBuilder{TSchema}.cs @@ -31,6 +31,12 @@ public interface ITestServerBuilder : ITestServerBuilder, IServiceColle /// TestServerBuilder<TSchema>. ITestServerBuilder AddTestComponent(IGraphQLTestFrameworkComponent component); + /// + ITestServerBuilder AddGraphType(TypeKind? typeKind = null); + + /// + ITestServerBuilder AddGraphType(Type type, TypeKind? typeKind = null); + /// ITestServerBuilder AddType(TypeKind? typeKind = null); diff --git a/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/DirectiveContextBuilder.cs b/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/DirectiveContextBuilder.cs new file mode 100644 index 000000000..08ec0b1bc --- /dev/null +++ b/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/DirectiveContextBuilder.cs @@ -0,0 +1,259 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Framework.PipelineContextBuilders +{ + using System; + using System.Threading; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Directives; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Execution.Contexts; + using GraphQL.AspNet.Execution.QueryPlans.InputArguments; + using GraphQL.AspNet.Execution.Source; + using GraphQL.AspNet.Execution.Variables; + using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Interfaces.Execution.QueryPlans.DocumentParts; + using GraphQL.AspNet.Interfaces.Execution.QueryPlans.InputArguments; + using GraphQL.AspNet.Interfaces.Logging; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Interfaces.Security; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Security; + using NSubstitute; + + /// + /// A builder used to create inlined mocked replacements + /// of various data to setup a test scenario targeting a single directive resolution. + /// + public class DirectiveContextBuilder + { + private readonly IDirective _directive; + private readonly ISchema _schema; + private readonly IGraphDirectiveRequest _mockRequest; + private readonly IDirectiveInvocationContext _mockInvocationContext; + private readonly IGraphMessageCollection _messageCollection; + private readonly IInputArgumentCollection _arguments; + private readonly IGraphFieldResolverMetaData _mockResolverMetadata; + private DirectiveLocation _location; + private IUserSecurityContext _securityContext; + private object _directiveTarget; + + /// + /// Initializes a new instance of the class. + /// + /// The service provider. + /// The user security context. + /// The schema where the directive is declared. + /// The directive to invoke. + /// The location where this directive is targeting. This information is presented to the directive + /// when it is invoked. + /// The phase of execution where the directive is being processed. + /// The metadata describing the method/functon to be invoked by a resolver. + public DirectiveContextBuilder( + IServiceProvider serviceProvider, + IUserSecurityContext userSecurityContext, + ISchema schema, + IDirective directive, + DirectiveLocation directiveLocation, + DirectiveInvocationPhase phase, + IGraphFieldResolverMetaData resolverMetadata) + { + _schema = Validation.ThrowIfNullOrReturn(schema, nameof(schema)); + _securityContext = Validation.ThrowIfNullOrReturn(userSecurityContext, nameof(userSecurityContext)); + _messageCollection = new GraphMessageCollection(); + _arguments = InputArgumentCollectionFactory.Create(); + _location = directiveLocation; + _directive = directive; + + this.ServiceProvider = Validation.ThrowIfNullOrReturn(serviceProvider, nameof(serviceProvider)); + + _mockInvocationContext = Substitute.For(); + _mockInvocationContext.Directive.Returns(_directive); + _mockInvocationContext.Arguments.Returns(_arguments); + _mockInvocationContext.Origin.Returns(SourceOrigin.None); + _mockInvocationContext.Location.Returns((x) => _location); + + // fake the request for the directive + // (normally generated by the primary query execution pipeline) + var id = Guid.NewGuid(); + _mockRequest = Substitute.For(); + _mockRequest.Id.Returns(id); + _mockRequest.Origin.Returns(SourceOrigin.None); + _mockRequest.Directive.Returns(_directive); + _mockRequest.DirectiveTarget.Returns((x) => _directiveTarget); + _mockRequest.InvocationContext.Returns(_mockInvocationContext); + _mockRequest.DirectivePhase.Returns(phase); + + // copy in the resolver to a controlled mock (vs. whatever implementation was passed) + if (resolverMetadata != null) + { + _mockResolverMetadata = Substitute.For(); + _mockResolverMetadata.ParentInternalName.Returns(resolverMetadata.ParentInternalName); + _mockResolverMetadata.ParentObjectType.Returns(resolverMetadata.ParentObjectType); + _mockResolverMetadata.ExpectedReturnType.Returns(resolverMetadata.ExpectedReturnType); + _mockResolverMetadata.Method.Returns(resolverMetadata.Method); + _mockResolverMetadata.IsAsyncField.Returns(resolverMetadata.IsAsyncField); + _mockResolverMetadata.InternalName.Returns(resolverMetadata.InternalName); + _mockResolverMetadata.InternalName.Returns(resolverMetadata.InternalName); + _mockResolverMetadata.Parameters.Returns(resolverMetadata.Parameters); + } + } + + /// + /// Mimics a specific location in a document text. This value is passed to the request instead of + /// using . This information is handed to any context created by this builder. + /// + /// An origin within a query document text. + /// MockFieldExecutionContext. + public DirectiveContextBuilder AddOrigin(SourceOrigin origin) + { + _mockRequest.Origin.Returns(origin); + _mockInvocationContext.Origin.Returns(origin); + return this; + } + + /// + /// Simulate an argument being passed to the directive from a query document. It is assumed the value will cast correctly + /// and will not cause an error. + /// + /// Name of the argument on the directive, as declared in the schema. + /// A fully resolved value to use. + /// DirectiveContextBuilder. + public DirectiveContextBuilder AddInputArgument(string argumentName, object value) + { + var resolvedInputValue = new ResolvedInputArgumentValue(argumentName, value); + var arg = _directive.Arguments[argumentName]; + var inputArgument = new InputArgument(arg, resolvedInputValue, SourceOrigin.None); + _arguments.Add(inputArgument); + return this; + } + + /// + /// Alters the current security context to be different than that provided by the server that created this builder. + /// + /// The new security context to use. + /// MockFieldRequest. + public DirectiveContextBuilder AddSecurityContext(IUserSecurityContext securityContext) + { + _securityContext = securityContext; + return this; + } + + /// + /// + /// Adds a specific target that is passed to a directive resolver during resolution. + /// + /// For execution directives this is an object that implements . + /// + /// + /// For type system directives this is an object that implements . + /// + /// + /// The object being targeted by the directive invocation. + /// DirectiveContextBuilder. + public DirectiveContextBuilder AddTarget(object target) + { + _directiveTarget = target; + return this; + } + + private IGraphQLMiddlewareExecutionContext CreateFakeParentMiddlewareContext() + { + var queryRequest = Substitute.For(); + var parentContext = Substitute.For(); + + parentContext.QueryRequest.Returns(queryRequest); + parentContext.ServiceProvider.Returns(this.ServiceProvider); + parentContext.SecurityContext.Returns(_securityContext); + parentContext.Metrics.Returns(null as IQueryExecutionMetrics); + parentContext.Logger.Returns(null as IGraphEventLogger); + parentContext.Messages.Returns(_messageCollection); + parentContext.IsValid.Returns(_messageCollection.IsSucessful); + parentContext.Session.Returns(new QuerySession()); + return parentContext; + } + + /// + /// Creates an authorization context that can be used to test authorization middleware components. + /// + /// SchemaItemSecurityChallengeContext. + public SchemaItemSecurityChallengeContext CreateSecurityContext() + { + var parent = this.CreateFakeParentMiddlewareContext(); + + var request = new SchemaItemSecurityRequest(this.DirectiveRequest); + return new SchemaItemSecurityChallengeContext( + parent, + request); + } + + /// + /// Creates a qualified execution context that can be passed to the directive execution pipeline + /// to test middleware components. + /// + /// GraphDirectiveExecutionContext. + public GraphDirectiveExecutionContext CreateExecutionContext() + { + return new GraphDirectiveExecutionContext( + _schema, + this.CreateFakeParentMiddlewareContext(), + this.DirectiveRequest, + ResolvedVariableCollectionFactory.Create(), + _securityContext.DefaultUser); + } + + /// + /// Creates the resolution context that can be passed to a resolver for resolution. + /// + /// FieldResolutionContext. + public DirectiveResolutionContext CreateResolutionContext() + { + var context = this.CreateExecutionContext(); + + ExecutionArgumentGenerator.TryConvert( + _arguments, + context.VariableData, + context.Messages, + out var executionArguments); + + return new DirectiveResolutionContext( + this.ServiceProvider, + context.Session, + _schema, + context.QueryRequest, + this.DirectiveRequest, + executionArguments, + context.Messages, + context.Logger, + context.User, + CancellationToken.None); + } + + /// + /// Gets a reference to the directive request to be contained in the context. + /// + /// The field request. + public IGraphDirectiveRequest DirectiveRequest => _mockRequest; + + /// + /// Gets or sets the service provider used in all created contexts. + /// + /// The service provider. + public IServiceProvider ServiceProvider { get; set; } + + /// + /// Gets a mock reference to the root method that will be resonsible for resolving the context in case any direct + /// invocation tests are needed. Otherwise, this property is not used in resolving a context put directly + /// against the testserver. + /// + /// The metadata describing the resolver method to be invoked. + public IGraphFieldResolverMetaData ResolverMetaData => _mockResolverMetadata; + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/FieldContextBuilder.cs b/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/FieldContextBuilder.cs index a92dffd49..4b42b4622 100644 --- a/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/FieldContextBuilder.cs +++ b/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/FieldContextBuilder.cs @@ -10,22 +10,23 @@ namespace GraphQL.AspNet.Tests.Framework.PipelineContextBuilders { using System; + using System.Threading; using GraphQL.AspNet.Common; - using GraphQL.AspNet.Execution.Source; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Directives; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Contexts; using GraphQL.AspNet.Execution.FieldResolution; using GraphQL.AspNet.Execution.QueryPlans.InputArguments; + using GraphQL.AspNet.Execution.Source; using GraphQL.AspNet.Execution.Variables; using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Interfaces.Execution.QueryPlans.DocumentParts; + using GraphQL.AspNet.Interfaces.Execution.QueryPlans.InputArguments; using GraphQL.AspNet.Interfaces.Logging; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Interfaces.Security; using GraphQL.AspNet.Security; - using GraphQL.AspNet.Interfaces.Execution.QueryPlans.DocumentParts; - using GraphQL.AspNet.Interfaces.Execution.QueryPlans.InputArguments; using NSubstitute; /// @@ -41,7 +42,7 @@ public class FieldContextBuilder private readonly IFieldDocumentPart _mockFieldDocumentPart; private readonly IGraphMessageCollection _messageCollection; private readonly IInputArgumentCollection _arguments; - + private readonly IGraphFieldResolverMetaData _mockResolverMetaData; private IUserSecurityContext _securityContext; /// @@ -51,13 +52,13 @@ public class FieldContextBuilder /// The user security context. /// The graph field. /// The schema. - /// The metadata describing the method/functon to be invoked by a resolver. + /// The metadata describing the method/functon to be invoked by a resolver. public FieldContextBuilder( IServiceProvider serviceProvider, IUserSecurityContext userSecurityContext, IGraphField graphField, ISchema schema, - IGraphFieldResolverMethod graphMethod) + IGraphFieldResolverMetaData resolverMetadata) { _schema = Validation.ThrowIfNullOrReturn(schema, nameof(schema)); _graphField = Validation.ThrowIfNullOrReturn(graphField, nameof(graphField)); @@ -69,10 +70,13 @@ public FieldContextBuilder( Type expectedInputType = null; - if (!Validation.IsCastable(graphMethod.Parent.ObjectType) - && !Validation.IsCastable(graphMethod.Parent.ObjectType)) + if (!Validation.IsCastable(resolverMetadata.ParentObjectType) + && !Validation.IsCastable(resolverMetadata.ParentObjectType)) { - expectedInputType = graphMethod.Parent.ObjectType; + if (graphField.Parent is IInterfaceGraphType iif) + expectedInputType = iif.ObjectType; + else if (graphField.Parent is IObjectGraphType ogt) + expectedInputType = ogt.ObjectType; } _mockFieldDocumentPart = Substitute.For(); @@ -99,17 +103,15 @@ public FieldContextBuilder( _mockRequest.Field.Returns(_graphField); _mockRequest.InvocationContext.Returns(_mockInvocationContext); - this.GraphMethod = Substitute.For(); - this.GraphMethod.Parent.Returns(graphMethod.Parent); - this.GraphMethod.ObjectType.Returns(graphMethod.ObjectType); - this.GraphMethod.ExpectedReturnType.Returns(graphMethod.ExpectedReturnType); - this.GraphMethod.Method.Returns(graphMethod.Method); - this.GraphMethod.IsAsyncField.Returns(graphMethod.IsAsyncField); - this.GraphMethod.Name.Returns(graphMethod.Name); - this.GraphMethod.InternalFullName.Returns(graphMethod.InternalFullName); - this.GraphMethod.InternalName.Returns(graphMethod.InternalName); - this.GraphMethod.Route.Returns(graphMethod.Route); - this.GraphMethod.Arguments.Returns(graphMethod.Arguments); + _mockResolverMetaData = Substitute.For(); + _mockResolverMetaData.ParentInternalName.Returns(resolverMetadata.ParentInternalName); + _mockResolverMetaData.ParentObjectType.Returns(resolverMetadata.ParentObjectType); + _mockResolverMetaData.ExpectedReturnType.Returns(resolverMetadata.ExpectedReturnType); + _mockResolverMetaData.Method.Returns(resolverMetadata.Method); + _mockResolverMetaData.IsAsyncField.Returns(resolverMetadata.IsAsyncField); + _mockResolverMetaData.InternalName.Returns(resolverMetadata.InternalName); + _mockResolverMetaData.Parameters.Returns(resolverMetadata.Parameters); + _mockResolverMetaData.DefinitionSource.Returns(resolverMetadata.DefinitionSource); } /// @@ -142,7 +144,7 @@ public FieldContextBuilder AddOrigin(SourceOrigin origin) /// /// Adds a value for a given input argument to this field. It is assumed the value will cast correctly. /// - /// Name of the input field, as declared in the schema, on the field being mocked. + /// Name of the argument, as declared in the schema, on the field. /// A fully resolved value to use. /// MockFieldRequest. public FieldContextBuilder AddInputArgument(string argumentName, object value) @@ -155,9 +157,9 @@ public FieldContextBuilder AddInputArgument(string argumentName, object value) } /// - /// alters the security context to be different than that provided by the server that created this builder. + /// Alters the security context to be different than that provided by the test server that created this builder. /// - /// The security context. + /// The user security context. /// MockFieldRequest. public FieldContextBuilder AddSecurityContext(IUserSecurityContext securityContext) { @@ -182,9 +184,9 @@ private IGraphQLMiddlewareExecutionContext CreateFakeParentMiddlewareContext() } /// - /// Creates an authorization context to validate the field request this builder is creating. + /// Creates an authorization context that can be used to test authorization middleware components. /// - /// GraphFieldAuthorizationContext. + /// SchemaItemSecurityChallengeContext. public SchemaItemSecurityChallengeContext CreateSecurityContext() { var parent = this.CreateFakeParentMiddlewareContext(); @@ -196,7 +198,7 @@ public SchemaItemSecurityChallengeContext CreateSecurityContext() } /// - /// Creates this instance. + /// Creates an execution context that can be used to test field middleware pipeline contents. /// /// GraphFieldExecutionContext. public GraphFieldExecutionContext CreateExecutionContext() @@ -208,8 +210,7 @@ public GraphFieldExecutionContext CreateExecutionContext() } /// - /// Creates the resolution context capable of being acted on directly by a resolver, as opposed to being - /// processed through a pipeline. + /// Creates the resolution context that can be passed to a resolver to test field resolution. /// /// FieldResolutionContext. public FieldResolutionContext CreateResolutionContext() @@ -222,13 +223,17 @@ public FieldResolutionContext CreateResolutionContext() context.Messages, out var executionArguments); - executionArguments = executionArguments.ForContext(context); - return new FieldResolutionContext( + this.ServiceProvider, + context.Session, _schema, - this.CreateFakeParentMiddlewareContext(), + context.QueryRequest, this.FieldRequest, - executionArguments); + executionArguments, + context.Messages, + context.Logger, + context.User, + CancellationToken.None); } /// @@ -249,6 +254,6 @@ public FieldResolutionContext CreateResolutionContext() /// against the testserver. /// /// The graph method. - public IGraphFieldResolverMethod GraphMethod { get; } + public IGraphFieldResolverMetaData ResolverMetaData => _mockResolverMetaData; } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/QueryContextBuilder.cs b/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/QueryContextBuilder.cs index 472705792..97a4f48d8 100644 --- a/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/QueryContextBuilder.cs +++ b/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/QueryContextBuilder.cs @@ -19,6 +19,7 @@ namespace GraphQL.AspNet.Tests.Framework.PipelineContextBuilders using GraphQL.AspNet.Interfaces.Logging; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Interfaces.Security; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; using NSubstitute; @@ -157,7 +158,7 @@ public virtual QueryExecutionContext Build() foreach (var kvp in _sourceData) { var mockField = Substitute.For(); - mockField.FieldSource.Returns(Internal.TypeTemplates.GraphFieldSource.Action); + mockField.FieldSource.Returns(GraphFieldSource.Action); mockField.Route.Returns(kvp.Key); context.DefaultFieldSources.AddSource(mockField, kvp.Value); } diff --git a/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/SingleMethodGraphControllerTemplate.cs b/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/SingleMethodGraphControllerTemplate.cs index feaa2f4f0..9e7cab6a0 100644 --- a/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/SingleMethodGraphControllerTemplate.cs +++ b/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/SingleMethodGraphControllerTemplate.cs @@ -9,27 +9,26 @@ namespace GraphQL.AspNet.Tests.Framework.PipelineContextBuilders { - using System.Reflection; - using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Internal.TypeTemplates; + using System; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; /// /// A mocked controller template that will selectively parse actions instead of the whole template. /// - /// The type of the controller to templatize. - public class SingleMethodGraphControllerTemplate : GraphControllerTemplate - where TControllerType : GraphController + public class SingleMethodGraphControllerTemplate : GraphControllerTemplate { private readonly string _methodName; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// + /// Type of the controller. /// Name of the single action method to parse. When not - /// provided (e.g. null) this template will function the same as + /// provided (e.g. null) this template will function the same as /// and all methods will be parsed. - public SingleMethodGraphControllerTemplate(string methodName = null) - : base(typeof(TControllerType)) + public SingleMethodGraphControllerTemplate(Type controllerType, string methodName = null) + : base(controllerType) { _methodName = methodName; } @@ -41,9 +40,9 @@ public SingleMethodGraphControllerTemplate(string methodName = null) /// /// The member information to check. /// true if the info represents a possible graph field; otherwise, false. - protected override bool CouldBeGraphField(MemberInfo memberInfo) + protected override bool CouldBeGraphField(IMemberInfoProvider memberInfo) { - if (_methodName != null && memberInfo.Name != _methodName) + if (_methodName != null && memberInfo.MemberInfo.Name != _methodName) return false; return base.CouldBeGraphField(memberInfo); diff --git a/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/SingleMethodGraphControllerTemplate{TControllerType}.cs b/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/SingleMethodGraphControllerTemplate{TControllerType}.cs new file mode 100644 index 000000000..978d708db --- /dev/null +++ b/src/unit-tests/graphql-aspnet-testframework/PipelineContextBuilders/SingleMethodGraphControllerTemplate{TControllerType}.cs @@ -0,0 +1,33 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Framework.PipelineContextBuilders +{ + using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + + /// + /// A mocked controller template that will selectively parse actions instead of the whole template. + /// + /// The type of the controller to templatize. + public class SingleMethodGraphControllerTemplate : SingleMethodGraphControllerTemplate + where TControllerType : GraphController + { + /// + /// Initializes a new instance of the class. + /// + /// Name of the single action method to parse. When not + /// provided (e.g. null) this template will function the same as + /// and all methods will be parsed. + public SingleMethodGraphControllerTemplate(string methodName = null) + : base(typeof(TControllerType), methodName) + { + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-testframework/TestServer.cs b/src/unit-tests/graphql-aspnet-testframework/TestServer.cs index 4ebca6190..b329cc9fe 100644 --- a/src/unit-tests/graphql-aspnet-testframework/TestServer.cs +++ b/src/unit-tests/graphql-aspnet-testframework/TestServer.cs @@ -11,33 +11,26 @@ namespace GraphQL.AspNet.Tests.Framework { using System; using System.IO; - using System.Linq; using System.Text.Encodings.Web; using System.Text.Json; using System.Threading; using System.Threading.Tasks; + using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Directives; using GraphQL.AspNet.Engine; - using GraphQL.AspNet.Engine.TypeMakers; - using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Contexts; - using GraphQL.AspNet.Execution.FieldResolution; - using GraphQL.AspNet.Execution.QueryPlans.InputArguments; using GraphQL.AspNet.Execution.Response; using GraphQL.AspNet.Execution.Source; - using GraphQL.AspNet.Execution.Variables; using GraphQL.AspNet.Interfaces.Engine; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Execution.QueryPlans.DocumentParts; - using GraphQL.AspNet.Interfaces.Execution.QueryPlans.InputArguments; - using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Interfaces.Logging; using GraphQL.AspNet.Interfaces.Middleware; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Interfaces.Security; using GraphQL.AspNet.Interfaces.Web; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Tests.Framework.PipelineContextBuilders; using Microsoft.AspNetCore.Http; @@ -177,222 +170,175 @@ public virtual Task CreateQueryPlan(string queryText, strin /// GraphTypeCreationResult. public virtual GraphTypeCreationResult CreateGraphType(Type concreteType, TypeKind kind) { - var maker = GraphQLProviders.GraphTypeMakerProvider.CreateTypeMaker(this.Schema, kind); - return maker.CreateGraphType(concreteType); + var factory = new GraphTypeMakerFactory(this.Schema); + + var maker = factory.CreateTypeMaker(concreteType, kind); + var template = factory.MakeTemplate(concreteType); + return maker.CreateGraphType(template); } /// - /// Creates a builder that will generate a field execution context for an action on a target controller. This - /// context can be submitted against the field execution pipeline to generate a result. + /// (DEPRECATED, DO NOT USE). + /// + /// The concrete type representing the graph type in the schema. + /// Name of the field, on the type, as it exists in the schema. + /// The source data to use as the input to the field. This can be changed, but must be supplied. A + /// generic will be used if not supplied. + /// The type kind to resolve the field as (only necessary for input object types). + /// FieldContextBuilder. + [Obsolete("Use " + nameof(CreateFieldContextBuilder) + " Instead")] + public virtual FieldContextBuilder CreateGraphTypeFieldContextBuilder(string fieldName, object sourceData, TypeKind typeKind) + { + return CreateFieldContextBuilder(fieldName, sourceData); + } + + /// + /// (DEPRECATED, DO NOT USE). /// /// The type of the controller that owns the /// action. /// Name of the action/field in the controller, as it exists in the schema. /// FieldContextBuilder. - public virtual FieldContextBuilder CreateGraphTypeFieldContextBuilder(string actionName) - where TController : GraphController + [Obsolete("Use " + nameof(CreateFieldContextBuilder) + " Instead")] + public virtual FieldContextBuilder CreateActionMethodFieldContextBuilder(string actionName) { - var template = GraphQLTemplateHelper.CreateFieldTemplate(actionName); - var fieldMaker = new GraphFieldMaker(this.Schema); - var fieldResult = fieldMaker.CreateField(template); - - var builder = new FieldContextBuilder( - this.ServiceProvider, - _userSecurityContext, - fieldResult.Field, - this.Schema, - template as IGraphFieldResolverMethod); - - builder.AddSourceData(new object()); - return builder; + return CreateFieldContextBuilder(actionName); } /// - /// Creates a low level field execution context that can be processed by the test server. + /// Creates a builder that will generate a field execution context for an action on a target controller. This + /// context can be submitted against the field execution pipeline to generate a result. /// - /// The concrete type representing the graph type in the schema. - /// Name of the field, on the type, as it exists in the schema. - /// The source data to use as the input to the field. This can be changed, but must be supplied. A - /// generic will be used if not supplied. - /// The collection of arguments that need to be supplied - /// to the field to properly resolve it. - /// FieldContextBuilder. - public virtual GraphFieldExecutionContext CreateFieldExecutionContext( - string fieldName, - object sourceData, - IInputArgumentCollection arguments = null) + /// The type of the entity that owns . This entity must be a + /// controller or an entity representing an OBJECT graph type that is registered to the schema. + /// Name of the field as it appears in the graph or the name of the action, method or property + /// as it appears on the . This parameter is case sensitive. + /// (optional) A source data object to supply to the builder. + /// A builder that can be invoked against a controller to resolve a field. + public virtual FieldContextBuilder CreateFieldContextBuilder(string fieldOrActionName, object sourceData = null) { - IGraphType graphType = this.Schema.KnownTypes.FindGraphType(typeof(TType)); - - if (graphType == null) - { - throw new InvalidOperationException($"Unable to locate a registered graph type that matched the supplied source data (Type: {typeof(TType).FriendlyName()})"); - } - - var typedGraphType = graphType as ITypedSchemaItem; - if (typedGraphType == null) - { - throw new InvalidOperationException($"The target graph type '{graphType.Name}' is not a strongly typed graph type and cannot be invoked via this builder."); - } + return this.CreateFieldContextBuilder(typeof(TEntity), fieldOrActionName, sourceData); + } - var container = graphType as IGraphFieldContainer; - if (container == null) + /// + /// Attempts to search the schema for a field with the given internal name. If more than one field + /// with the same name is found, the first instance is used. A context builder is then created + /// for the found field. + /// + /// The internal name assigned to the fild. + /// The source data. + /// A builder that can be invoked against a controller to resolve a field. + public virtual FieldContextBuilder CreateFieldContextBuilder(string internalName, object sourceData = null) + { + IGraphField field = null; + foreach (var f in this.Schema.AllSchemaItems()) { - throw new InvalidOperationException($"The target graph type '{graphType.Name}' is not a field container. No field context builder can be created."); + if (f is IGraphField gf && gf.InternalName == internalName) + { + field = gf; + break; + } } - var field = container.Fields.FindField(fieldName); if (field == null) { - throw new InvalidOperationException($"The target graph type '{graphType.Name}' does not contain a field named '{fieldName}'."); + throw new InvalidOperationException($"A field with the internal name of '{internalName}' was not found. " + + $"Internal names for fields are case sensitive."); } - arguments = arguments ?? InputArgumentCollectionFactory.Create(); - var messages = new GraphMessageCollection(); - var metaData = new MetaDataCollection(); - - var queryRequest = Substitute.For(); - var fieldInvocationContext = Substitute.For(); - var parentContext = Substitute.For(); - var graphFieldRequest = Substitute.For(); - var fieldDocumentPart = Substitute.For(); - - queryRequest.Items.Returns(metaData); - - parentContext.QueryRequest.Returns(queryRequest); - parentContext.ServiceProvider.Returns(this.ServiceProvider); - parentContext.SecurityContext.Returns(this.SecurityContext); - parentContext.Metrics.Returns(null as IQueryExecutionMetrics); - parentContext.Logger.Returns(null as IGraphEventLogger); - parentContext.Messages.Returns((x) => messages); - parentContext.IsValid.Returns((x) => messages.IsSucessful); - parentContext.Session.Returns(new QuerySession()); - - fieldDocumentPart.Name.Returns(field.Name); - fieldDocumentPart.Alias.Returns(field.Name); - fieldDocumentPart.Field.Returns(field); - - fieldInvocationContext.ExpectedSourceType.Returns(typeof(TType)); - fieldInvocationContext.Field.Returns(field); - fieldInvocationContext.Arguments.Returns(arguments); - fieldInvocationContext.Name.Returns(field.Name); - fieldInvocationContext.ChildContexts.Returns(new FieldInvocationContextCollection()); - fieldInvocationContext.Origin.Returns(SourceOrigin.None); - fieldInvocationContext.Schema.Returns(this.Schema); - fieldInvocationContext.FieldDocumentPart.Returns(fieldDocumentPart); - - var resolvedParentDataItem = new FieldDataItem( - fieldInvocationContext, - sourceData, - SourcePath.None); - - var sourceDataContainer = new FieldDataItemContainer( - sourceData, - SourcePath.None, - resolvedParentDataItem); - - var id = Guid.NewGuid(); - graphFieldRequest.Id.Returns(id); - graphFieldRequest.Origin.Returns(SourceOrigin.None); - graphFieldRequest.Field.Returns(field); - graphFieldRequest.InvocationContext.Returns(fieldInvocationContext); - graphFieldRequest.Data.Returns((x) => sourceDataContainer); - - return new GraphFieldExecutionContext( - parentContext, - graphFieldRequest, - ResolvedVariableCollectionFactory.Create()); + return this.CreateFieldContextBuilder(field, sourceData); } /// - /// Creates a mocked context for the execution of a single field of data against the given concrete type and field name. This - /// context can be submitted against the field execution pipeline to generate a result. + /// Creates a builder targeting the field owned the the combination entity and field, method or property name. /// - /// The concrete type representing the graph type in the schema. - /// Name of the field, on the type, as it exists in the schema. - /// The source data to use as the input to the field. This can be changed, but must be supplied. A - /// generic will be used if not supplied. - /// The type kind to resolve the field as (only necessary for input object types). - /// FieldContextBuilder. - public virtual FieldContextBuilder CreateGraphTypeFieldContextBuilder(string fieldName, object sourceData, TypeKind? typeKind = null) + /// Type of the entity that owns the . This entity must be a + /// controller or an entity representing an OBJECT graph type that is registered to the schema + /// Name of the field as it appears in the graph or the name of the action, method or property + /// as it appears on the . This parameter is case sensitive. + /// (optional) A source data object to supply to the builder. + /// A builder that can be invoked against a controller to resolve a field. + public virtual FieldContextBuilder CreateFieldContextBuilder(Type entityType, string fieldOrActionName, object sourceData = null) { - IGraphType graphType = this.Schema.KnownTypes.FindGraphType(typeof(TType)); + Validation.ThrowIfNull(entityType, nameof(entityType)); - if (graphType == null) - { - throw new InvalidOperationException($"Unable to locate a registered graph type that matched the supplied source data (Type: {typeof(TType).FriendlyName()})"); - } + IGraphField field = null; + fieldOrActionName = Validation.ThrowIfNullWhiteSpaceOrReturn(fieldOrActionName, nameof(fieldOrActionName)); - var typedGraphType = graphType as ITypedSchemaItem; - if (typedGraphType == null) + if (Validation.IsCastable(entityType)) { - throw new InvalidOperationException($"The target graph type '{graphType.Name}' is not a strongly typed graph type and cannot be invoked via this builder."); + var fieldTemplate = GraphQLTemplateHelper.CreateFieldTemplate(entityType, fieldOrActionName); + var fieldMaker = new GraphFieldMaker(this.Schema, new GraphArgumentMaker(this.Schema)); + field = fieldMaker.CreateField(fieldTemplate)?.Field; } - - var container = graphType as IGraphFieldContainer; - if (container == null) + else { - throw new InvalidOperationException($"The target graph type '{graphType.Name}' is not a field container. No field context builder can be created."); + var graphType = this.Schema.KnownTypes.FindGraphType(entityType, TypeKind.OBJECT) as IObjectGraphType; + if (graphType == null) + { + throw new InvalidOperationException($"Unknown or unregistered OBJECT graph for type {entityType.FriendlyName()}. This method " + + $"can only create a context builder for OBJECT graph types."); + } + + var typedGraphType = graphType as ITypedSchemaItem; + if (typedGraphType == null) + { + throw new InvalidOperationException($"The target graph type '{graphType.Name}' is not a strongly typed graph type and cannot be invoked via this builder."); + } + + var container = graphType as IGraphFieldContainer; + if (container == null) + { + throw new InvalidOperationException($"The target graph type '{graphType.Name}' is not a field container. No field context builder can be created."); + } + + // find the field on the graph type + field = graphType.Fields.FindField(fieldOrActionName); + if (field == null) + { + // fallback, try and find by method/property name + foreach (var item in graphType.Fields) + { + if (item.Resolver.MetaData.DeclaredName == fieldOrActionName) + { + field = item; + break; + } + } + } } - var field = container.Fields.FindField(fieldName); if (field == null) { - throw new InvalidOperationException($"The target graph type '{graphType.Name}' does not contain a field named '{fieldName}'."); + throw new InvalidOperationException($"The entity '{entityType.FriendlyName()}' does not contain a field, action, method or property named '{fieldOrActionName}'. " + + $"Field names are case sensitive."); } - var graphMethod = this.CreateInvokableReference(fieldName, typeKind); + return this.CreateFieldContextBuilder(field, sourceData); + } + /// + /// Creates a builder targeting the supplied field. + /// + /// The field to create a context builder for. + /// (optional) A source data object to supply to the builder. + /// A builder that can be invoked against a controller to resolve a field. + public FieldContextBuilder CreateFieldContextBuilder(IGraphField field, object sourceData = null) + { + var metaData = field.Resolver.MetaData; var builder = new FieldContextBuilder( this.ServiceProvider, _userSecurityContext, field, this.Schema, - graphMethod); + metaData); builder.AddSourceData(sourceData); - return builder; } /// - /// Creates a reference to the invokable method or property that represents the - /// root resolver for the given . (i.e. the method - /// or property of the object that can produce the core data value). - /// - /// The type of the object to create a reference from. - /// Name of the field. - /// The type kind to resolve the field as (only necessary for input object types). - /// IGraphMethod. - public virtual IGraphFieldResolverMethod CreateInvokableReference(string fieldName, TypeKind? typeKind = null) - { - var template = GraphQLTemplateHelper.CreateGraphTypeTemplate(typeKind); - var fieldContainer = template as IGraphTypeFieldTemplateContainer; - if (fieldContainer == null) - { - throw new InvalidOperationException($"The provided type '{typeof(TObjectType).FriendlyName()}' is not " + - $"a field container, no invokable method references can be created from it."); - } - - var fieldTemplate = fieldContainer.FieldTemplates - .SingleOrDefault(x => string.Compare(x.Name, fieldName, StringComparison.OrdinalIgnoreCase) == 0); - - if (fieldTemplate == null) - { - throw new InvalidOperationException($"The provided type '{typeof(TObjectType).FriendlyName()}' does not " + $"contain a field named '{fieldName}'."); - } - - var method = fieldTemplate as IGraphFieldResolverMethod; - if (method == null) - { - throw new InvalidOperationException($"The field named '{fieldName}' on the provided type '{typeof(TObjectType).FriendlyName()}' " + $"does not represent an invokable {typeof(IGraphFieldResolverMethod)}. Operation cannot proceed."); - } - - return method; - } - - /// - /// Creates an execution context to invoke a directive execution pipeleine. + /// /// (DEPRECATED, DO NOT USE). /// /// The type of the directive to invoke. /// The target location from where the directive is being called. @@ -401,6 +347,7 @@ public virtual IGraphFieldResolverMethod CreateInvokableReference(s /// The origin in a source document, if any. /// The arguments to pass to the directive, if any. /// GraphDirectiveExecutionContext. + [Obsolete("Use " + nameof(CreateDirectiveContextBuilder) + " Instead")] public virtual GraphDirectiveExecutionContext CreateDirectiveExecutionContext( DirectiveLocation location, object directiveTarget, @@ -409,51 +356,133 @@ public virtual GraphDirectiveExecutionContext CreateDirectiveExecutionContext() - .Build(); + var builder = this.CreateDirectiveContextBuilder( + typeof(TDirective), + location, + directiveTarget, + phase, + origin, + arguments); + + return builder.CreateExecutionContext(); + } + + /// + /// Creates a builder targeting a specific directive. This builder can be used to create execution contexts (targeting middleware components) + /// and resolution contexts (targeting specific directive resolvers) in order to perform different types of tests. + /// + /// The type of the to build for. + /// The directive location, of those accepted by the directive. + /// The directive target. Typically an for execution directives + /// or an for type system directives. + /// The phase of execution. This value will be passed to the middleware item or resolver. + /// The origin location within a query document. This value will be passed to the middleware item + /// or resolver. + /// The set of input argument values accepted by the directive. These arguments are expected + /// to be supplied in order of definition within the schema and castable to the appropriate data types. This builder + /// will NOT attempt any validation or mangling on these values. + /// DirectiveContextBuilder. + public virtual DirectiveContextBuilder CreateDirectiveContextBuilder( + DirectiveLocation directiveLocation, + object directiveTarget = null, + DirectiveInvocationPhase phase = DirectiveInvocationPhase.QueryDocumentExecution, + SourceOrigin origin = default, + object[] arguments = null) + { + var builder = this.CreateDirectiveContextBuilder( + typeof(TDirectiveType), + directiveLocation, + directiveTarget, + phase, + origin, + arguments); + + return builder; + } - var targetDirective = server.Schema.KnownTypes.FindDirective(typeof(TDirective)); + /// + /// Creates a builder targeting a specific directive. This builder can be used to create execution contexts (targeting middleware components) + /// and resolution contexts (targeting specific directive resolvers) in order to perform different types of tests. + /// + /// + /// If the target is not a valid directive or not registered to the schema a context builder + /// will still be created with null values to test various scenarios. + /// + /// The type of the directive that is registered to teh schema. + /// The directive location, of those accepted by the directive. + /// The directive target. Typically an for execution directives + /// or an for type system directives. + /// The phase of execution. This value will be passed to the middleware item or resolver. + /// The origin location within a query document. This value will be passed to the middleware item + /// or resolver. + /// The set of input argument values accepted by the directive. These arguments are expected + /// to be supplied in order of definition within the schema and castable to the appropriate data types. This builder + /// will NOT attempt any validation or mangling on these values. + /// DirectiveContextBuilder. + public virtual DirectiveContextBuilder CreateDirectiveContextBuilder( + Type directiveType, + DirectiveLocation directiveLocation, + object directiveTarget = null, + DirectiveInvocationPhase phase = DirectiveInvocationPhase.QueryDocumentExecution, + SourceOrigin origin = default, + object[] arguments = null) + { + var directiveInstance = this.Schema.KnownTypes.FindGraphType(directiveType, TypeKind.DIRECTIVE) as IDirective; - var queryRequest = Substitute.For(); - var directiveRequest = Substitute.For(); - var invocationContext = Substitute.For(); - var argCollection = InputArgumentCollectionFactory.Create(); + IGraphFieldResolverMetaData metadata = null; + directiveInstance?.Resolver.MetaData.TryGetValue(directiveLocation, out metadata); - directiveRequest.DirectivePhase.Returns(phase); - directiveRequest.InvocationContext.Returns(invocationContext); - directiveRequest.DirectiveTarget.Returns(directiveTarget); + var builder = new DirectiveContextBuilder( + this.ServiceProvider, + this.SecurityContext, + this.Schema, + directiveInstance, + directiveLocation, + phase, + metadata); - invocationContext.Location.Returns(location); - invocationContext.Arguments.Returns(argCollection); - invocationContext.Origin.Returns(origin); - invocationContext.Directive.Returns(targetDirective); + if (directiveTarget != null) + builder.AddTarget(directiveTarget); - if (targetDirective != null && targetDirective.Kind == TypeKind.DIRECTIVE - && arguments != null) + if (arguments != null && directiveInstance != null) { - for (var i = 0; i < targetDirective.Arguments.Count; i++) + for (var i = 0; i < arguments.Length; i++) { - if (arguments.Length <= i) - break; - - var directiveArg = targetDirective.Arguments[i]; - var resolvedValue = arguments[i]; - - var argValue = new ResolvedInputArgumentValue(directiveArg.Name, resolvedValue); - var inputArg = new InputArgument(directiveArg, argValue); - argCollection.Add(inputArg); + if (directiveInstance.Arguments.Count > i) + { + var argByPosition = directiveInstance.Arguments[i]; + builder.AddInputArgument(argByPosition.Name, arguments[i]); + } } } - var context = new GraphDirectiveExecutionContext( - server.Schema, - directiveRequest, - queryRequest, - server.ServiceProvider, - new QuerySession()); + return builder; + } + + /// + /// Creates a reference to the invokable method or property that represents the + /// root resolver for the given . (i.e. the method + /// or property of the object that can produce the core data value). + /// + /// The type of the object to create a reference from. + /// Name of the field. + /// IGraphMethod. + public virtual IGraphFieldResolverMetaData CreateResolverMetadata(string fieldName) + { + return CreateResolverMetadata(typeof(TObjectType), fieldName); + } - return context; + /// + /// Creates a reference to the invokable method or property that acts as a resolver for the given . + /// The must represent a controller or OBJECT graph type. + /// + /// Type of the entity that owns the field. + /// Name of the field or method/property name to search for. + /// IGraphMethod. + public virtual IGraphFieldResolverMetaData CreateResolverMetadata(Type entityType, string fieldOrActionName) + { + var builder = CreateFieldContextBuilder(entityType, fieldOrActionName); + return builder?.ResolverMetaData; } /// @@ -511,6 +540,18 @@ public virtual async Task ExecuteField(GraphFieldExecutionContext context, Cance await pipeline.InvokeAsync(context, cancelToken).ConfigureAwait(false); } + /// + /// Executes the field authorization pipeline against the provided context. + /// + /// The context. + /// The cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// Task. + public virtual async Task ExecuteFieldAuthorization(SchemaItemSecurityChallengeContext context, CancellationToken cancelToken = default) + { + var pipeline = this.ServiceProvider.GetService>(); + await pipeline.InvokeAsync(context, cancelToken).ConfigureAwait(false); + } + /// /// Renders the provided operation request through the engine and generates a JSON string output. /// @@ -561,15 +602,12 @@ protected virtual async Task RenderResult(IQueryExecutionResult result) } /// - /// Executes the field authorization pipeline against the provided context. + /// Creates a factory object that can generate templates and graph types. /// - /// The context. - /// The cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// Task. - public virtual async Task ExecuteFieldAuthorization(SchemaItemSecurityChallengeContext context, CancellationToken cancelToken = default) + /// DefaultGraphQLTypeMakerFactory. + public GraphTypeMakerFactory CreateMakerFactory() { - var pipeline = this.ServiceProvider.GetService>(); - await pipeline.InvokeAsync(context, cancelToken).ConfigureAwait(false); + return new GraphTypeMakerFactory(this.Schema); } /// diff --git a/src/unit-tests/graphql-aspnet-testframework/TestServerBuilder{TSchema}.cs b/src/unit-tests/graphql-aspnet-testframework/TestServerBuilder{TSchema}.cs index f56925d74..b29fcae5d 100644 --- a/src/unit-tests/graphql-aspnet-testframework/TestServerBuilder{TSchema}.cs +++ b/src/unit-tests/graphql-aspnet-testframework/TestServerBuilder{TSchema}.cs @@ -127,6 +127,18 @@ public ITestServerBuilder AddTestComponent(IGraphQLTestFrameworkCompone return this; } + /// + public virtual ITestServerBuilder AddGraphType(TypeKind? typeKind = null) + { + return this.AddType(typeof(TType), typeKind, null); + } + + /// + public virtual ITestServerBuilder AddGraphType(Type type, TypeKind? typeKind = null) + { + return this.AddType(type, typeKind, null); + } + /// public virtual ITestServerBuilder AddType(TypeKind? typeKind = null) { @@ -215,8 +227,15 @@ public virtual TestServer Build() // perform a schema injection to setup all the registered // graph types for the schema in the DI container - var injector = GraphQLSchemaInjectorFactory.Create(this.SchemaOptions, masterConfigMethod); - injector.ConfigureServices(); + var wasFound = GraphQLSchemaInjectorFactory.TryGetOrCreate( + out var injector, + this.SchemaOptions, + masterConfigMethod); + + if (!wasFound) + { + injector.ConfigureServices(); + } // allow the typed test components to do their thing with the // schema builder diff --git a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/CommonAssertions.cs b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/CommonAssertions.cs index 592e33cee..703a05d39 100644 --- a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/CommonAssertions.cs +++ b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/CommonAssertions.cs @@ -7,12 +7,11 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Framework.CommonHelpers +namespace GraphQL.AspNet.Tests.Common.CommonHelpers { using System.Collections.Generic; using System.Text.Json; - using GraphQL.AspNet.Tests.Framework.CommonHelpers.JsonComparing; - using NUnit; + using GraphQL.AspNet.Tests.Common.CommonHelpers.JsonComparing; using NUnit.Framework; /// diff --git a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/InheritedTwoPropertyObject.cs b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/InheritedTwoPropertyObject.cs index cf57a4ae5..0cf503309 100644 --- a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/InheritedTwoPropertyObject.cs +++ b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/InheritedTwoPropertyObject.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Framework.CommonHelpers +namespace GraphQL.AspNet.Tests.Common.CommonHelpers { using System.Diagnostics; using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/JsonComparing/JsonComparer.cs b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/JsonComparing/JsonComparer.cs index 83dcd7d56..6f236d00d 100644 --- a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/JsonComparing/JsonComparer.cs +++ b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/JsonComparing/JsonComparer.cs @@ -7,9 +7,8 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Framework.CommonHelpers.JsonComparing +namespace GraphQL.AspNet.Tests.Common.CommonHelpers.JsonComparing { - using System; using System.Text.Json; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/JsonComparing/JsonComparrisonResult.cs b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/JsonComparing/JsonComparrisonResult.cs index df17d79c6..692f7eeb0 100644 --- a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/JsonComparing/JsonComparrisonResult.cs +++ b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/JsonComparing/JsonComparrisonResult.cs @@ -7,10 +7,8 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Framework.CommonHelpers.JsonComparing +namespace GraphQL.AspNet.Tests.Common.CommonHelpers.JsonComparing { - using System; - /// /// A result containing the details of a json string comparrison. /// diff --git a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyGenericObject.cs b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyGenericObject.cs index ca93e24d9..ab1222590 100644 --- a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyGenericObject.cs +++ b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyGenericObject.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Framework.CommonHelpers +namespace GraphQL.AspNet.Tests.Common.CommonHelpers { /// /// A test object that has two properties of any type, both generic. diff --git a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyObject.cs b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyObject.cs index 28ab6c0ba..1ce05d9c7 100644 --- a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyObject.cs +++ b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyObject.cs @@ -7,11 +7,11 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Framework.CommonHelpers +namespace GraphQL.AspNet.Tests.Common.CommonHelpers { using System.Diagnostics; using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Framework.Interfaces; + using GraphQL.AspNet.Tests.Common.Interfaces; /// /// A represenstion of some data object with two properties of different value types. @@ -58,7 +58,7 @@ public TwoPropertyObject(string prop1, int prop2) /// public override string ToString() { - return $"{Property1}|{Property2}"; + return $"{this.Property1}|{this.Property2}"; } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyObjectV2.cs b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyObjectV2.cs index 3f235fd6f..6a80ca270 100644 --- a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyObjectV2.cs +++ b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyObjectV2.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Framework.CommonHelpers +namespace GraphQL.AspNet.Tests.Common.CommonHelpers { using System; using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyObjectV3.cs b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyObjectV3.cs index bbda54d51..ad7e645c1 100644 --- a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyObjectV3.cs +++ b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyObjectV3.cs @@ -6,11 +6,11 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Framework.CommonHelpers +namespace GraphQL.AspNet.Tests.Common.CommonHelpers { using System; using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Framework.Interfaces; + using GraphQL.AspNet.Tests.Common.Interfaces; /// /// A representation of a data object with two properties. diff --git a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyStruct.cs b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyStruct.cs index 58020806c..3cfe4bf54 100644 --- a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyStruct.cs +++ b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/TwoPropertyStruct.cs @@ -7,11 +7,11 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Framework.CommonHelpers +namespace GraphQL.AspNet.Tests.Common.CommonHelpers { using System.Diagnostics; using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Framework.Interfaces; + using GraphQL.AspNet.Tests.Common.Interfaces; /// /// A represenstion of some data struct with two properties, both declared as graph exposed items. diff --git a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/Utf8JsonReaderExtensions.cs b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/Utf8JsonReaderExtensions.cs index 89c25a01a..dbfcd8740 100644 --- a/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/Utf8JsonReaderExtensions.cs +++ b/src/unit-tests/graphql-aspnet-tests-common/CommonHelpers/Utf8JsonReaderExtensions.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Framework.CommonHelpers +namespace GraphQL.AspNet.Tests.Common.CommonHelpers { using System; using System.Text; diff --git a/src/unit-tests/graphql-aspnet-tests-common/Interfaces/ISinglePropertyObject.cs b/src/unit-tests/graphql-aspnet-tests-common/Interfaces/ISinglePropertyObject.cs index 26ec8cd77..b567644cb 100644 --- a/src/unit-tests/graphql-aspnet-tests-common/Interfaces/ISinglePropertyObject.cs +++ b/src/unit-tests/graphql-aspnet-tests-common/Interfaces/ISinglePropertyObject.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Framework.Interfaces +namespace GraphQL.AspNet.Tests.Common.Interfaces { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Attributes/ApplyDirectiveAttributeExtensionTests.cs b/src/unit-tests/graphql-aspnet-tests/Attributes/ApplyDirectiveAttributeExtensionTests.cs index 3ba0d61c7..68aa8b92e 100644 --- a/src/unit-tests/graphql-aspnet-tests/Attributes/ApplyDirectiveAttributeExtensionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Attributes/ApplyDirectiveAttributeExtensionTests.cs @@ -9,9 +9,9 @@ namespace GraphQL.AspNet.Tests.Attributes { using System.Linq; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Tests.Attributes.ApplyDirectiveAttributeTestData; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Attributes/ApplyDirectiveAttributeTestData/ApplyDirectiveTestObject.cs b/src/unit-tests/graphql-aspnet-tests/Attributes/ApplyDirectiveAttributeTestData/ApplyDirectiveTestObject.cs index 2be6d630c..82a694de6 100644 --- a/src/unit-tests/graphql-aspnet-tests/Attributes/ApplyDirectiveAttributeTestData/ApplyDirectiveTestObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Attributes/ApplyDirectiveAttributeTestData/ApplyDirectiveTestObject.cs @@ -8,7 +8,7 @@ // ************************************************************* namespace GraphQL.AspNet.Tests.Attributes.ApplyDirectiveAttributeTestData { - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [InheritedApplyDirective(typeof(TwoPropertyObject), "arg1")] internal class ApplyDirectiveTestObject diff --git a/src/unit-tests/graphql-aspnet-tests/Attributes/AttributeDataIntegrityTests.cs b/src/unit-tests/graphql-aspnet-tests/Attributes/AttributeDataIntegrityTests.cs index da9bbb75f..2e2a275eb 100644 --- a/src/unit-tests/graphql-aspnet-tests/Attributes/AttributeDataIntegrityTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Attributes/AttributeDataIntegrityTests.cs @@ -135,6 +135,46 @@ public void QueryAttribute_UnionConstructor_PropertyCheck() Assert.AreEqual(FieldResolutionMode.PerSourceItem, attrib.ExecutionMode); } + [Test] + public void QueryAttribute_UnionConstructor_1Type_PropertyCheck() + { + var attrib = new QueryAttribute("myField", "myUnionType", typeof(AttributeDataIntegrityTests)); + Assert.AreEqual(SchemaItemCollections.Query, attrib.FieldType); + Assert.AreEqual(false, attrib.IsRootFragment); + Assert.AreEqual("myUnionType", attrib.UnionTypeName); + Assert.AreEqual("myField", attrib.Template); + Assert.AreEqual(null, attrib.TypeExpression); + Assert.AreEqual(1, attrib.Types.Count); + Assert.AreEqual(typeof(AttributeDataIntegrityTests), attrib.Types[0]); + Assert.AreEqual(FieldResolutionMode.PerSourceItem, attrib.ExecutionMode); + } + + [Test] + public void QueryAttribute_UnionConstructor_NoTypes_PropertyCheck() + { + var attrib = new QueryAttribute("myField", "myUnionType"); + Assert.AreEqual(SchemaItemCollections.Query, attrib.FieldType); + Assert.AreEqual(false, attrib.IsRootFragment); + Assert.AreEqual("myUnionType", attrib.UnionTypeName); + Assert.AreEqual("myField", attrib.Template); + Assert.AreEqual(null, attrib.TypeExpression); + Assert.AreEqual(0, attrib.Types.Count); + Assert.AreEqual(FieldResolutionMode.PerSourceItem, attrib.ExecutionMode); + } + + [Test] + public void QueryAttribute_UnionConstructor_NullTypes_PropertyCheck() + { + var attrib = new QueryAttribute("myField", "myUnionType", null, null, null); + Assert.AreEqual(SchemaItemCollections.Query, attrib.FieldType); + Assert.AreEqual(false, attrib.IsRootFragment); + Assert.AreEqual("myUnionType", attrib.UnionTypeName); + Assert.AreEqual("myField", attrib.Template); + Assert.AreEqual(null, attrib.TypeExpression); + Assert.AreEqual(0, attrib.Types.Count); + Assert.AreEqual(FieldResolutionMode.PerSourceItem, attrib.ExecutionMode); + } + [Test] public void MutationAttribute_EmptyConstructor_PropertyCheck() { @@ -220,6 +260,46 @@ public void MutationAttribute_UnionConstructor_PropertyCheck() Assert.AreEqual(FieldResolutionMode.PerSourceItem, attrib.ExecutionMode); } + [Test] + public void MutationAttribute_UnionConstructor_1Type_PropertyCheck() + { + var attrib = new MutationAttribute("myField", "myUnionType", typeof(AttributeDataIntegrityTests)); + Assert.AreEqual(SchemaItemCollections.Mutation, attrib.FieldType); + Assert.AreEqual(false, attrib.IsRootFragment); + Assert.AreEqual("myUnionType", attrib.UnionTypeName); + Assert.AreEqual("myField", attrib.Template); + Assert.AreEqual(null, attrib.TypeExpression); + Assert.AreEqual(1, attrib.Types.Count); + Assert.AreEqual(typeof(AttributeDataIntegrityTests), attrib.Types[0]); + Assert.AreEqual(FieldResolutionMode.PerSourceItem, attrib.ExecutionMode); + } + + [Test] + public void MutationAttribute_UnionConstructor_NoTypes_PropertyCheck() + { + var attrib = new MutationAttribute("myField", "myUnionType"); + Assert.AreEqual(SchemaItemCollections.Mutation, attrib.FieldType); + Assert.AreEqual(false, attrib.IsRootFragment); + Assert.AreEqual("myUnionType", attrib.UnionTypeName); + Assert.AreEqual("myField", attrib.Template); + Assert.AreEqual(null, attrib.TypeExpression); + Assert.AreEqual(0, attrib.Types.Count); + Assert.AreEqual(FieldResolutionMode.PerSourceItem, attrib.ExecutionMode); + } + + [Test] + public void MutationAttribute_UnionConstructor_NullTypes_PropertyCheck() + { + var attrib = new MutationAttribute("myField", "myUnionType", null, null, null); + Assert.AreEqual(SchemaItemCollections.Mutation, attrib.FieldType); + Assert.AreEqual(false, attrib.IsRootFragment); + Assert.AreEqual("myUnionType", attrib.UnionTypeName); + Assert.AreEqual("myField", attrib.Template); + Assert.AreEqual(null, attrib.TypeExpression); + Assert.AreEqual(0, attrib.Types.Count); + Assert.AreEqual(FieldResolutionMode.PerSourceItem, attrib.ExecutionMode); + } + [Test] public void QueryRootAttribute_EmptyConstructor_PropertyCheck() { diff --git a/src/unit-tests/graphql-aspnet-tests/Common/Extensions/AttributeExtensionTests.cs b/src/unit-tests/graphql-aspnet-tests/Common/Extensions/AttributeExtensionTests.cs new file mode 100644 index 000000000..77f708129 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Common/Extensions/AttributeExtensionTests.cs @@ -0,0 +1,39 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Common.Extensions +{ + using GraphQL.AspNet.Common.Extensions; + using Microsoft.AspNetCore.Authorization; + using NUnit.Framework; + + [TestFixture] + public class AttributeExtensionTests + { + [Test] + public void CanBeAppliedMultipleTimes_MultipleCopyAttribute_ReturnsTrue() + { + var attrib = new AuthorizeAttribute("bob"); + + var result = attrib.CanBeAppliedMultipleTimes(); + + Assert.IsTrue(result); + } + + [Test] + public void CanBeAppliedMultipleTimes_SingleCopyAttribute_ReturnsFalse() + { + var attrib = new AllowAnonymousAttribute(); + + var result = attrib.CanBeAppliedMultipleTimes(); + + Assert.IsFalse(result); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Common/Extensions/LinqExtensionTests.cs b/src/unit-tests/graphql-aspnet-tests/Common/Extensions/LinqExtensionTests.cs index adeb91996..e114d7e70 100644 --- a/src/unit-tests/graphql-aspnet-tests/Common/Extensions/LinqExtensionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Common/Extensions/LinqExtensionTests.cs @@ -13,7 +13,8 @@ namespace GraphQL.AspNet.Tests.Common.Extensions using System.Collections.Generic; using System.Linq; using GraphQL.AspNet.Common.Extensions; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Common.Interfaces; using GraphQL.AspNet.Tests.Framework.Interfaces; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Common/Extensions/TypeExtensionTests.cs b/src/unit-tests/graphql-aspnet-tests/Common/Extensions/TypeExtensionTests.cs index 5b3287e3c..0c7b971e6 100644 --- a/src/unit-tests/graphql-aspnet-tests/Common/Extensions/TypeExtensionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Common/Extensions/TypeExtensionTests.cs @@ -15,9 +15,9 @@ namespace GraphQL.AspNet.Tests.Common.Extensions using System.Threading.Tasks; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Common.Extensions.AttributeTestData; using GraphQL.AspNet.Tests.Common.Extensions.ReflectionExtensionTestData; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Tests.ThirdPartyDll; using GraphQL.AspNet.Tests.ThirdPartyDll.Model; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Common/InstanceFactoryTests.cs b/src/unit-tests/graphql-aspnet-tests/Common/InstanceFactoryTests.cs index c945ad88c..db499607e 100644 --- a/src/unit-tests/graphql-aspnet-tests/Common/InstanceFactoryTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Common/InstanceFactoryTests.cs @@ -32,6 +32,10 @@ public void DivideNumbers(int arg1, int arg2) { } + public static void DivideNumbersAsStaticMethod(int arg1, int arg2) + { + } + public static int SubtractNumbers(int arg1, int arg2) { return arg1 - arg2; @@ -139,14 +143,14 @@ public void ObjectActivator_Struct_NonParameterizedConstructorOfStructWithParame } [Test] - public void MethodInvoker_NullMethodInfo_ReturnsNull() + public void InstanceMethodInvoker_NullMethodInfo_ReturnsNull() { var invoker = InstanceFactory.CreateInstanceMethodInvoker(null); Assert.IsNull(invoker); } [Test] - public void MethodInvoker_StaticMethodInfo_ThrowsException() + public void InstanceMethodInvoker_StaticMethodInfo_ThrowsException() { InstanceFactory.Clear(); var methodInfo = typeof(InstanceFactoryTests).GetMethod(nameof(InstanceFactoryTests.SubtractNumbers)); @@ -158,7 +162,7 @@ public void MethodInvoker_StaticMethodInfo_ThrowsException() } [Test] - public void MethodInvoker_VoidReturn_ThrowsException() + public void InstanceMethodInvoker_VoidReturn_ThrowsException() { InstanceFactory.Clear(); var methodInfo = typeof(InstanceFactoryTests).GetMethod(nameof(InstanceFactoryTests.DivideNumbers)); @@ -170,7 +174,7 @@ public void MethodInvoker_VoidReturn_ThrowsException() } [Test] - public void MethodInvoker_StandardInvoke_ReturnsValue() + public void InstanceMethodInvoker_StandardInvoke_ReturnsValue() { InstanceFactory.Clear(); var methodInfo = typeof(InstanceFactoryTests).GetMethod(nameof(InstanceFactoryTests.AddNumbers)); @@ -183,12 +187,12 @@ public void MethodInvoker_StandardInvoke_ReturnsValue() Assert.AreEqual(8, result); // ensure it was cached. - Assert.AreEqual(1, InstanceFactory.MethodInvokers.Count); + Assert.AreEqual(1, InstanceFactory.InstanceMethodInvokers.Count); InstanceFactory.Clear(); } [Test] - public void MethodInvoker_StandardInvoke_CachesAndReturnsValue() + public void InstanceMethodInvoker_StandardInvoke_CachesAndReturnsValue() { InstanceFactory.Clear(); var methodInfo = typeof(InstanceFactoryTests).GetMethod(nameof(InstanceFactoryTests.AddNumbers)); @@ -208,12 +212,12 @@ public void MethodInvoker_StandardInvoke_CachesAndReturnsValue() // ensure it was cached. Assert.AreSame(invoker, secondInvoker); - Assert.AreEqual(1, InstanceFactory.MethodInvokers.Count); + Assert.AreEqual(1, InstanceFactory.InstanceMethodInvokers.Count); InstanceFactory.Clear(); } [Test] - public void MethodInvoker_Struct_StandardInvoke_ReturnsValue_StructIsModified() + public void InstanceMethodInvoker_Struct_StandardInvoke_ReturnsValue_StructIsModified() { InstanceFactory.Clear(); var methodInfo = typeof(StructWithMethod).GetMethod(nameof(StructWithMethod.AddAndSet)); @@ -230,7 +234,7 @@ public void MethodInvoker_Struct_StandardInvoke_ReturnsValue_StructIsModified() Assert.AreEqual("propValue1", resultCast.Property1); // ensure it was cached. - Assert.AreEqual(1, InstanceFactory.MethodInvokers.Count); + Assert.AreEqual(1, InstanceFactory.InstanceMethodInvokers.Count); InstanceFactory.Clear(); } @@ -316,5 +320,62 @@ public void PropertyGetterInvoker_StandardInvoke_ReturnsValue() InstanceFactory.Clear(); } + + [Test] + public void StaticMethodInvoker_NullMethodInfo_ReturnsNull() + { + var invoker = InstanceFactory.CreateStaticMethodInvoker(null); + Assert.IsNull(invoker); + } + + [Test] + public void StaticMethodInvoker_VoidReturn_ThrowsException() + { + InstanceFactory.Clear(); + var methodInfo = typeof(InstanceFactoryTests).GetMethod(nameof(InstanceFactoryTests.DivideNumbersAsStaticMethod)); + + Assert.Throws(() => + { + var invoker = InstanceFactory.CreateStaticMethodInvoker(methodInfo); + }); + } + + [Test] + public void StaticMethodInvoker_StandardInvoke_ReturnsValue() + { + InstanceFactory.Clear(); + var methodInfo = typeof(InstanceFactoryTests).GetMethod(nameof(InstanceFactoryTests.SubtractNumbers)); + + var invoker = InstanceFactory.CreateStaticMethodInvoker(methodInfo); + + var result = invoker(5, 3); + Assert.AreEqual(2, result); + + // ensure it was cached. + Assert.AreEqual(1, InstanceFactory.StaticMethodInvokers.Count); + InstanceFactory.Clear(); + } + + [Test] + public void StaticMethodInvoker_StandardInvoke_CachesAndReturnsValue() + { + InstanceFactory.Clear(); + var methodInfo = typeof(InstanceFactoryTests).GetMethod(nameof(InstanceFactoryTests.SubtractNumbers)); + + var invoker = InstanceFactory.CreateStaticMethodInvoker(methodInfo); + + var result = invoker(5, 3); + Assert.AreEqual(2, result); + + var secondInvoker = InstanceFactory.CreateStaticMethodInvoker(methodInfo); + + result = secondInvoker(9, 1); + Assert.AreEqual(8, result); + + // ensure it was cached. + Assert.AreSame(invoker, secondInvoker); + Assert.AreEqual(1, InstanceFactory.StaticMethodInvokers.Count); + InstanceFactory.Clear(); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Common/JsonNodeTests.cs b/src/unit-tests/graphql-aspnet-tests/Common/JsonNodeTests.cs index 914ab0fb8..be5d1b598 100644 --- a/src/unit-tests/graphql-aspnet-tests/Common/JsonNodeTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Common/JsonNodeTests.cs @@ -12,7 +12,8 @@ namespace GraphQL.AspNet.Tests.Common using System.Collections.Generic; using System.Text.Json.Nodes; using GraphQL.AspNet.Common.Extensions; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Common.JsonNodes; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/CommonHelpers/CompletePropertyObject.cs b/src/unit-tests/graphql-aspnet-tests/CommonHelpers/CompletePropertyObject.cs index 91bbe6d03..31cca6e55 100644 --- a/src/unit-tests/graphql-aspnet-tests/CommonHelpers/CompletePropertyObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/CommonHelpers/CompletePropertyObject.cs @@ -10,7 +10,7 @@ // ReSharper disable All namespace GraphQL.AspNet.Tests.CommonHelpers { - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; /// /// An object representing a complex data object with properties that are a mix of scalars and objects. diff --git a/src/unit-tests/graphql-aspnet-tests/CommonHelpers/JsonComparingTests.cs b/src/unit-tests/graphql-aspnet-tests/CommonHelpers/JsonComparingTests.cs index 596bb4250..86e2a8f97 100644 --- a/src/unit-tests/graphql-aspnet-tests/CommonHelpers/JsonComparingTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/CommonHelpers/JsonComparingTests.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Tests.CommonHelpers { using System.Text.Json; - using GraphQL.AspNet.Tests.Framework.CommonHelpers.JsonComparing; + using GraphQL.AspNet.Tests.Common.CommonHelpers.JsonComparing; using NUnit.Framework; /// diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/ConfigurationFieldNamingTests.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/ConfigurationFieldNamingTests.cs index e55e0da7c..94cf3c871 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/ConfigurationFieldNamingTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/ConfigurationFieldNamingTests.cs @@ -11,9 +11,9 @@ namespace GraphQL.AspNet.Tests.Configuration { using System.Threading.Tasks; using GraphQL.AspNet.Configuration.Formatting; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Configuration.ConfigurationTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/ConfigurationSetupTests.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/ConfigurationSetupTests.cs index 5ae86eeea..ec820c131 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/ConfigurationSetupTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/ConfigurationSetupTests.cs @@ -9,6 +9,7 @@ namespace GraphQL.AspNet.Tests.Configuration { + using System; using System.Linq; using System.Reflection; using System.Threading.Tasks; @@ -16,9 +17,7 @@ namespace GraphQL.AspNet.Tests.Configuration using GraphQL.AspNet.Engine; using GraphQL.AspNet.Execution.Contexts; using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Execution.Parsing; using GraphQL.AspNet.Interfaces.Engine; - using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Middleware; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Interfaces.Web; @@ -34,20 +33,6 @@ namespace GraphQL.AspNet.Tests.Configuration [TestFixture] public class ConfigurationSetupTests { - [SetUp] - public void Setup() - { - GraphQLProviders.TemplateProvider.CacheTemplates = true; - } - - [TearDown] - public void TearDown() - { - GraphQLSchemaBuilderExtensions.Clear(); - GraphQLProviders.TemplateProvider.Clear(); - GraphQLProviders.TemplateProvider.CacheTemplates = false; - } - [Test] public void AddGraphQL_AddingDefaultSchema_WithOneController_GeneratesAllDefaultEngineParts() { @@ -71,8 +56,6 @@ public void AddGraphQL_AddingDefaultSchema_WithOneController_GeneratesAllDefault Assert.IsNotNull(sp.GetService(typeof(IQueryExecutionPlanGenerator)) as DefaultQueryExecutionPlanGenerator); Assert.IsNotNull(sp.GetService(typeof(IQueryResponseWriter)) as DefaultQueryResponseWriter); Assert.IsNotNull(sp.GetService(typeof(IQueryExecutionMetricsFactory)) as DefaultQueryExecutionMetricsFactory); - - GraphQLProviders.TemplateProvider.Clear(); } [Test] @@ -81,29 +64,10 @@ public void AddGraphQL_AttemptingToAddDefautSchemaAsTyped_ThrowsException() var serviceCollection = new ServiceCollection(); serviceCollection.AddGraphQL(); - Assert.Throws(() => + Assert.Throws(() => { serviceCollection.AddGraphQL(); }); - - GraphQLProviders.TemplateProvider.Clear(); - } - - [Test] - public void UseGraphQL_PerformsPreparseAndSetupRoutines() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddGraphQL(options => - { - options.AddType(); - }); - - var sp = serviceCollection.BuildServiceProvider(); - sp.UseGraphQL(); - - // FanController, FanItem, FanSpeed, - // skipDirective, includeDirective, deprecatedDirective, specifiedByDirective - Assert.AreEqual(7, GraphQLProviders.TemplateProvider.Count); } [Test] @@ -118,11 +82,6 @@ public void SchemaBuilder_AddAssembly_AddGraphAssembly() var provider = serviceCollection.BuildServiceProvider(); provider.UseGraphQL(); - // virtualResolvedObject, candleController, candle, waxType, - // customerController, customer, - // skipDirective, includeDirective, deprecatedDirective, specifiedByDirective - Assert.AreEqual(10, GraphQLProviders.TemplateProvider.Count); - var sp = serviceCollection.BuildServiceProvider(); sp.UseGraphQL(); @@ -152,11 +111,6 @@ public void SchemaBuilder_AddSchemaAssembly_AllControllersAddedToType() var provider = serviceCollection.BuildServiceProvider(); provider.UseGraphQL(); - // virtualResolvedObject, candleController, candle, waxType, - // customerController, customer, - // skipDirective, includeDirective, deprecatedDirective, specifiedByDirective - Assert.AreEqual(10, GraphQLProviders.TemplateProvider.Count); - var sp = serviceCollection.BuildServiceProvider(); var schema = sp.GetService(typeof(CandleSchema)) as ISchema; Assert.IsNotNull(schema); @@ -184,11 +138,6 @@ public void SchemaBuilder_AddGraphController_AppearsInSchema() var provider = serviceCollection.BuildServiceProvider(); provider.UseGraphQL(); - // virtualResolvedObject, candleController, candle, - // waxType, - // skipDirective, includeDirective, deprecatedDirective, specifiedByDirective - Assert.AreEqual(8, GraphQLProviders.TemplateProvider.Count); - var sp = serviceCollection.BuildServiceProvider(); var schema = sp.GetService(typeof(GraphSchema)) as ISchema; Assert.IsNotNull(schema); @@ -219,7 +168,6 @@ public void SchemaBuilder_AddGraphDirective_AppearsInSchema() // sample1Directive, // skipDirective, includeDirective, deprecatedDirective, specifiedByDirective - Assert.AreEqual(5, GraphQLProviders.TemplateProvider.Count); var schema = provider.GetService(typeof(GraphSchema)) as GraphSchema; Assert.IsNotNull(schema); @@ -302,8 +250,6 @@ public async Task SchemaBuilder_AddMiddleware_AsTypeRegistration_AppearsInPipeli [Test] public void ChangingGlobalConfig_ChangesHowControllersAreRegistered() { - using var restorePoint = new GraphQLGlobalRestorePoint(); - // make sure the original setting is not what we hope to change it to // otherwise the test is inconclusive if (GraphQLServerSettings.ControllerServiceLifeTime == ServiceLifetime.Singleton) diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/DirectiveApplicatorTests.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/DirectiveApplicatorTests.cs index 0fa15f78d..58d1d9c75 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/DirectiveApplicatorTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/DirectiveApplicatorTests.cs @@ -14,8 +14,8 @@ namespace GraphQL.AspNet.Tests.Configuration using GraphQL.AspNet.Interfaces.Configuration; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] @@ -42,12 +42,12 @@ object[] CreateArgs(ISchemaItem item) .Build() .Schema; - var applicator = new DirectiveBindingConfiguration("testDirective"); + var applicator = new DirectiveBindingSchemaExtension("testDirective"); applicator .WithArguments(CreateArgs) .ToItems(x => x is IGraphField); - ((ISchemaConfigurationExtension)applicator).Configure(schema); + ((IGraphQLServerExtension)applicator).EnsureSchema(schema); for (var i = 0; i < matchedSchemaItems.Count; i++) { @@ -79,14 +79,14 @@ object[] CreateArgsOther(ISchemaItem item) .Build() .Schema; - var applicator = new DirectiveBindingConfiguration("testDirective"); + var applicator = new DirectiveBindingSchemaExtension("testDirective"); applicator .WithArguments(CreateArgs) .WithArguments(new object[0]) .WithArguments(CreateArgsOther) .ToItems(x => x is IGraphField); - ((ISchemaConfigurationExtension)applicator).Configure(schema); + ((IGraphQLServerExtension)applicator).EnsureSchema(schema); // count would be greater than zero fi and only if the last // supplied function was executed and any fields were found @@ -103,12 +103,12 @@ public void ConstantSuppliedArgumentsAreUsed_ForAllMatchedItems() .Build() .Schema; - var applicator = new DirectiveBindingConfiguration("testDirective"); + var applicator = new DirectiveBindingSchemaExtension("testDirective"); applicator .WithArguments(argSet) .ToItems(x => x is IGraphField); - ((ISchemaConfigurationExtension)applicator).Configure(schema); + ((IGraphQLServerExtension)applicator).EnsureSchema(schema); foreach (var item in schema.AllSchemaItems()) { diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/GraphSchemaBuilderTests.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/GraphSchemaBuilderTests.cs new file mode 100644 index 000000000..7604caa43 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/GraphSchemaBuilderTests.cs @@ -0,0 +1,79 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Configuration +{ + using GraphQL.AspNet.Configuration.Startup; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Tests.Configuration.SchemaBuildTestData; + using Microsoft.Extensions.DependencyInjection; + using NUnit.Framework; + + [TestFixture] + public class GraphSchemaBuilderTests + { + [Test] + public void SimpleSchema_IsRenderedOut() + { + var collection = new ServiceCollection(); + collection.AddSingleton(); + + var provider = collection.BuildServiceProvider(); + var scope = provider.CreateScope(); + + var schema = GraphSchemaBuilder.BuildSchema(scope.ServiceProvider); + + Assert.IsNotNull(schema); + } + + [Test] + public void SchemaWithOneAvailableService_IsRenderedOut() + { + var collection = new ServiceCollection(); + collection.AddSingleton(); + collection.AddSingleton(); + + var provider = collection.BuildServiceProvider(); + var scope = provider.CreateScope(); + + var schema = GraphSchemaBuilder.BuildSchema(scope.ServiceProvider); + Assert.IsNotNull(schema); + } + + [Test] + public void MultipleConstructors_ButOnlyOneMatches_CorrectConstructorIsUsed() + { + var collection = new ServiceCollection(); + collection.AddSingleton(); + collection.AddSingleton(); + + var provider = collection.BuildServiceProvider(); + var scope = provider.CreateScope(); + + var schema = GraphSchemaBuilder.BuildSchema(scope.ServiceProvider); + Assert.IsNotNull(schema); + Assert.AreEqual(1, schema.PropValue); + } + + [Test] + public void MultipleConstructors_ButOnlyOneMatches_ButHasDefaultValues_CorrectConstructorIsUsed() + { + var collection = new ServiceCollection(); + collection.AddSingleton(); + collection.AddSingleton(); + + var provider = collection.BuildServiceProvider(); + var scope = provider.CreateScope(); + + var schema = GraphSchemaBuilder.BuildSchema(scope.ServiceProvider); + Assert.IsNotNull(schema); + Assert.AreEqual(3, schema.PropValue); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/MultiConstructorSchemWithDefaultValues.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/MultiConstructorSchemWithDefaultValues.cs new file mode 100644 index 000000000..c920f5db6 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/MultiConstructorSchemWithDefaultValues.cs @@ -0,0 +1,34 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Configuration.SchemaBuildTestData +{ + using System; + using GraphQL.AspNet.Schemas; + + public class MultiConstructorSchemWithDefaultValues : GraphSchema + { + public MultiConstructorSchemWithDefaultValues(TestService1 service) + { + this.PropValue = 1; + } + + public MultiConstructorSchemWithDefaultValues(TestService1 service, TestService2 service2) + { + this.PropValue = 2; + } + + public MultiConstructorSchemWithDefaultValues(TestService1 service = null, TestService2 service2 = null, TestService3 service3 = null) + { + this.PropValue = 3; + } + + public int PropValue { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/MultiConstructorSchema.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/MultiConstructorSchema.cs new file mode 100644 index 000000000..156365f6c --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/MultiConstructorSchema.cs @@ -0,0 +1,40 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Configuration.SchemaBuildTestData +{ + using System; + using GraphQL.AspNet.Schemas; + + public class MultiConstructorSchema : GraphSchema + { + public MultiConstructorSchema(TestService1 service) + { + ArgumentNullException.ThrowIfNull(service, nameof(service)); + this.PropValue = 1; + } + + public MultiConstructorSchema(TestService1 service, TestService2 service2) + { + ArgumentNullException.ThrowIfNull(service, nameof(service)); + ArgumentNullException.ThrowIfNull(service2, nameof(service2)); + this.PropValue = 2; + } + + public MultiConstructorSchema(TestService1 service, TestService2 service2, TestService3 service3) + { + ArgumentNullException.ThrowIfNull(service, nameof(service)); + ArgumentNullException.ThrowIfNull(service2, nameof(service2)); + ArgumentNullException.ThrowIfNull(service3, nameof(service3)); + this.PropValue = 3; + } + + public int PropValue { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/OneServiceSchema.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/OneServiceSchema.cs new file mode 100644 index 000000000..d15c71781 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/OneServiceSchema.cs @@ -0,0 +1,22 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Configuration.SchemaBuildTestData +{ + using System; + using GraphQL.AspNet.Schemas; + + public class OneServiceSchema : GraphSchema + { + public OneServiceSchema(TestService1 service) + { + ArgumentNullException.ThrowIfNull(service, nameof(service)); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/TestService1.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/TestService1.cs new file mode 100644 index 000000000..aaa43d3b7 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/TestService1.cs @@ -0,0 +1,16 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Configuration.SchemaBuildTestData +{ + public class TestService1 + { + public int Prop1 { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/TestService2.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/TestService2.cs new file mode 100644 index 000000000..6bad91de6 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/TestService2.cs @@ -0,0 +1,16 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Configuration.SchemaBuildTestData +{ + public class TestService2 + { + public int Prop1 { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/TestService3.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/TestService3.cs new file mode 100644 index 000000000..84cb27b08 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaBuildTestData/TestService3.cs @@ -0,0 +1,16 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Configuration.SchemaBuildTestData +{ + public class TestService3 + { + public int Prop1 { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam1.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam1.cs index 1fe886a10..78250dc8f 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam1.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam1.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Tests.Configuration.SchemaInjectorTestData { using System; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ObjectWithInvalidMethodParam1 { diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam2.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam2.cs index 85bc36037..fec502462 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam2.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam2.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Tests.Configuration.SchemaInjectorTestData { using System; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ObjectWithInvalidMethodParam2 { diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam3.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam3.cs index 5f8d7a778..12d07564c 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam3.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam3.cs @@ -10,7 +10,6 @@ namespace GraphQL.AspNet.Tests.Configuration.SchemaInjectorTestData { using System; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; public class ObjectWithInvalidMethodParam3 { diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam4.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam4.cs index f41f18b30..55d118c50 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam4.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam4.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Tests.Configuration.SchemaInjectorTestData { using System; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ObjectWithInvalidMethodParam4 { diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam5.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam5.cs index 2939c7e2f..f1fbadf0d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam5.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam5.cs @@ -9,9 +9,6 @@ namespace GraphQL.AspNet.Tests.Configuration.SchemaInjectorTestData { - using System; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - public delegate void MyDelegate(int param1); public class ObjectWithInvalidMethodParam5 diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam6.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam6.cs index 584c0c2b0..aa24a6367 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam6.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidMethodParam6.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Tests.Configuration.SchemaInjectorTestData { using System; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.Interfaces; using GraphQL.AspNet.Tests.Framework.Interfaces; public class ObjectWithInvalidMethodParam6 diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidPropertyType.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidPropertyType.cs index 9f4469e56..7b92a1f3d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidPropertyType.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaInjectorTestData/ObjectWithInvalidPropertyType.cs @@ -10,8 +10,6 @@ namespace GraphQL.AspNet.Tests.Configuration.SchemaInjectorTestData { using System; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - using GraphQL.AspNet.Tests.Framework.Interfaces; public class ObjectWithInvalidPropertyType { diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaItemFilterExtensionTests.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaItemFilterExtensionTests.cs index 586f02cd0..edf380399 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaItemFilterExtensionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaItemFilterExtensionTests.cs @@ -14,9 +14,9 @@ namespace GraphQL.AspNet.Tests.Configuration using GraphQL.AspNet.Directives.Global; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Configuration.SchemaItemExtensionTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NSubstitute; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaOptionsAddTypesTests.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaOptionsAddTypesTests.cs index 035567b20..03eafd86a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaOptionsAddTypesTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaOptionsAddTypesTests.cs @@ -13,9 +13,9 @@ namespace GraphQL.AspNet.Tests.Configuration using GraphQL.AspNet.Configuration.Exceptions; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Configuration.ConfigurationTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaOptionsApplyDirectiveTests.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaOptionsApplyDirectiveTests.cs index ac5655313..3d0a2cd7a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaOptionsApplyDirectiveTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaOptionsApplyDirectiveTests.cs @@ -14,9 +14,9 @@ namespace GraphQL.AspNet.Tests.Configuration using GraphQL.AspNet.Configuration.Exceptions; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Configuration.SchemaOptionsTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaOptionsCustomLifetimeRegistrationTests.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaOptionsCustomLifetimeRegistrationTests.cs index a32549372..f6c76c8ae 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaOptionsCustomLifetimeRegistrationTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaOptionsCustomLifetimeRegistrationTests.cs @@ -22,8 +22,6 @@ public class SchemaOptionsCustomLifetimeRegistrationTests [Test] public void DirectiveAddedWithNonDefaultLifeTime_IsRegisteredAsSuch() { - using var restorePoint = new GraphQLGlobalRestorePoint(); - GraphQLServerSettings.ControllerServiceLifeTime = ServiceLifetime.Transient; var collection = new ServiceCollection(); @@ -41,8 +39,6 @@ public void DirectiveAddedWithNonDefaultLifeTime_IsRegisteredAsSuch() [Test] public void ControllerAddedWithNonDefaultLifeTime_IsRegisteredAsSuch() { - using var restorePoint = new GraphQLGlobalRestorePoint(); - GraphQLServerSettings.ControllerServiceLifeTime = ServiceLifetime.Transient; var collection = new ServiceCollection(); diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaTypeToRegisterTests.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaTypeToRegisterTests.cs index 447b53d1b..c6df39f55 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaTypeToRegisterTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaTypeToRegisterTests.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Configuration using System; using System.Collections.Generic; using GraphQL.AspNet.Configuration; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/ServiceToRegisterTests.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/ServiceToRegisterTests.cs index e61cf8b97..7c112ec00 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/ServiceToRegisterTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/ServiceToRegisterTests.cs @@ -9,7 +9,8 @@ namespace GraphQL.AspNet.Tests.Configuration { using GraphQL.AspNet.Configuration; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Common.Interfaces; using GraphQL.AspNet.Tests.Framework.Interfaces; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/StartupTests.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/StartupTests.cs new file mode 100644 index 000000000..3d9764f6a --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/StartupTests.cs @@ -0,0 +1,60 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Configuration +{ + using System; + using System.Linq; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Schemas; + using Microsoft.Extensions.DependencyInjection; + using NUnit.Framework; + + [TestFixture] + public class StartupTests + { + public class Schema2 : GraphSchema + { + } + + [Test] + public void AttemptingToAddGraphQL_ForDifferentSchemas_isFIne() + { + var collection = new ServiceCollection(); + collection.AddGraphQL(); + collection.AddGraphQL(); + } + + [Test] + public void AttemptingToAddGraphQL_TwiceForSameSchema_ThrowsException() + { + var collection = new ServiceCollection(); + collection.AddGraphQL(); + + Assert.Throws(() => + { + collection.AddGraphQL(); + }); + } + + [Test] + public void AttemptingToAddGraphQL_ForDifferentSchemas_YieldsTwoInjectorsInPRovider() + { + var collection = new ServiceCollection(); + collection.AddGraphQL(); + collection.AddGraphQL(); + + var provider = collection.BuildServiceProvider(); + + var services = provider.GetServices(); + Assert.AreEqual(2, services.Count()); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Controllers/ActionResults/ActionResultTests.cs b/src/unit-tests/graphql-aspnet-tests/Controllers/ActionResults/ActionResultTests.cs index 0a67aaa1d..3463063ac 100644 --- a/src/unit-tests/graphql-aspnet-tests/Controllers/ActionResults/ActionResultTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Controllers/ActionResults/ActionResultTests.cs @@ -14,11 +14,12 @@ namespace GraphQL.AspNet.Tests.Controllers.ActionResults using System.Threading.Tasks; using GraphQL.AspNet.Controllers.ActionResults; using GraphQL.AspNet.Controllers.InputModel; - using GraphQL.AspNet.Engine.TypeMakers; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Contexts; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Generation; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; using GraphQL.AspNet.Tests.Controllers.ActionResults.ActuionResultTestData; using GraphQL.AspNet.Tests.Framework; using NSubstitute; @@ -33,7 +34,7 @@ private FieldResolutionContext CreateResolutionContext() .AddType() .Build(); - var builder = server.CreateGraphTypeFieldContextBuilder( + var builder = server.CreateFieldContextBuilder( nameof(ActionableController.DoStuff)); return builder.CreateResolutionContext(); } @@ -119,7 +120,7 @@ public async Task GraphFieldError_WithMessageAndCode_ReturnsMessageWithException [Test] public async Task InternalServerError_WithAction_AndException_FriendlyErrorMessage() { - var action = GraphQLTemplateHelper.CreateFieldTemplate(nameof(ActionableController.DoStuff)) as IGraphFieldResolverMethod; + var action = GraphQLTemplateHelper.CreateFieldTemplate(nameof(ActionableController.DoStuff)).CreateResolverMetaData(); var exception = new Exception("Fail"); var actionResult = new InternalServerErrorGraphActionResult(action, exception); @@ -155,7 +156,7 @@ public async Task ObjectRetrunedGraph_ReturnsSameItemGivenToIt() { var testObject = new object(); - var actionResult = new ObjectReturnedGraphActionResult(testObject); + var actionResult = new OperationCompleteGraphActionResult(testObject); var context = this.CreateResolutionContext(); await actionResult.CompleteAsync(context); @@ -165,12 +166,12 @@ public async Task ObjectRetrunedGraph_ReturnsSameItemGivenToIt() } [Test] - public async Task RouteNotFound_ViaGraphAction_YieldsNegativeResult() + public async Task RouteNotFound_ViaResolverMetaData_WithThrownException_YieldsNegativeResult_AndThrowsExceptionWrappedException() { - var action = GraphQLTemplateHelper.CreateFieldTemplate(nameof(ActionableController.DoStuff)) as IGraphFieldResolverMethod; + var resolverMetadata = GraphQLTemplateHelper.CreateFieldTemplate(nameof(ActionableController.DoStuff)).CreateResolverMetaData(); var exception = new Exception("fail"); - var actionResult = new RouteNotFoundGraphActionResult(action, exception); + var actionResult = new RouteNotFoundGraphActionResult(resolverMetadata, exception); var context = this.CreateResolutionContext(); await actionResult.CompleteAsync(context); @@ -178,7 +179,11 @@ public async Task RouteNotFound_ViaGraphAction_YieldsNegativeResult() Assert.IsTrue(context.IsCancelled); Assert.AreEqual(1, context.Messages.Count); Assert.AreEqual(Constants.ErrorCodes.INVALID_ROUTE, context.Messages[0].Code); - Assert.IsTrue(context.Messages[0].Message.Contains(action.Name)); + + // exception message should have the resolver name in it + Assert.IsTrue(context.Messages[0].Message.Contains(context.Route.Name)); + Assert.IsTrue(context.Messages[0].Exception.Message.Contains(resolverMetadata.InternalName)); + Assert.AreEqual(context.Messages[0].Exception.InnerException, exception); } [Test] diff --git a/src/unit-tests/graphql-aspnet-tests/Controllers/ActionResults/ActuionResultTestData/ActionableController.cs b/src/unit-tests/graphql-aspnet-tests/Controllers/ActionResults/ActuionResultTestData/ActionableController.cs index 903f31ed0..4f610fde7 100644 --- a/src/unit-tests/graphql-aspnet-tests/Controllers/ActionResults/ActuionResultTestData/ActionableController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Controllers/ActionResults/ActuionResultTestData/ActionableController.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Controllers.ActionResults.ActuionResultTestData using System.Threading.Tasks; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ActionableController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Controllers/GraphControllerTests.cs b/src/unit-tests/graphql-aspnet-tests/Controllers/GraphControllerTests.cs index 1369558fa..4615cf01a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Controllers/GraphControllerTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Controllers/GraphControllerTests.cs @@ -10,13 +10,12 @@ namespace GraphQL.AspNet.Tests.Controllers { using System.Reflection; - using System.Security.Principal; using System.Threading.Tasks; using GraphQL.AspNet.Controllers.ActionResults; using GraphQL.AspNet.Controllers.InputModel; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Controllers.ControllerTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NSubstitute; using NUnit.Framework; @@ -30,17 +29,17 @@ public async Task MethodInvocation_EnsureInternalPropertiesAreSet() .AddGraphController() .Build(); - var fieldContextBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = server.CreateFieldContextBuilder( nameof(InvokableController.AsyncActionMethod)); fieldContextBuilder.AddInputArgument("arg1", "random string"); var resolutionContext = fieldContextBuilder.CreateResolutionContext(); var controller = new InvokableController(); - var result = await controller.InvokeActionAsync(fieldContextBuilder.GraphMethod, resolutionContext); + var result = await controller.InvokeActionAsync(fieldContextBuilder.ResolverMetaData, resolutionContext); Assert.IsNotNull(result); - Assert.IsTrue(result is ObjectReturnedGraphActionResult); + Assert.IsTrue(result is OperationCompleteGraphActionResult); Assert.AreEqual(3, controller.CapturedItems.Count); @@ -59,16 +58,16 @@ public async Task MethodInvocation_SyncMethodReturnsObjectNotTask() .AddGraphController() .Build(); - var fieldContextBuilder = tester.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = tester.CreateFieldContextBuilder( nameof(InvokableController.SyncronousActionMethod)); fieldContextBuilder.AddInputArgument("arg1", "random string"); var controller = new InvokableController(); var resolutionContext = fieldContextBuilder.CreateResolutionContext(); - var result = await controller.InvokeActionAsync(fieldContextBuilder.GraphMethod, resolutionContext); + var result = await controller.InvokeActionAsync(fieldContextBuilder.ResolverMetaData, resolutionContext); Assert.IsNotNull(result); - Assert.IsTrue(result is ObjectReturnedGraphActionResult); + Assert.IsTrue(result is OperationCompleteGraphActionResult); } [Test] @@ -77,15 +76,15 @@ public async Task MethodInvocation_UnawaitableAsyncMethodFlag_ResultsInInternalE var tester = new TestServerBuilder(TestOptions.UseCodeDeclaredNames) .AddGraphController() .Build(); - var fieldContextBuilder = tester.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = tester.CreateFieldContextBuilder( nameof(InvokableController.SyncronousActionMethod)); fieldContextBuilder.AddInputArgument("arg1", "random string"); - fieldContextBuilder.GraphMethod.IsAsyncField.Returns(true); + fieldContextBuilder.ResolverMetaData.IsAsyncField.Returns(true); var controller = new InvokableController(); var resolutionContext = fieldContextBuilder.CreateResolutionContext(); - var result = await controller.InvokeActionAsync(fieldContextBuilder.GraphMethod, resolutionContext); + var result = await controller.InvokeActionAsync(fieldContextBuilder.ResolverMetaData, resolutionContext); // ensure a server error reslt is generated Assert.IsNotNull(result); @@ -98,14 +97,14 @@ public async Task MethodInvocation_MissingMethodInfo_ReturnsInternalServerError( var tester = new TestServerBuilder(TestOptions.UseCodeDeclaredNames) .AddGraphController() .Build(); - var fieldContextBuilder = tester.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = tester.CreateFieldContextBuilder( nameof(InvokableController.SyncronousActionMethod)); fieldContextBuilder.AddInputArgument("arg1", "random string"); - fieldContextBuilder.GraphMethod.Method.Returns(null as MethodInfo); + fieldContextBuilder.ResolverMetaData.Method.Returns(null as MethodInfo); var controller = new InvokableController(); var resolutionContext = fieldContextBuilder.CreateResolutionContext(); - var result = await controller.InvokeActionAsync(fieldContextBuilder.GraphMethod, resolutionContext); + var result = await controller.InvokeActionAsync(fieldContextBuilder.ResolverMetaData, resolutionContext); // ensure a server error reslt is generated Assert.IsNotNull(result); @@ -118,14 +117,14 @@ public void MethodInvocation_UserCodeExceptionIsAllowedToThrow() var tester = new TestServerBuilder(TestOptions.UseCodeDeclaredNames) .AddGraphController() .Build(); - var fieldContextBuilder = tester.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = tester.CreateFieldContextBuilder( nameof(InvokableController.AsyncActionMethodToCauseException)); fieldContextBuilder.AddInputArgument("arg1", "random string"); var controller = new InvokableController(); var resolutionContext = fieldContextBuilder.CreateResolutionContext(); - Assert.ThrowsAsync(async () => await controller.InvokeActionAsync(fieldContextBuilder.GraphMethod, resolutionContext)); + Assert.ThrowsAsync(async () => await controller.InvokeActionAsync(fieldContextBuilder.ResolverMetaData, resolutionContext)); } [Test] @@ -135,13 +134,13 @@ public async Task ErrorResult() .AddType() .Build(); - var fieldContextBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = server.CreateFieldContextBuilder( nameof(InvokableController.ErrorResult)); var controller = new InvokableController(); var resolutionContext = fieldContextBuilder.CreateResolutionContext(); var result = await controller.InvokeActionAsync( - fieldContextBuilder.GraphMethod, + fieldContextBuilder.ResolverMetaData, resolutionContext) as GraphFieldErrorActionResult; Assert.IsNotNull(result); diff --git a/src/unit-tests/graphql-aspnet-tests/Controllers/GraphModelStateDictionaryTests.cs b/src/unit-tests/graphql-aspnet-tests/Controllers/GraphModelStateDictionaryTests.cs index 79ca412bb..0e4d8ce42 100644 --- a/src/unit-tests/graphql-aspnet-tests/Controllers/GraphModelStateDictionaryTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Controllers/GraphModelStateDictionaryTests.cs @@ -35,7 +35,6 @@ private ExecutionArgument CreateArgument( argTemplate.Name.Returns(name); argTemplate.TypeExpression.Returns(new GraphTypeExpression(name, wrappers)); - argTemplate.ArgumentModifiers.Returns(GraphArgumentModifiers.None); argTemplate.ObjectType.Returns(concreteType); argTemplate.ParameterName.Returns(name); diff --git a/src/unit-tests/graphql-aspnet-tests/Controllers/GraphQueryProcessorTests.cs b/src/unit-tests/graphql-aspnet-tests/Controllers/GraphQueryProcessorTests.cs index 80a405267..f61e6427c 100644 --- a/src/unit-tests/graphql-aspnet-tests/Controllers/GraphQueryProcessorTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Controllers/GraphQueryProcessorTests.cs @@ -15,9 +15,9 @@ namespace GraphQL.AspNet.Tests.Controllers using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Web; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Controllers.GraphQueryControllerData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Web; using Microsoft.AspNetCore.Http; using Microsoft.Net.Http.Headers; diff --git a/src/unit-tests/graphql-aspnet-tests/Controllers/SchemaItemPathTests.cs b/src/unit-tests/graphql-aspnet-tests/Controllers/SchemaItemPathTests.cs index d10597929..1ec96f840 100644 --- a/src/unit-tests/graphql-aspnet-tests/Controllers/SchemaItemPathTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Controllers/SchemaItemPathTests.cs @@ -65,7 +65,7 @@ public void IsTopLevelField(string input, bool isTopField) } [Test] - public void Destructuring_Query_TwoFragmentPathHasADefinedParent() + public void Query_TwoFragmentPathHasADefinedParent() { var fragment = "[query]/path1/path2"; var route = new SchemaItemPath(fragment); @@ -202,5 +202,26 @@ public void GraphRouteArgumentPath_YieldsAlternatePathString() var route = new GraphArgumentFieldPath(parent, "arg1"); Assert.AreEqual($"{Constants.Routing.TYPE_ROOT}/typeName/fieldName[arg1]", route.Path); } + + [TestCase("[query]/path1/path2", SchemaItemCollections.Query, "/path1/path2")] + [TestCase("[query]", SchemaItemCollections.Query, "/")] + [TestCase("[mutation]/path1/path2", SchemaItemCollections.Mutation, "/path1/path2")] + [TestCase("[subscription]/path1/path2", SchemaItemCollections.Subscription, "/path1/path2")] + [TestCase("[wrong]/path1/path2", SchemaItemCollections.Unknown, "")] + [TestCase("[query]/path1", SchemaItemCollections.Query, "/path1")] + [TestCase("[mutation]/path1", SchemaItemCollections.Mutation, "/path1")] + [TestCase("[subscription]/path1", SchemaItemCollections.Subscription, "/path1")] + [TestCase("[wrong]/path1", SchemaItemCollections.Unknown, "")] + public void Destructuring_ToCollectionAndPath( + string input, + SchemaItemCollections expectedCollection, + string expectedPath) + { + var route = new SchemaItemPath(input); + var (col, path) = route; + + Assert.AreEqual(expectedCollection, col); + Assert.AreEqual(expectedPath, path); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Directives/DeprecatedDirectiveTests.cs b/src/unit-tests/graphql-aspnet-tests/Directives/DeprecatedDirectiveTests.cs index 5361e121b..f1f94e1f4 100644 --- a/src/unit-tests/graphql-aspnet-tests/Directives/DeprecatedDirectiveTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Directives/DeprecatedDirectiveTests.cs @@ -24,7 +24,6 @@ namespace GraphQL.AspNet.Tests.Directives using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using NUnit.Framework; @@ -33,6 +32,7 @@ namespace GraphQL.AspNet.Tests.Directives using GraphQL.AspNet.Interfaces.Execution.QueryPlans.InputArguments; using GraphQL.AspNet.Interfaces.Execution.Variables; using GraphQL.AspNet.Execution.QueryPlans.InputArguments; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [TestFixture] [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] diff --git a/src/unit-tests/graphql-aspnet-tests/Directives/DirectiveTestData/SimpleExecutionController.cs b/src/unit-tests/graphql-aspnet-tests/Directives/DirectiveTestData/SimpleExecutionController.cs index 97d876e14..0b3325b4e 100644 --- a/src/unit-tests/graphql-aspnet-tests/Directives/DirectiveTestData/SimpleExecutionController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Directives/DirectiveTestData/SimpleExecutionController.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Directives.DirectiveTestData using System; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphRoute("simple")] public class SimpleExecutionController : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/Directives/DocumentAlterationDirectiveTests.cs b/src/unit-tests/graphql-aspnet-tests/Directives/DocumentAlterationDirectiveTests.cs index 3f615a262..d912e0f8f 100644 --- a/src/unit-tests/graphql-aspnet-tests/Directives/DocumentAlterationDirectiveTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Directives/DocumentAlterationDirectiveTests.cs @@ -10,9 +10,9 @@ namespace GraphQL.AspNet.Tests.Directives { using System.Threading.Tasks; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Directives.DirectiveTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Directives/IncludeDirectiveTests.cs b/src/unit-tests/graphql-aspnet-tests/Directives/IncludeDirectiveTests.cs index 550009019..94fbd026b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Directives/IncludeDirectiveTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Directives/IncludeDirectiveTests.cs @@ -11,9 +11,9 @@ namespace GraphQL.AspNet.Tests.Directives { using System.Threading.Tasks; using GraphQL.AspNet.Directives.Global; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Directives.DirectiveTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Directives/IncludeSkipCombinedDirectiveTests.cs b/src/unit-tests/graphql-aspnet-tests/Directives/IncludeSkipCombinedDirectiveTests.cs index dc8b0bcfe..c036913b0 100644 --- a/src/unit-tests/graphql-aspnet-tests/Directives/IncludeSkipCombinedDirectiveTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Directives/IncludeSkipCombinedDirectiveTests.cs @@ -11,9 +11,9 @@ namespace GraphQL.AspNet.Tests.Directives { using System.Threading.Tasks; using GraphQL.AspNet.Directives.Global; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Directives.DirectiveTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Directives/RepeatableDirectiveTests.cs b/src/unit-tests/graphql-aspnet-tests/Directives/RepeatableDirectiveTests.cs index a6ed1be7e..713c8b2f3 100644 --- a/src/unit-tests/graphql-aspnet-tests/Directives/RepeatableDirectiveTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Directives/RepeatableDirectiveTests.cs @@ -10,9 +10,9 @@ namespace GraphQL.AspNet.Tests.Directives { using GraphQL.AspNet.Configuration; using GraphQL.AspNet.Configuration.Exceptions; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Directives.DirectiveTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Directives/SkipDirectiveTests.cs b/src/unit-tests/graphql-aspnet-tests/Directives/SkipDirectiveTests.cs index e35fd88d1..a843217db 100644 --- a/src/unit-tests/graphql-aspnet-tests/Directives/SkipDirectiveTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Directives/SkipDirectiveTests.cs @@ -11,9 +11,9 @@ namespace GraphQL.AspNet.Tests.Directives { using System.Threading.Tasks; using GraphQL.AspNet.Directives.Global; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Directives.DirectiveTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Directives/SpecifiedByDirectiveTests.cs b/src/unit-tests/graphql-aspnet-tests/Directives/SpecifiedByDirectiveTests.cs index 2a39ef0e4..168f2eb79 100644 --- a/src/unit-tests/graphql-aspnet-tests/Directives/SpecifiedByDirectiveTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Directives/SpecifiedByDirectiveTests.cs @@ -29,11 +29,11 @@ namespace GraphQL.AspNet.Tests.Directives using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using NUnit.Framework; using GraphQL.AspNet.Execution.RulesEngine.RuleSets.DocumentValidation.QueryInputValueSteps; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [TestFixture] [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarIncorrectAppliedDirectivesParent.cs b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarIncorrectAppliedDirectivesParent.cs index ee86ba590..ff64e9d01 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarIncorrectAppliedDirectivesParent.cs +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarIncorrectAppliedDirectivesParent.cs @@ -23,6 +23,8 @@ public class SomeItem : ISchemaItem public string Name { get; set; } public string Description { get; set; } + + public string InternalName { get; set; } } public ScalarIncorrectAppliedDirectivesParent() diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarNullOtherTypeCollection.cs b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarNullOtherTypeCollection.cs index db7c73b46..2f06d2709 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarNullOtherTypeCollection.cs +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarNullOtherTypeCollection.cs @@ -12,7 +12,6 @@ public class ScalarNullOtherTypeCollection : ScalarTestBase { public ScalarNullOtherTypeCollection() { - this.OtherKnownTypes = null; } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarOtherTypeInUse.cs b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarOtherTypeInUse.cs index af8476562..3a989fa0f 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarOtherTypeInUse.cs +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarOtherTypeInUse.cs @@ -14,7 +14,6 @@ public class ScalarOtherTypeInUse : ScalarTestBase { public ScalarOtherTypeInUse() { - this.OtherKnownTypes = new TypeCollection(typeof(int)); } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarTestBase.cs b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarTestBase.cs index 257891457..1e6161c08 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarTestBase.cs +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarTestBase.cs @@ -22,7 +22,6 @@ public abstract class ScalarTestBase : IScalarGraphType { protected ScalarTestBase() { - this.OtherKnownTypes = new TypeCollection(); this.Kind = TypeKind.SCALAR; this.ValueType = ScalarValueType.Number; this.Publish = true; @@ -38,7 +37,13 @@ protected ScalarTestBase() this.SourceResolver = Substitute.For(); } - public TypeCollection OtherKnownTypes { get; set; } + public virtual IScalarGraphType Clone(string newName) + { + newName = Validation.ThrowIfNullWhiteSpaceOrReturn(newName, nameof(newName)); + var newInstance = GlobalTypes.CreateScalarInstanceOrThrow(this.GetType()) as ScalarTestBase; + newInstance.Name = newName; + return newInstance; + } public ScalarValueType ValueType { get; set; } diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTests.cs b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTests.cs deleted file mode 100644 index 3981e9836..000000000 --- a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTests.cs +++ /dev/null @@ -1,176 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Tests.Engine -{ - using System; - using System.Collections.Generic; - using GraphQL.AspNet; - using GraphQL.AspNet.Engine; - using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Tests.Engine.DefaultScalarTypeProviderTestData; - using NUnit.Framework; - - [TestFixture] - public class DefaultScalarTypeProviderTests - { - [Test] - public void EachInvocationOfaScalarIsANewInstance() - { - var provider = new DefaultScalarGraphTypeProvider(); - var instances = new List(); - - for (var i = 0; i < 3; i++) - { - var scalar = provider.CreateScalar(typeof(int)); - instances.Add(scalar); - } - - Assert.AreEqual(3, instances.Count); - - Assert.IsFalse(ReferenceEquals(instances[0], instances[1])); - Assert.IsFalse(ReferenceEquals(instances[0], instances[2])); - Assert.IsFalse(ReferenceEquals(instances[1], instances[2])); - } - - [Test] - public void AllRegisteredTypesProduceSameScalar() - { - var provider = new DefaultScalarGraphTypeProvider(); - - var primary = provider.CreateScalar(typeof(int)); - var secondary = provider.CreateScalar(typeof(int?)); - - Assert.IsFalse(ReferenceEquals(primary, secondary)); - Assert.AreEqual(primary.Name, secondary.Name); - } - - [Test] - public void IsLeaf_NullType_IsFalse() - { - var provider = new DefaultScalarGraphTypeProvider(); - - var result = provider.IsLeaf(null); - Assert.IsFalse(result); - } - - [Test] - public void RegisterCustomScalar_ValidScalarIsRegistered() - { - var provider = new DefaultScalarGraphTypeProvider(); - - provider.RegisterCustomScalar(typeof(ScalarFullyValid)); - - var instance = provider.CreateScalar(typeof(ScalarDataType)); - - Assert.IsNotNull(instance); - Assert.AreEqual(typeof(ScalarDataType), instance.ObjectType); - } - - [Test] - public void AllDefaultScalars_CanBeInstantiatedAndSearched() - { - var provider = new DefaultScalarGraphTypeProvider(); - - foreach (var instanceType in provider.ConcreteTypes) - { - var instanceFromConcrete = provider.CreateScalar(instanceType); - Assert.IsNotNull(instanceFromConcrete, $"Could not create scalar from type '{instanceType.Name}'"); - - var instanceFromName = provider.CreateScalar(instanceFromConcrete.Name); - Assert.IsNotNull(instanceFromName, $"Could not create scalar from name '{instanceFromConcrete.Name}'"); - } - } - - [Test] - public void CreateScalar_ForUnRegisteredScalarName_ReturnsNull() - { - var provider = new DefaultScalarGraphTypeProvider(); - var instance = provider.CreateScalar("NotAScalarName"); - - Assert.IsNull(instance); - } - - [Test] - public void CreateScalar_ForUnRegisteredScalarType_ReturnsNull() - { - var provider = new DefaultScalarGraphTypeProvider(); - var instance = provider.CreateScalar(typeof(DefaultScalarTypeProviderTests)); - - Assert.IsNull(instance); - } - - [Test] - public void RetrieveConcreteType_ByScalarName_ReturnsScalar() - { - var provider = new DefaultScalarGraphTypeProvider(); - var type = provider.RetrieveConcreteType(Constants.ScalarNames.INT); - - Assert.AreEqual(typeof(int), type); - } - - [Test] - public void RetrieveConcreteType_ByScalarName_ReturnsNull() - { - var provider = new DefaultScalarGraphTypeProvider(); - var type = provider.RetrieveConcreteType("not a scalar name"); - - Assert.IsNull(type); - } - - [Test] - public void RetrieveScalarName_ByConcreteType_ReturnsnUll() - { - var provider = new DefaultScalarGraphTypeProvider(); - var type = provider.RetrieveScalarName(typeof(DefaultScalarTypeProviderTests)); - - Assert.IsNull(type); - } - - [Test] - public void RetrieveScalarName_ByConcreteType() - { - var provider = new DefaultScalarGraphTypeProvider(); - var name = provider.RetrieveScalarName(typeof(int)); - - Assert.AreEqual(Constants.ScalarNames.INT, name); - } - - [TestCase(null, typeof(ArgumentNullException))] - [TestCase(typeof(ScalarTypeDoesNotImplementScalarInterface), typeof(GraphTypeDeclarationException))] - [TestCase(typeof(ScalarNoDefaultConstructor), typeof(GraphTypeDeclarationException))] - [TestCase(typeof(ScalarInvalidGraphName), typeof(GraphTypeDeclarationException))] - [TestCase(typeof(ScalarNoGraphName), typeof(GraphTypeDeclarationException))] - [TestCase(typeof(ScalarInUseGraphName), typeof(GraphTypeDeclarationException))] - [TestCase(typeof(ScalarNotCorrectTypeKind), typeof(GraphTypeDeclarationException))] - [TestCase(typeof(ScalarNoPrimaryObjectType), typeof(GraphTypeDeclarationException))] - [TestCase(typeof(ScalarNoSourceResolver), typeof(GraphTypeDeclarationException))] - [TestCase(typeof(ScalarUnknownValueType), typeof(GraphTypeDeclarationException))] - [TestCase(typeof(ScalarNullOtherTypeCollection), typeof(GraphTypeDeclarationException))] - [TestCase(typeof(ScalarNullAppliedDirectives), typeof(GraphTypeDeclarationException))] - [TestCase(typeof(ScalarIncorrectAppliedDirectivesParent), typeof(GraphTypeDeclarationException))] - [TestCase(typeof(ScalarImplementationTypeInUse), typeof(GraphTypeDeclarationException))] - [TestCase(typeof(ScalarOtherTypeInUse), typeof(GraphTypeDeclarationException))] - public void RegisterCustomScalar_ExpectedDeclarationException(Type scalarType, Type expectedExceptionType) - { - var provider = new DefaultScalarGraphTypeProvider(); - try - { - provider.RegisterCustomScalar(scalarType); - } - catch (Exception ex) - { - Assert.IsTrue(ex.GetType() == expectedExceptionType, $"Expected exception type not thrown. Tested Type '{scalarType?.Name ?? "-null-"}'"); - return; - } - - Assert.Fail($"Excepted an exception of type '{expectedExceptionType.Name}' to be thrown."); - } - } -} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomController.cs b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomController.cs new file mode 100644 index 000000000..d70cb2252 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomController.cs @@ -0,0 +1,23 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Engine.DefaultSchemaFactoryTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Controllers; + + public class CustomController : GraphController + { + [Query] + public int MethodField(int id) + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomDirective.cs b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomDirective.cs new file mode 100644 index 000000000..ee5011219 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomDirective.cs @@ -0,0 +1,25 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Engine.DefaultSchemaFactoryTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Directives; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Schemas.TypeSystem; + + public class CustomDirective : GraphDirective + { + [DirectiveLocations(DirectiveLocation.FIELD)] + public IGraphActionResult Execute(int arg) + { + return this.Ok(); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomEnum.cs b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomEnum.cs new file mode 100644 index 000000000..3b20c81a8 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomEnum.cs @@ -0,0 +1,17 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Engine.DefaultSchemaFactoryTestData +{ + public enum CustomEnum + { + Value1, + Value2, + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomObjectWithCustomScalarArgument.cs b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomObjectWithCustomScalarArgument.cs new file mode 100644 index 000000000..165f70f88 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomObjectWithCustomScalarArgument.cs @@ -0,0 +1,23 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Engine.DefaultSchemaFactoryTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class CustomObjectWithCustomScalarArgument + { + [GraphField] + public int FieldWithScalarArg(TwoPropertyObject obj) + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomObjectWithCustomScalarField.cs b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomObjectWithCustomScalarField.cs new file mode 100644 index 000000000..7f6a58ddf --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomObjectWithCustomScalarField.cs @@ -0,0 +1,23 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Engine.DefaultSchemaFactoryTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class CustomObjectWithCustomScalarField + { + [GraphField] + public TwoPropertyObject FieldWithScalarReturnValue(int arg) + { + return null; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomObjectWithFieldWithArg.cs b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomObjectWithFieldWithArg.cs new file mode 100644 index 000000000..20d21c68a --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/CustomObjectWithFieldWithArg.cs @@ -0,0 +1,22 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Engine.DefaultSchemaFactoryTestData +{ + using GraphQL.AspNet.Attributes; + + public class CustomObjectWithFieldWithArg + { + [GraphField] + public int MethodWithArg(decimal? arg) + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/IInjectedService.cs b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/IInjectedService.cs new file mode 100644 index 000000000..a81acf224 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/IInjectedService.cs @@ -0,0 +1,15 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Engine.DefaultSchemaFactoryTestData +{ + public interface IInjectedService + { + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/SkippedType.cs b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/SkippedType.cs new file mode 100644 index 000000000..457826a82 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/SkippedType.cs @@ -0,0 +1,20 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Engine.DefaultSchemaFactoryTestData +{ + using GraphQL.AspNet.Attributes; + + [GraphSkip] + [GraphType(PreventAutoInclusion = true)] + public class SkippedType + { + public int Prop1 { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/TwoPropertyObjectAsScalar.cs b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/TwoPropertyObjectAsScalar.cs new file mode 100644 index 000000000..bcedb9dff --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTestData/TwoPropertyObjectAsScalar.cs @@ -0,0 +1,31 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Engine.DefaultSchemaFactoryTestData +{ + using System; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class TwoPropertyObjectAsScalar : ScalarGraphTypeBase + { + public TwoPropertyObjectAsScalar() + : base(nameof(TwoPropertyObjectAsScalar), typeof(TwoPropertyObject)) + { + } + + public override object Resolve(ReadOnlySpan data) + { + return null; + } + + public override ScalarValueType ValueType => ScalarValueType.String; + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTests.cs b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTests.cs new file mode 100644 index 000000000..a431b6839 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultSchemaFactoryTests.cs @@ -0,0 +1,601 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Engine +{ + using System; + using System.Linq; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Engine; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Common.Interfaces; + using GraphQL.AspNet.Tests.Engine.DefaultSchemaFactoryTestData; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.DependencyInjection; + using NUnit.Framework; + + [TestFixture] + public class DefaultSchemaFactoryTests + { + private IServiceCollection SetupCollection() + { + var collection = new ServiceCollection(); + return collection; + } + + [Test] + public void OneScalarType_GeneratesCorrectly() + { + var collection = this.SetupCollection(); + + var factory = new DefaultGraphQLSchemaFactory(includeBuiltInDirectives: false); + var options = new SchemaOptions(collection); + options.DeclarationOptions.DisableIntrospection = true; + + var provider = collection.BuildServiceProvider(); + + var scope = provider.CreateScope(); + var config = options.CreateConfiguration(); + + var instance = factory.CreateInstance( + scope, + config, + new SchemaTypeToRegister[] + { + new SchemaTypeToRegister(typeof(int)), + }); + + Assert.IsNotNull(instance); + Assert.AreEqual(3, instance.KnownTypes.Count); + Assert.IsTrue(instance.KnownTypes.Contains(typeof(int))); + Assert.IsTrue(instance.KnownTypes.Contains(typeof(string))); + Assert.IsTrue(instance.Operations.ContainsKey(GraphOperationType.Query)); + + var graphType = instance.KnownTypes.FindGraphType(typeof(int)); + Assert.IsNotNull(graphType); + Assert.IsTrue(graphType is IScalarGraphType); + Assert.AreEqual(typeof(int), ((IScalarGraphType)graphType).ObjectType); + } + + [Test] + public void CustomScalar_AllAssociatedTypesAreRegistered() + { + var collection = this.SetupCollection(); + + var factory = new DefaultGraphQLSchemaFactory(includeBuiltInDirectives: false); + var options = new SchemaOptions(collection); + options.DeclarationOptions.DisableIntrospection = true; + + var provider = collection.BuildServiceProvider(); + + var scope = provider.CreateScope(); + var config = options.CreateConfiguration(); + + var instance = factory.CreateInstance( + scope, + config, + new SchemaTypeToRegister[] + { + new SchemaTypeToRegister(typeof(TwoPropertyObjectAsScalar)), + }); + + Assert.IsNotNull(instance); + Assert.AreEqual(3, instance.KnownTypes.Count); + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == nameof(TwoPropertyObjectAsScalar))); + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == Constants.ScalarNames.STRING)); + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == Constants.ReservedNames.QUERY_TYPE_NAME)); + + // should find the custom scalar + var customScalar = instance.KnownTypes.FindGraphType(typeof(TwoPropertyObject)) as IScalarGraphType; + Assert.IsNotNull(customScalar); + Assert.AreEqual(typeof(TwoPropertyObject), customScalar.ObjectType); + } + + [Test] + public void OneEnum_AllAssociatedTypesAreRegistered() + { + var collection = this.SetupCollection(); + + var factory = new DefaultGraphQLSchemaFactory(includeBuiltInDirectives: false); + var options = new SchemaOptions(collection); + options.DeclarationOptions.DisableIntrospection = true; + + var provider = collection.BuildServiceProvider(); + + var scope = provider.CreateScope(); + var config = options.CreateConfiguration(); + + var instance = factory.CreateInstance( + scope, + config, + new SchemaTypeToRegister[] + { + new SchemaTypeToRegister(typeof(CustomEnum)), + }); + + Assert.IsNotNull(instance); + Assert.AreEqual(3, instance.KnownTypes.Count); + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == nameof(CustomEnum))); + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == Constants.ScalarNames.STRING)); + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == Constants.ReservedNames.QUERY_TYPE_NAME)); + + // should find the custom scalar + var customEnum = instance.KnownTypes.FindGraphType(typeof(CustomEnum)) as IEnumGraphType; + Assert.IsNotNull(customEnum); + Assert.AreEqual(typeof(CustomEnum), customEnum.ObjectType); + + Assert.AreEqual(2, customEnum.Values.Count); + Assert.IsNotNull(customEnum.Values.FindByEnumValue(CustomEnum.Value1)); + Assert.IsNotNull(customEnum.Values.FindByEnumValue(CustomEnum.Value2)); + } + + [Test] + public void OneObjectType_NoArgumentsOnFields_GeneratesCorrectly() + { + var collection = this.SetupCollection(); + + var factory = new DefaultGraphQLSchemaFactory(includeBuiltInDirectives: false); + var options = new SchemaOptions(collection); + options.DeclarationOptions.DisableIntrospection = true; + + var provider = collection.BuildServiceProvider(); + + var scope = provider.CreateScope(); + var config = options.CreateConfiguration(); + + var instance = factory.CreateInstance( + scope, + config, + new SchemaTypeToRegister[] + { + new SchemaTypeToRegister(typeof(TwoPropertyObjectV2)), + }); + + Assert.IsNotNull(instance); + + Assert.AreEqual(5, instance.KnownTypes.Count); + Assert.IsTrue(instance.KnownTypes.Contains(typeof(DateTime))); // field on v2 + Assert.IsTrue(instance.KnownTypes.Contains(typeof(float))); // field on v2 + Assert.IsTrue(instance.KnownTypes.Contains(typeof(TwoPropertyObjectV2))); // the object itself + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == Constants.ScalarNames.STRING)); // required for __typename + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == Constants.ReservedNames.QUERY_TYPE_NAME)); + + var graphType = instance.KnownTypes.FindGraphType(typeof(TwoPropertyObjectV2)); + Assert.IsNotNull(graphType); + Assert.IsTrue(graphType is IObjectGraphType); + Assert.AreEqual(typeof(TwoPropertyObjectV2), ((IObjectGraphType)graphType).ObjectType); + + // the two declared properties + __typekind + Assert.AreEqual(3, ((IObjectGraphType)graphType).Fields.Count); + } + + [Test] + public void OneInputObjectType_GeneratesCorrectly() + { + var collection = this.SetupCollection(); + + var factory = new DefaultGraphQLSchemaFactory(includeBuiltInDirectives: false); + var options = new SchemaOptions(collection); + options.DeclarationOptions.DisableIntrospection = true; + + var provider = collection.BuildServiceProvider(); + + var scope = provider.CreateScope(); + var config = options.CreateConfiguration(); + + var instance = factory.CreateInstance( + scope, + config, + new SchemaTypeToRegister[] + { + new SchemaTypeToRegister(typeof(TwoPropertyObjectV2), TypeKind.INPUT_OBJECT), + }); + + Assert.IsNotNull(instance); + + Assert.AreEqual(5, instance.KnownTypes.Count); + Assert.IsTrue(instance.KnownTypes.Contains(typeof(DateTime))); // field on v2 + Assert.IsTrue(instance.KnownTypes.Contains(typeof(float))); // field on v2 + Assert.IsTrue(instance.KnownTypes.Contains(typeof(TwoPropertyObjectV2))); // the object itself + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == Constants.ScalarNames.STRING)); // required for __typename + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == Constants.ReservedNames.QUERY_TYPE_NAME)); + + var graphType = instance.KnownTypes.FindGraphType(typeof(TwoPropertyObjectV2)); + Assert.IsNotNull(graphType); + Assert.IsTrue(graphType is IInputObjectGraphType); + Assert.AreEqual(typeof(TwoPropertyObjectV2), ((IInputObjectGraphType)graphType).ObjectType); + Assert.AreEqual(2, ((IInputObjectGraphType)graphType).Fields.Count); + } + + [Test] + public void OneInterfaceType_NoArgumentsOnFields_GeneratesCorrectly() + { + var collection = this.SetupCollection(); + + var factory = new DefaultGraphQLSchemaFactory(includeBuiltInDirectives: false); + var options = new SchemaOptions(collection); + options.DeclarationOptions.DisableIntrospection = true; + + var provider = collection.BuildServiceProvider(); + + var scope = provider.CreateScope(); + var config = options.CreateConfiguration(); + + var instance = factory.CreateInstance( + scope, + config, + new SchemaTypeToRegister[] + { + new SchemaTypeToRegister(typeof(ISinglePropertyObject)), + }); + + Assert.IsNotNull(instance); + + Assert.AreEqual(3, instance.KnownTypes.Count); + Assert.IsTrue(instance.KnownTypes.Contains(typeof(ISinglePropertyObject))); // the object itself + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == Constants.ScalarNames.STRING)); + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == Constants.ReservedNames.QUERY_TYPE_NAME)); + + var graphType = instance.KnownTypes.FindGraphType(typeof(ISinglePropertyObject)); + Assert.IsNotNull(graphType); + Assert.IsTrue(graphType is IInterfaceGraphType); + Assert.AreEqual(typeof(ISinglePropertyObject), ((IInterfaceGraphType)graphType).ObjectType); + + // the one declared field + __typekind + Assert.AreEqual(2, ((IInterfaceGraphType)graphType).Fields.Count); + } + + [Test] + public void OneObjectType_ArgumentsOnField_GeneratesCorrectly() + { + var collection = this.SetupCollection(); + + var factory = new DefaultGraphQLSchemaFactory(includeBuiltInDirectives: false); + var options = new SchemaOptions(collection); + options.DeclarationOptions.DisableIntrospection = true; + + var provider = collection.BuildServiceProvider(); + + var scope = provider.CreateScope(); + var config = options.CreateConfiguration(); + + var instance = factory.CreateInstance( + scope, + config, + new SchemaTypeToRegister[] + { + new SchemaTypeToRegister(typeof(CustomObjectWithFieldWithArg)), + }); + + Assert.IsNotNull(instance); + + Assert.AreEqual(5, instance.KnownTypes.Count); + Assert.IsTrue(instance.KnownTypes.Contains(typeof(decimal))); // argument on field + Assert.IsTrue(instance.KnownTypes.Contains(typeof(int))); // return type of field + Assert.IsTrue(instance.KnownTypes.Contains(typeof(CustomObjectWithFieldWithArg))); // the object itself + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == Constants.ScalarNames.STRING)); // required for __typename + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == Constants.ReservedNames.QUERY_TYPE_NAME)); + + var graphType = instance.KnownTypes.FindGraphType(typeof(CustomObjectWithFieldWithArg)); + Assert.IsNotNull(graphType); + Assert.IsTrue(graphType is IObjectGraphType); + Assert.AreEqual(typeof(CustomObjectWithFieldWithArg), ((IObjectGraphType)graphType).ObjectType); + + // the declared method + __typekind + Assert.AreEqual(2, ((IObjectGraphType)graphType).Fields.Count); + var field = ((IObjectGraphType)graphType).Fields.SingleOrDefault(x => x.Name != Constants.ReservedNames.TYPENAME_FIELD); + + Assert.IsNotNull(field); + Assert.AreEqual(1, field.Arguments.Count); + Assert.AreEqual(typeof(decimal), field.Arguments[0].ObjectType); + Assert.AreEqual("arg", field.Arguments[0].Name); + } + + [Test] + public void OneDirective_GeneratesCorrectly() + { + var collection = this.SetupCollection(); + + var factory = new DefaultGraphQLSchemaFactory(includeBuiltInDirectives: false); + var options = new SchemaOptions(collection); + options.DeclarationOptions.DisableIntrospection = true; + + var provider = collection.BuildServiceProvider(); + + var scope = provider.CreateScope(); + var config = options.CreateConfiguration(); + + var instance = factory.CreateInstance( + scope, + config, + new SchemaTypeToRegister[] + { + new SchemaTypeToRegister(typeof(CustomDirective)), + }); + + Assert.IsNotNull(instance); + + Assert.AreEqual(4, instance.KnownTypes.Count); + Assert.IsTrue(instance.KnownTypes.Contains(typeof(int))); // argument on directive + Assert.IsTrue(instance.KnownTypes.Contains(typeof(CustomDirective))); // the directive itself + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == Constants.ScalarNames.STRING)); // required for __typename + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == Constants.ReservedNames.QUERY_TYPE_NAME)); + + var graphType = instance.KnownTypes.FindGraphType(typeof(CustomDirective)); + Assert.IsNotNull(graphType); + Assert.IsTrue(graphType is IDirective); + Assert.AreEqual(typeof(CustomDirective), ((IDirective)graphType).ObjectType); + + // the declared method + __typekind + Assert.AreEqual(1, ((IDirective)graphType).Arguments.Count); + Assert.AreEqual(typeof(int), ((IDirective)graphType).Arguments[0].ObjectType); + } + + [Test] + public void OneController_GeneratesCorrectly() + { + var collection = this.SetupCollection(); + + var factory = new DefaultGraphQLSchemaFactory(includeBuiltInDirectives: false); + var options = new SchemaOptions(collection); + options.DeclarationOptions.DisableIntrospection = true; + + var provider = collection.BuildServiceProvider(); + + var scope = provider.CreateScope(); + var config = options.CreateConfiguration(); + + var instance = factory.CreateInstance( + scope, + config, + new SchemaTypeToRegister[] + { + new SchemaTypeToRegister(typeof(CustomController)), + }); + + Assert.IsNotNull(instance); + + Assert.AreEqual(5, instance.KnownTypes.Count); + Assert.IsTrue(instance.KnownTypes.Contains(typeof(VirtualResolvedObject))); // intermediary resolved value on the controller + Assert.IsTrue(instance.KnownTypes.Contains(typeof(int))); // arg of controller field + Assert.IsNotNull(instance.KnownTypes.FindGraphType("Query_Custom")); // intermediate type for the controller + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == Constants.ScalarNames.STRING)); // required for __typename + Assert.IsNotNull(instance.KnownTypes.SingleOrDefault(x => x.Name == Constants.ReservedNames.QUERY_TYPE_NAME)); + } + + [Test] + public void ClassArgumentToAField_ThatIsRegisteredAsAScalar_IsNamedProperly() + { + var collection = this.SetupCollection(); + + var factory = new DefaultGraphQLSchemaFactory(includeBuiltInDirectives: false); + var options = new SchemaOptions(collection); + options.DeclarationOptions.DisableIntrospection = true; + + var provider = collection.BuildServiceProvider(); + + var scope = provider.CreateScope(); + var config = options.CreateConfiguration(); + + var instance = factory.CreateInstance( + scope, + config, + new SchemaTypeToRegister[] + { + new SchemaTypeToRegister(typeof(TwoPropertyObjectAsScalar)), + new SchemaTypeToRegister(typeof(CustomObjectWithCustomScalarArgument)), + }); + + Assert.IsNotNull(instance); + + var graphType = instance.KnownTypes.FindGraphType(typeof(CustomObjectWithCustomScalarArgument)) as IObjectGraphType; + var field = graphType.Fields.SingleOrDefault(x => string.Compare(x.Name, nameof(CustomObjectWithCustomScalarArgument.FieldWithScalarArg), true) == 0); + Assert.IsNotNull(field); + + var arg = field.Arguments[0]; + + Assert.AreEqual("obj", arg.Name); + + // ensure the type expression points to the scalar name + // not the name as if it was an input object + Assert.AreEqual(nameof(TwoPropertyObjectAsScalar), arg.TypeExpression.TypeName); + } + + [Test] + public void ReturnValueOfAField_ThatIsRegisteredAsAScalar_IsNamedProperly() + { + var collection = this.SetupCollection(); + + var factory = new DefaultGraphQLSchemaFactory(includeBuiltInDirectives: false); + var options = new SchemaOptions(collection); + options.DeclarationOptions.DisableIntrospection = true; + + var provider = collection.BuildServiceProvider(); + + var scope = provider.CreateScope(); + var config = options.CreateConfiguration(); + + var instance = factory.CreateInstance( + scope, + config, + new SchemaTypeToRegister[] + { + new SchemaTypeToRegister(typeof(TwoPropertyObjectAsScalar)), + new SchemaTypeToRegister(typeof(CustomObjectWithCustomScalarField)), + }); + + Assert.IsNotNull(instance); + + var graphType = instance.KnownTypes.FindGraphType(typeof(CustomObjectWithCustomScalarField)) as IObjectGraphType; + var field = graphType.Fields.SingleOrDefault(x => string.Compare(x.Name, nameof(CustomObjectWithCustomScalarField.FieldWithScalarReturnValue), true) == 0); + Assert.IsNotNull(field); + + // ensure the type expression points to the scalar name + // not the name as if it was an input object + Assert.AreEqual(nameof(TwoPropertyObjectAsScalar), field.TypeExpression.TypeName); + } + + [Test] + public void SimpleRuntimeField_NoArguments_IsMappedCorrectly() + { + var collection = this.SetupCollection(); + + var factory = new DefaultGraphQLSchemaFactory(includeBuiltInDirectives: false); + var options = new SchemaOptions(collection); + options.DeclarationOptions.DisableIntrospection = true; + + options.MapQuery("field1", () => 0); + + var provider = collection.BuildServiceProvider(); + var scope = provider.CreateScope(); + var config = options.CreateConfiguration(); + + var instance = factory.CreateInstance( + scope, + config, + runtimeItemDefinitions: options.RuntimeTemplates); + + Assert.IsNotNull(instance); + + // the root query object should contain the field + var query = instance.Operations[GraphOperationType.Query]; + + // field1 & __typename + Assert.AreEqual(2, query.Fields.Count); + Assert.IsNotNull(query.Fields["field1"]); + } + + [Test] + public void SimpleRuntimeField_OneExplicitSchemaArgument_IsMappedCorrectly() + { + var collection = this.SetupCollection(); + + var factory = new DefaultGraphQLSchemaFactory(includeBuiltInDirectives: false); + var options = new SchemaOptions(collection); + options.DeclarationOptions.DisableIntrospection = true; + + options.MapQuery("field1", (string arg1) => 0); + + var provider = collection.BuildServiceProvider(); + var scope = provider.CreateScope(); + var config = options.CreateConfiguration(); + + var instance = factory.CreateInstance( + scope, + config, + runtimeItemDefinitions: options.RuntimeTemplates); + + Assert.IsNotNull(instance); + + // the root query object should contain the field + var query = instance.Operations[GraphOperationType.Query]; + + // field1 & __typename + Assert.AreEqual(2, query.Fields.Count); + + var field = query.Fields["field1"]; + Assert.IsNotNull(field); + Assert.AreEqual(1, field.Arguments.Count); + Assert.IsNotNull(field.Arguments["arg1"]); + Assert.AreEqual(typeof(string), field.Arguments["arg1"].ObjectType); + } + + [Test] + public void SimpleRuntimeField_OneExplicitServiceArgument_IsMappedCorrectly() + { + var collection = this.SetupCollection(); + + var factory = new DefaultGraphQLSchemaFactory(includeBuiltInDirectives: false); + var options = new SchemaOptions(collection); + options.DeclarationOptions.DisableIntrospection = true; + + options.MapQuery("field1", ([FromServices] IInjectedService service) => 0); + + var provider = collection.BuildServiceProvider(); + var scope = provider.CreateScope(); + var config = options.CreateConfiguration(); + + var instance = factory.CreateInstance( + scope, + config, + runtimeItemDefinitions: options.RuntimeTemplates); + + Assert.IsNotNull(instance); + + // the root query object should contain the field + var query = instance.Operations[GraphOperationType.Query]; + + // field1 & __typename + Assert.AreEqual(2, query.Fields.Count); + + var field = query.Fields["field1"]; + Assert.IsNotNull(field); + + // no argument should be registered to the schema + Assert.AreEqual(0, field.Arguments.Count); + } + + [Test] + public void SchemaItemValidators_AreInvoked() + { + var collection = this.SetupCollection(); + + var factory = new DefaultGraphQLSchemaFactory(includeBuiltInDirectives: false); + var options = new SchemaOptions(collection); + options.DeclarationOptions.DisableIntrospection = true; + + // incorrect explicit schema item, enforced by runtime argument validator + options.MapQuery("field1", ([FromGraphQL] IInjectedService service) => 0); + + var provider = collection.BuildServiceProvider(); + var scope = provider.CreateScope(); + var config = options.CreateConfiguration(); + + Assert.Throws(() => + { + var instance = factory.CreateInstance( + scope, + config, + runtimeItemDefinitions: options.RuntimeTemplates); + }); + } + + [Test] + public void RuntimeFieldDefs_HaveSamePath_ThrowException() + { + var collection = this.SetupCollection(); + + var factory = new DefaultGraphQLSchemaFactory(includeBuiltInDirectives: false); + var options = new SchemaOptions(collection); + options.DeclarationOptions.DisableIntrospection = true; + + // same field path + options.MapQuery("field1", (int arg1) => 0); + options.MapQuery("field1", (int arg1) => 0); + + var provider = collection.BuildServiceProvider(); + var scope = provider.CreateScope(); + var config = options.CreateConfiguration(); + + var ex = Assert.Throws(() => + { + var instance = factory.CreateInstance( + scope, + config, + runtimeItemDefinitions: options.RuntimeTemplates); + }); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/DirectiveTypeMakerTests.cs b/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/DirectiveTypeMakerTests.cs deleted file mode 100644 index a12e686aa..000000000 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/DirectiveTypeMakerTests.cs +++ /dev/null @@ -1,85 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers -{ - using System.Linq; - using GraphQL.AspNet.Engine; - using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.Resolvers; - using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Engine.TypeMakers.TestData; - using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - using NUnit.Framework; - - [TestFixture] - public class DirectiveTypeMakerTests : GraphTypeMakerTestBase - { - [Test] - public void Directive_BasicPropertyCheck() - { - var builder = new TestServerBuilder(); - var server = builder.Build(); - var typeMaker = new DefaultGraphTypeMakerProvider() - .CreateTypeMaker(server.Schema, TypeKind.DIRECTIVE); - - var directive = typeMaker.CreateGraphType(typeof(MultiMethodDirective)).GraphType as IDirective; - - Assert.AreEqual("multiMethod", directive.Name); - Assert.AreEqual("A Multi Method Directive", directive.Description); - Assert.AreEqual(TypeKind.DIRECTIVE, directive.Kind); - Assert.IsTrue((bool)directive.Publish); - Assert.AreEqual(DirectiveLocation.FIELD | DirectiveLocation.SCALAR, directive.Locations); - Assert.AreEqual(typeof(GraphDirectiveActionResolver), directive.Resolver.GetType()); - - Assert.AreEqual(2, directive.Arguments.Count); - - var arg0 = Enumerable.FirstOrDefault(directive.Arguments); - var arg1 = Enumerable.Skip(directive.Arguments, 1).FirstOrDefault(); - - Assert.IsNotNull(arg0); - Assert.AreEqual("firstArg", arg0.Name); - Assert.AreEqual(typeof(int), arg0.ObjectType); - - Assert.IsNotNull(arg1); - Assert.AreEqual("secondArg", arg1.Name); - Assert.AreEqual(typeof(TwoPropertyObject), arg1.ObjectType); - } - - [Test] - public void Directive_RepeatableAttributeIsSetWhenPresent() - { - var builder = new TestServerBuilder(); - var server = builder.Build(); - var typeMaker = new DefaultGraphTypeMakerProvider() - .CreateTypeMaker(server.Schema, TypeKind.DIRECTIVE); - - var directive = typeMaker.CreateGraphType(typeof(RepeatableDirective)).GraphType as IDirective; - - Assert.IsTrue((bool)directive.IsRepeatable); - Assert.AreEqual("repeatable", directive.Name); - Assert.AreEqual(TypeKind.DIRECTIVE, directive.Kind); - Assert.IsTrue((bool)directive.Publish); - Assert.AreEqual(DirectiveLocation.SCALAR, directive.Locations); - - Assert.AreEqual(2, directive.Arguments.Count); - - var arg0 = Enumerable.FirstOrDefault(directive.Arguments); - var arg1 = Enumerable.Skip(directive.Arguments, 1).FirstOrDefault(); - - Assert.IsNotNull(arg0); - Assert.AreEqual("firstArg", arg0.Name); - Assert.AreEqual(typeof(int), arg0.ObjectType); - - Assert.IsNotNull(arg1); - Assert.AreEqual("secondArg", arg1.Name); - Assert.AreEqual(typeof(TwoPropertyObject), arg1.ObjectType); - } - } -} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/GraphTypeMakerFactoryTests.cs b/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/GraphTypeMakerFactoryTests.cs deleted file mode 100644 index 86c9a8789..000000000 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/GraphTypeMakerFactoryTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Tests.Engine.TypeMakers -{ - using GraphQL.AspNet.Engine; - using GraphQL.AspNet.Schemas; - using GraphQL.AspNet.Schemas.TypeSystem; - using NUnit.Framework; - - [TestFixture] - public class GraphTypeMakerFactoryTests : GraphTypeMakerTestBase - { - [Test] - public void DefaultFactory_NoSchema_YieldsNoMaker() - { - var factory = new DefaultGraphTypeMakerProvider(); - var instance = factory.CreateTypeMaker(null, TypeKind.OBJECT); - Assert.IsNull(instance); - } - - [Test] - public void DefaultFactory_UnknownTypeKind_YieldsNoMaker() - { - var schema = new GraphSchema(); - var factory = new DefaultGraphTypeMakerProvider(); - var instance = factory.CreateTypeMaker(schema, TypeKind.LIST); - Assert.IsNull(instance); - } - } -} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/ScalarGraphTypeMakerTests.cs b/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/ScalarGraphTypeMakerTests.cs deleted file mode 100644 index da614264a..000000000 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/ScalarGraphTypeMakerTests.cs +++ /dev/null @@ -1,36 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers -{ - using GraphQL.AspNet.Engine.TypeMakers; - using NUnit.Framework; - - [TestFixture] - public class ScalarGraphTypeMakerTests - { - [Test] - public void RegisteredScalarIsReturned() - { - var maker = new ScalarGraphTypeMaker(); - - var result = maker.CreateGraphType(typeof(int)); - Assert.IsNotNull(result?.GraphType); - Assert.AreEqual(typeof(int), result.ConcreteType); - } - - [Test] - public void NullType_ReturnsNullResult() - { - var maker = new ScalarGraphTypeMaker(); - - var result = maker.CreateGraphType(null); - Assert.IsNull(result); - } - } -} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/ApolloTracingTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/ApolloTracingTests.cs index e52b6e141..48ad121cb 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/ApolloTracingTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/ApolloTracingTests.cs @@ -16,10 +16,10 @@ namespace GraphQL.AspNet.Tests.Execution using GraphQL.AspNet.Engine; using GraphQL.AspNet.Execution.Metrics; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.BatchResolverTestData; using GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/AuthenticatedUserTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/AuthenticatedUserTests.cs index 97c48db54..60e0d8d39 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/AuthenticatedUserTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/AuthenticatedUserTests.cs @@ -9,9 +9,9 @@ namespace GraphQL.AspNet.Tests.Execution { using System.Threading.Tasks; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.AuthenticatedUserTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/ContextTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/ContextTests.cs index e497f817a..3cde90d46 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/ContextTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/ContextTests.cs @@ -10,6 +10,7 @@ namespace GraphQL.AspNet.Tests.Execution { using System; + using System.Security.Claims; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Contexts; using GraphQL.AspNet.Interfaces.Execution; @@ -114,6 +115,9 @@ public void GraphDirectiveExecutionContext_PropertyCheck() var metrics = Substitute.For(); var securityContext = Substitute.For(); var schema = new GraphSchema(); + var user = new ClaimsPrincipal(); + var session = new QuerySession(); + var messages = new GraphMessageCollection(); parentContext.QueryRequest.Returns(queryRequest); parentContext.ServiceProvider.Returns(serviceProvider); @@ -127,18 +131,24 @@ public void GraphDirectiveExecutionContext_PropertyCheck() var sourceFieldCollection = new FieldSourceCollection(); var context = new DirectiveResolutionContext( + serviceProvider, + session, schema, - parentContext, + queryRequest, directiveRequest, - args); + args, + messages, + logger, + user); Assert.AreEqual(queryRequest, context.QueryRequest); Assert.AreEqual(directiveRequest, context.Request); Assert.AreEqual(serviceProvider, context.ServiceProvider); Assert.AreEqual(logger, context.Logger); - Assert.AreEqual(metrics, context.Metrics); - Assert.AreEqual(securityContext, context.SecurityContext); Assert.AreEqual(schema, context.Schema); + Assert.AreEqual(directiveRequest, context.Request); + Assert.AreEqual(messages, context.Messages); + Assert.AreEqual(session, context.Session); } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/ControllerIsolationTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/ControllerIsolationTests.cs index f8f0809c4..6c8590ecf 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/ControllerIsolationTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/ControllerIsolationTests.cs @@ -11,9 +11,9 @@ namespace GraphQL.AspNet.Tests.Execution { using System.Threading.Tasks; using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.ControllerIsolationTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/DefaultGraphResponseWriterTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/DefaultGraphResponseWriterTests.cs index 9a78d3df9..1d48a1f9d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/DefaultGraphResponseWriterTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/DefaultGraphResponseWriterTests.cs @@ -21,9 +21,9 @@ namespace GraphQL.AspNet.Tests.Execution using GraphQL.AspNet.Interfaces.Engine; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NSubstitute; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/DirectiveProcessorTypeSystemTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/DirectiveProcessorTypeSystemTests.cs index d262feecf..ad17be59a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/DirectiveProcessorTypeSystemTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/DirectiveProcessorTypeSystemTests.cs @@ -10,16 +10,21 @@ namespace GraphQL.AspNet.Tests.Execution { using System; using System.Collections.Generic; + using System.Linq; using System.Threading; using System.Threading.Tasks; + using GraphQL.AspNet.Configuration; using GraphQL.AspNet.Configuration.Exceptions; using GraphQL.AspNet.Directives; + using GraphQL.AspNet.Engine; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Contexts; using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Engine; using GraphQL.AspNet.Interfaces.Middleware; using GraphQL.AspNet.Middleware; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Tests.Execution.TestData.GraphSchemaProcessorTestData; using Microsoft.Extensions.DependencyInjection; @@ -41,6 +46,7 @@ public class DirectiveProcessorTypeSystemTests public DirectiveProcessorTypeSystemTests() { _serviceCollection = new ServiceCollection(); + _itemsExecuted = new List(); _typesToAdd = new List(); } @@ -50,10 +56,15 @@ private void BuildInstance( GraphMiddlewareInvocationDelegate delegateToExecute = null) { // build the schema - _schemaInstance = new GraphSchema(); - var manager = new GraphSchemaManager(_schemaInstance); - foreach (var type in _typesToAdd) - manager.EnsureGraphType(type); + var options = new SchemaOptions(_serviceCollection); + + var schemaFactory = new DefaultGraphQLSchemaFactory( + processTypeSystemDirectives: false); + + _schemaInstance = schemaFactory.CreateInstance( + _serviceCollection.BuildServiceProvider().CreateScope(), + options.CreateConfiguration(), + _typesToAdd.Select(x => new SchemaTypeToRegister(x))); // build hte directive pipeline instance if (buildRequiredDirectivePipeline) diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/ExecutionArgumentTestData/IServiceForExecutionArgumentTest.cs b/src/unit-tests/graphql-aspnet-tests/Execution/ExecutionArgumentTestData/IServiceForExecutionArgumentTest.cs new file mode 100644 index 000000000..ed3bbb6a9 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/ExecutionArgumentTestData/IServiceForExecutionArgumentTest.cs @@ -0,0 +1,16 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution.ExecutionArgumentTestData +{ + public interface IServiceForExecutionArgumentTest + { + int ServiceValue { get; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/ExecutionArgumentTestData/ObjectWithFields.cs b/src/unit-tests/graphql-aspnet-tests/Execution/ExecutionArgumentTestData/ObjectWithFields.cs new file mode 100644 index 000000000..3453d04ed --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/ExecutionArgumentTestData/ObjectWithFields.cs @@ -0,0 +1,47 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution.ExecutionArgumentTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class ObjectWithFields + { + [GraphField] + public int FieldWithInjectedArgument(IServiceForExecutionArgumentTest arg1) + { + return arg1.ServiceValue; + } + + [GraphField] + public int FieldWithInjectedArgumentWithDefaultValue(IServiceForExecutionArgumentTest arg1 = null) + { + return 33; + } + + [GraphField] + public int FieldWithNullableSchemaArgument(TwoPropertyObject obj) + { + return 33; + } + + [GraphField] + public int FieldWithNonNullableSchemaArgumentThatHasDefaultValue(int arg1 = 3) + { + return 33; + } + + [GraphField] + public int FieldWithNotNullableQueryArgument(int arg1) + { + return 33; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Integration/EventPublishingTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/ExecutionArgumentTestData/ServiceForExecutionArgumentTest.cs similarity index 51% rename from src/unit-tests/graphql-aspnet-subscriptions-tests/Integration/EventPublishingTests.cs rename to src/unit-tests/graphql-aspnet-tests/Execution/ExecutionArgumentTestData/ServiceForExecutionArgumentTest.cs index 68073473e..bc5092361 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Integration/EventPublishingTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/ExecutionArgumentTestData/ServiceForExecutionArgumentTest.cs @@ -7,18 +7,15 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Integration +namespace GraphQL.AspNet.Tests.Execution.ExecutionArgumentTestData { - using System.Threading.Tasks; - using NUnit.Framework; - - [TestFixture] - public class EventPublishingTests + public class ServiceForExecutionArgumentTest : IServiceForExecutionArgumentTest { - [Test] - public Task EventPublishedToServer_IsTransmittedToListeningClient() + public ServiceForExecutionArgumentTest() { - return Task.CompletedTask; + this.ServiceValue = 99; } + + public int ServiceValue { get; } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/ExecutionArgumentTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/ExecutionArgumentTests.cs index 9aa0b754a..651feedf6 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/ExecutionArgumentTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/ExecutionArgumentTests.cs @@ -9,8 +9,15 @@ namespace GraphQL.AspNet.Tests.Execution { + using GraphQL.AspNet.Configuration; using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Tests.Execution.ExecutionArgumentTestData; + using GraphQL.AspNet.Tests.Framework; + using Microsoft.Extensions.DependencyInjection; using NSubstitute; using NUnit.Framework; @@ -57,5 +64,186 @@ public void TryGetArgument_WhenArgDoesntCast_FailsWithResponse() Assert.IsFalse(success); Assert.AreEqual(0, value); // default of int } + + [Test] + public void PrepareArguments_WhenArgumentShouldComeFromDI_AndExistsInDI_IsPreparedCorrectly() + { + var serviceInstance = new ServiceForExecutionArgumentTest(); + + var builder = new TestServerBuilder(TestOptions.UseCodeDeclaredNames); + builder.AddGraphQL(o => + { + o.AddType(); + o.DeclarationOptions.ArgumentBindingRule = SchemaArgumentBindingRules.ParametersPreferQueryResolution; + }); + builder.AddSingleton(serviceInstance); + + var testServer = builder.Build(); + + var contextBuilder = testServer + .CreateFieldContextBuilder(nameof(ObjectWithFields.FieldWithInjectedArgument)); + + var context = contextBuilder.CreateResolutionContext(); + + // mimic a situation where no values are parsed from a query (no execution args) + var argSet = new ExecutionArgumentCollection() as IExecutionArgumentCollection; + argSet = argSet.ForContext(context); + + var resolvedArgs = argSet.PrepareArguments(contextBuilder.ResolverMetaData); + + Assert.IsNotNull(resolvedArgs); + Assert.AreEqual(1, resolvedArgs.Length); + Assert.AreEqual(resolvedArgs[0], serviceInstance); + } + + [Test] + public void PrepareArguments_WhenArgumentShouldComeFromDI_AndDoesNotExistInDI_AndHasNoDefaultValue_ExecutionExceptionIsThrown() + { + var serviceInstance = new ServiceForExecutionArgumentTest(); + + var builder = new TestServerBuilder(TestOptions.UseCodeDeclaredNames); + builder.AddGraphQL(o => + { + o.AddType(); + o.DeclarationOptions.ArgumentBindingRule = SchemaArgumentBindingRules.ParametersPreferQueryResolution; + }); + + var testServer = builder.Build(); + + var contextBuilder = testServer + .CreateFieldContextBuilder(nameof(ObjectWithFields.FieldWithInjectedArgument)); + + var context = contextBuilder.CreateResolutionContext(); + + // mimic a situation where no values are parsed from a query (no execution args) + // and no default value is present + var argSet = new ExecutionArgumentCollection() as IExecutionArgumentCollection; + argSet = argSet.ForContext(context); + + Assert.Throws(() => + { + var resolvedArgs = argSet.PrepareArguments(contextBuilder.ResolverMetaData); + }); + } + + [Test] + public void PrepareArguments_WhenArgumentShouldComeFromDI_AndDoesNotExistInDI_AndHasADefaultValue_ValueIsResolved() + { + var serviceInstance = new ServiceForExecutionArgumentTest(); + + var builder = new TestServerBuilder(TestOptions.UseCodeDeclaredNames); + builder.AddGraphQL(o => + { + o.AddType(); + o.DeclarationOptions.ArgumentBindingRule = SchemaArgumentBindingRules.ParametersPreferQueryResolution; + }); + + var testServer = builder.Build(); + + var contextBuilder = testServer + .CreateFieldContextBuilder(nameof(ObjectWithFields.FieldWithInjectedArgumentWithDefaultValue)); + + // mimic a situation where no values are parsed from a query (no execution args) + // and nothing is available from DI + // but the parameter declares a default value + var argSet = new ExecutionArgumentCollection() as IExecutionArgumentCollection; + argSet = argSet.ForContext(contextBuilder.CreateResolutionContext()); + + var resolvedArgs = argSet.PrepareArguments(contextBuilder.ResolverMetaData); + Assert.IsNotNull(resolvedArgs); + Assert.AreEqual(1, resolvedArgs.Length); + Assert.IsNull(resolvedArgs[0]); + } + + [Test] + public void PrepareArguments_WhenArgumentShouldComeFromSchema_HasNoSuppliedValueHasNoDefaultValueIsNullable_ValueIsResolved() + { + var serviceInstance = new ServiceForExecutionArgumentTest(); + + var builder = new TestServerBuilder(TestOptions.UseCodeDeclaredNames); + builder.AddGraphQL(o => + { + o.AddType(); + o.DeclarationOptions.ArgumentBindingRule = SchemaArgumentBindingRules.ParametersPreferQueryResolution; + }); + + var testServer = builder.Build(); + + var template = new ObjectGraphTypeTemplate(typeof(ObjectWithFields)); + template.Parse(); + template.ValidateOrThrow(); + + var fieldBuilder = testServer + .CreateFieldContextBuilder(nameof(ObjectWithFields.FieldWithNullableSchemaArgument)); + + // mimic a situation where no values are parsed from a query (no execution args) + // and nothing is available from DI + // but the parameter declares a default value + var argSet = new ExecutionArgumentCollection() as IExecutionArgumentCollection; + argSet = argSet.ForContext(fieldBuilder.CreateResolutionContext()); + + var resolvedArgs = argSet.PrepareArguments(fieldBuilder.ResolverMetaData); + Assert.IsNotNull(resolvedArgs); + Assert.AreEqual(1, resolvedArgs.Length); + Assert.IsNull(resolvedArgs[0]); + } + + [Test] + public void PrepareArguments_WhenArgumentShouldComeFromSchema_HasNoSuppliedValueAndIsNotNullableHasDefaultValue_ValueIsResolved() + { + var serviceInstance = new ServiceForExecutionArgumentTest(); + + var builder = new TestServerBuilder(TestOptions.UseCodeDeclaredNames); + builder.AddGraphQL(o => + { + o.AddType(); + o.DeclarationOptions.ArgumentBindingRule = SchemaArgumentBindingRules.ParametersPreferQueryResolution; + }); + + var testServer = builder.Build(); + + var fieldBuilder = testServer + .CreateFieldContextBuilder( + nameof(ObjectWithFields.FieldWithNonNullableSchemaArgumentThatHasDefaultValue)); + + // mimic a situation where no values are parsed from a query (no execution args) + // and nothing is available from DI + // but the parameter declares a default value + var argSet = new ExecutionArgumentCollection() as IExecutionArgumentCollection; + argSet = argSet.ForContext(fieldBuilder.CreateResolutionContext()); + + var resolvedArgs = argSet.PrepareArguments(fieldBuilder.ResolverMetaData); + Assert.IsNotNull(resolvedArgs); + Assert.AreEqual(1, resolvedArgs.Length); + Assert.AreEqual(3, resolvedArgs[0]); + } + + [Test] + public void PrepareArguments_WhenArgumentShouldComeFromQuery_AndIsNotSupplied_AndIsNotNullable_ExecutionExceptionOccurs() + { + var serviceInstance = new ServiceForExecutionArgumentTest(); + + var builder = new TestServerBuilder(TestOptions.UseCodeDeclaredNames); + builder.AddGraphQL(o => + { + o.AddType(); + o.DeclarationOptions.ArgumentBindingRule = SchemaArgumentBindingRules.ParametersPreferQueryResolution; + }); + + var testServer = builder.Build(); + + var fieldBuilder = testServer + .CreateFieldContextBuilder(nameof(ObjectWithFields.FieldWithNotNullableQueryArgument)); + + // mimic a situation where no values are parsed from a query (no execution args) + // but the argument expected there to be + var argSet = new ExecutionArgumentCollection() as IExecutionArgumentCollection; + argSet = argSet.ForContext(fieldBuilder.CreateResolutionContext()); + + Assert.Throws(() => + { + var resolvedArgs = argSet.PrepareArguments(fieldBuilder.ResolverMetaData); + }); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/ExecutionDirectiveTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/ExecutionDirectiveTests.cs index 4db4c5129..011d5dc70 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/ExecutionDirectiveTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/ExecutionDirectiveTests.cs @@ -13,9 +13,9 @@ namespace GraphQL.AspNet.Tests.Execution using GraphQL.AspNet.Directives; using GraphQL.AspNet.Interfaces.Execution.QueryPlans.DocumentParts; using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.ExecutionDirectiveTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/FieldSourceCollectionTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/FieldSourceCollectionTests.cs index 0238951bc..07c18142b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/FieldSourceCollectionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/FieldSourceCollectionTests.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Execution { using GraphQL.AspNet.Execution; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; using NSubstitute; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/FragmentExecutionTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/FragmentExecutionTests.cs index 0247606b6..806895ee3 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/FragmentExecutionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/FragmentExecutionTests.cs @@ -11,9 +11,9 @@ namespace GraphQL.AspNet.Tests.Execution { using System.Threading.Tasks; using GraphQL.AspNet.Common; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/GeneralExecutionTests_NET60.cs b/src/unit-tests/graphql-aspnet-tests/Execution/GeneralExecutionTests_NET60.cs index 58326e2d4..4dfa7ae6f 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/GeneralExecutionTests_NET60.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/GeneralExecutionTests_NET60.cs @@ -14,8 +14,8 @@ namespace GraphQL.AspNet.Tests.Execution using System.Threading.Tasks; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests.cs index 3b5e2bef6..a8aa916d4 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests.cs @@ -12,9 +12,9 @@ namespace GraphQL.AspNet.Tests.Execution using System; using System.Threading.Tasks; using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] @@ -27,7 +27,7 @@ public async Task SingleFieldResolution_ViaPipeline_YieldsCorrectResult() .AddType() .Build(); - var builder = server.CreateGraphTypeFieldContextBuilder( + var builder = server.CreateFieldContextBuilder( nameof(SimpleExecutionController.SimpleQueryMethod)); builder.AddInputArgument("arg1", "my value"); builder.AddInputArgument("arg2", 15L); @@ -930,7 +930,7 @@ public async Task TypeExtension_OnValueType_ResolvesDataCorrectly() [Test] public async Task TypeExtension_OnGeneralObject_ResolvesDataCorrectly() { - var server = new TestServerBuilder() + var server = new TestServerBuilder(TestOptions.IncludeExceptions) .AddType() .Build(); diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests2.cs b/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests2.cs index 960e1f4de..1bd16d541 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests2.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests2.cs @@ -11,9 +11,9 @@ namespace GraphQL.AspNet.Tests.Execution { using System.Linq; using System.Threading.Tasks; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using Newtonsoft.Json.Linq; using NUnit.Framework; @@ -144,7 +144,7 @@ public async Task TypeNameOnAUnionReturn_YieldsResults() .AddQueryText(@"query { retrieveUnion { ... on TwoPropertyObject { - property1 + property1 property2 } __typename diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests3.cs b/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests3.cs index 8a5b18c00..f987f45c6 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests3.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/GeneralQueryExecutionTests3.cs @@ -10,9 +10,9 @@ namespace GraphQL.AspNet.Tests.Execution { using System.Threading.Tasks; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/InheritanceExecutionTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/InheritanceExecutionTests.cs index ea1a20f8b..ce45f30ee 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/InheritanceExecutionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/InheritanceExecutionTests.cs @@ -10,9 +10,9 @@ namespace GraphQL.AspNet.Tests.Execution { using System.Threading.Tasks; using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.InheritanceTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/InterfaceExecutionTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/InterfaceExecutionTests.cs index 827153742..8627aee9d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/InterfaceExecutionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/InterfaceExecutionTests.cs @@ -11,9 +11,9 @@ namespace GraphQL.AspNet.Tests.Execution { using System.Threading.Tasks; using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.InterfaceExtensionTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/IntrospectionTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/IntrospectionTests.cs index 9054680f7..dba2e6e84 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/IntrospectionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/IntrospectionTests.cs @@ -18,10 +18,10 @@ namespace GraphQL.AspNet.Tests.Execution using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Schemas.TypeSystem.Introspection.Model; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData; using GraphQL.AspNet.Tests.Execution.TestData.IntrospectionTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] @@ -1165,7 +1165,7 @@ public async Task DeprecatedLateBoundEnumValue_ReturnsTrueDeprecationFlag() schemaItem != null && schemaItem is IEnumValue ev && ev.Parent.ObjectType == typeof(IntrospectableEnum) - && Convert.ToInt32(ev.InternalValue) == (int)IntrospectableEnum.Value1); + && Convert.ToInt32(ev.DeclaredValue) == (int)IntrospectableEnum.Value1); }) .Build(); @@ -1345,13 +1345,10 @@ public async Task SpecifiedByLateBound_WithAtSymbol_PopulateSpecifiedByURL() [Test] public async Task SpecifiedByEarlyBound_PopulateSpecifiedByURL() { - using var restorePoint = new GraphQLGlobalRestorePoint(); - - GraphQLProviders.ScalarProvider = new DefaultScalarGraphTypeProvider(); - GraphQLProviders.ScalarProvider.RegisterCustomScalar(typeof(CustomSpecifiedScalar)); var serverBuilder = new TestServerBuilder(); var server = serverBuilder.AddGraphQL(o => { + o.AddGraphType(); o.AddGraphType(); }) .Build(); diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/ListManglerTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/ListManglerTests.cs index b4f7bf561..e2dcab4a3 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/ListManglerTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/ListManglerTests.cs @@ -15,8 +15,8 @@ namespace GraphQL.AspNet.Tests.Execution using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.ListManglerTestData; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/Parsing/SourceTextLexerExtensionsTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/Parsing/SourceTextLexerExtensionsTests.cs index 46f65f07f..b8a9503d7 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/Parsing/SourceTextLexerExtensionsTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/Parsing/SourceTextLexerExtensionsTests.cs @@ -8,7 +8,7 @@ // ************************************************************* // ReSharper disable All -namespace GraphQL.AspNet.Tests.Parsing +namespace GraphQL.AspNet.Tests.Execution.Parsing { using System; using System.Linq; @@ -127,7 +127,7 @@ public void SourceText_NextString_ValidStrings( // Note: expectedResult is ignored if null. if (expectedResult != null) - Assert.AreEqual(expectedResult, source.RetrieveText(result).ToString()); + Assert.AreEqual(expectedResult, source.RetrieveText(result).ToString()); } [TestCase("", 0, -1, -1, -1)] // nothing diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/QueryPlans/ArgumentGeneratorTestData/InputController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/QueryPlans/ArgumentGeneratorTestData/InputController.cs index e116ae7f6..4ec0c5c95 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/QueryPlans/ArgumentGeneratorTestData/InputController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/QueryPlans/ArgumentGeneratorTestData/InputController.cs @@ -13,7 +13,7 @@ namespace GraphQL.AspNet.Tests.Execution.QueryPlans.ArgumentGeneratorTestData using System.Linq; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class InputController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/QueryPlans/ArgumentGeneratorTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/QueryPlans/ArgumentGeneratorTests.cs index 4dcb22f55..53a36fc46 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/QueryPlans/ArgumentGeneratorTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/QueryPlans/ArgumentGeneratorTests.cs @@ -18,9 +18,9 @@ namespace GraphQL.AspNet.Tests.Execution.QueryPlans using GraphQL.AspNet.Execution.Variables; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.QueryPlans.ArgumentGeneratorTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/QueryPlans/ExecutionPlanGenerationTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/QueryPlans/ExecutionPlanGenerationTests.cs index ac28597de..827f3238a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/QueryPlans/ExecutionPlanGenerationTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/QueryPlans/ExecutionPlanGenerationTests.cs @@ -13,14 +13,15 @@ namespace GraphQL.AspNet.Tests.Execution.QueryPlans using System.Linq; using System.Threading.Tasks; using GraphQL.AspNet.Engine; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Execution.Variables; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Execution.QueryPlans.DocumentParts; - using GraphQL.AspNet.Internal.Resolvers; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Common.Interfaces; using GraphQL.AspNet.Tests.Execution.QueryPlans.PlanGenerationTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Tests.Framework.Interfaces; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/QueryPlans/PlanGenerationTestData/SimplePlanGenerationController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/QueryPlans/PlanGenerationTestData/SimplePlanGenerationController.cs index 013c9999d..12d9d322b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/QueryPlans/PlanGenerationTestData/SimplePlanGenerationController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/QueryPlans/PlanGenerationTestData/SimplePlanGenerationController.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Execution.QueryPlans.PlanGenerationTestData using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphRoute("simple")] public class SimplePlanGenerationController : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/InputResolverGeneratorTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/InputResolverGeneratorTests.cs similarity index 96% rename from src/unit-tests/graphql-aspnet-tests/Internal/InputResolverGeneratorTests.cs rename to src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/InputResolverGeneratorTests.cs index 36130e4cc..ba106baf2 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/InputResolverGeneratorTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/InputResolverGeneratorTests.cs @@ -7,12 +7,11 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal +namespace GraphQL.AspNet.Tests.Execution.Resolvers { using System; using System.Collections; using System.Collections.Generic; - using GraphQL.AspNet.Engine; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Execution.Parsing; using GraphQL.AspNet.Execution.Parsing.Lexing; @@ -21,19 +20,18 @@ namespace GraphQL.AspNet.Tests.Internal using GraphQL.AspNet.Execution.Parsing.SyntaxNodes; using GraphQL.AspNet.Execution.QueryPlans.DocumentParts; using GraphQL.AspNet.Execution.QueryPlans.DocumentParts.SuppliedValues; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Execution.Source; using GraphQL.AspNet.Interfaces.Execution.QueryPlans.DocumentParts; using GraphQL.AspNet.Interfaces.Execution.QueryPlans.Resolvables; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.Resolvers; using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using GraphQL.AspNet.Tests.Execution.Resolvers.InputValueNodeTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Internal.InputValueNodeTestData; using NSubstitute; using NUnit.Framework; - using NUnit.Framework.Constraints; [TestFixture] public class InputResolverGeneratorTests @@ -47,12 +45,9 @@ private enum TestEnum private ISchema CreateSchema() { var builder = new TestServerBuilder(); - var defaultScalars = new DefaultScalarGraphTypeProvider(); - foreach (var scalarConcreteType in defaultScalars.ConcreteTypes) - { + foreach (var scalarConcreteType in GlobalTypes.ScalarInstanceTypes) builder.AddType(scalarConcreteType); - } builder.AddType(typeof(TestEnum)); builder.AddType(typeof(Telephone), TypeKind.INPUT_OBJECT); @@ -168,6 +163,7 @@ static InputResolverGeneratorTests() public void DefaultScalarValueResolvers(string expressionText, string inputText, object expectedOutput) { var owner = Substitute.For(); + owner.Parent.Returns(null as IDocumentPart); var generator = new InputValueResolverMethodGenerator(this.CreateSchema()); @@ -229,6 +225,7 @@ public void DefaultScalarValueResolvers_WithGermanCulture(string expressionText, public void DefaultScalarValueResolvers_InvalidInputValue(string expressionText, string inputText) { var owner = Substitute.For(); + owner.Parent.Returns(null as IDocumentPart); var generator = new InputValueResolverMethodGenerator(this.CreateSchema()); @@ -272,16 +269,14 @@ public void DefaultScalarValueResolvers_InvalidInputValue(string expressionText, public void BasicListValueResolver() { var listOwner = Substitute.For(); - listOwner.Parent.Returns(x => null); + listOwner.Parent.Returns(null as IDocumentPart); var sourceList = new DocumentListSuppliedValue(listOwner, SourceLocation.None); sourceList.Children.Add(new DocumentScalarSuppliedValue(sourceList, "15", ScalarValueType.Unknown, SourceLocation.None)); sourceList.Children.Add(new DocumentScalarSuppliedValue(sourceList, "12", ScalarValueType.Unknown, SourceLocation.None)); var typeExpression = GraphTypeExpression.FromDeclaration("[Int]"); - - var schema = this.CreateSchema(); - var generator = new InputValueResolverMethodGenerator(schema); + var generator = new InputValueResolverMethodGenerator(this.CreateSchema()); var resolver = generator.CreateResolver(typeExpression); var result = resolver.Resolve(sourceList) as IEnumerable; @@ -293,7 +288,7 @@ public void BasicListValueResolver() public void ListOfListValueResolver() { var listOwner = Substitute.For(); - listOwner.Parent.Returns(x => null); + listOwner.Parent.Returns(null as IDocumentPart); var outerList = new DocumentListSuppliedValue(listOwner, SourceLocation.None); var innerList1 = new DocumentListSuppliedValue(outerList, SourceLocation.None); @@ -323,7 +318,7 @@ public void InputObjectValueResolver_GeneratesObjectWhenPassed() var obj = schema.KnownTypes.FindGraphType("Input_Telephone") as IInputObjectGraphType; var owner = Substitute.For(); - owner.Parent.Returns(x => null); + owner.Parent.Returns(null as IDocumentPart); var complexObject = new DocumentComplexSuppliedValue(owner, SourceLocation.None); var idField = new DocumentInputObjectField(complexObject, "id", obj.Fields["id"], SourceLocation.None); @@ -355,7 +350,7 @@ public void InputObjectValueResolver_RequiredFieldNotSupplied_ExceptionThrown() var obj = schema.KnownTypes.FindGraphType("Input_Telephone") as IInputObjectGraphType; var owner = Substitute.For(); - owner.Parent.Returns(x => null); + owner.Parent.Returns(null as IDocumentPart); var complexObject = new DocumentComplexSuppliedValue(owner, SourceLocation.None); var brandField = new DocumentInputObjectField(complexObject, "brand", obj.Fields["brand"], SourceLocation.None); @@ -414,6 +409,7 @@ public void InputObjectValueResolver_ThrowsException_WhenNotPassedFieldSet() var schema = this.CreateSchema(); var owner = Substitute.For(); + owner.Parent.Returns(null as IDocumentPart); var idValue = new DocumentScalarSuppliedValue(owner, "15", ScalarValueType.String, SourceLocation.None); var typeExpression = GraphTypeExpression.FromDeclaration("Input_Telephone"); @@ -433,7 +429,7 @@ public void InputObjectValueResolver_WhenNullPasssedToNonNullableAndRequiredFiel var obj = schema.KnownTypes.FindGraphType("Input_Telephone") as IInputObjectGraphType; var owner = Substitute.For(); - owner.Parent.Returns(x => null); + owner.Parent.Returns(null as IDocumentPart); var path = new SourcePath(); path.AddFieldName("topfield"); diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/InputValueNodeTestData/CoffeeCan.cs b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/InputValueNodeTestData/CoffeeCan.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/InputValueNodeTestData/CoffeeCan.cs rename to src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/InputValueNodeTestData/CoffeeCan.cs index 7ccfc71a0..947ecfa0b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/InputValueNodeTestData/CoffeeCan.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/InputValueNodeTestData/CoffeeCan.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.InputValueNodeTestData +namespace GraphQL.AspNet.Tests.Execution.Resolvers.InputValueNodeTestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/InputValueNodeTestData/Telephone.cs b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/InputValueNodeTestData/Telephone.cs similarity index 88% rename from src/unit-tests/graphql-aspnet-tests/Internal/InputValueNodeTestData/Telephone.cs rename to src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/InputValueNodeTestData/Telephone.cs index 57edaf6f2..ea6e57ae3 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/InputValueNodeTestData/Telephone.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/InputValueNodeTestData/Telephone.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.InputValueNodeTestData +namespace GraphQL.AspNet.Tests.Execution.Resolvers.InputValueNodeTestData { using System.ComponentModel.DataAnnotations; using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/ObjectMethodResolverTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ObjectMethodResolverTests.cs similarity index 88% rename from src/unit-tests/graphql-aspnet-tests/Internal/ObjectMethodResolverTests.cs rename to src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ObjectMethodResolverTests.cs index 201a52eee..3b97191f8 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/ObjectMethodResolverTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ObjectMethodResolverTests.cs @@ -7,14 +7,15 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal +namespace GraphQL.AspNet.Tests.Execution.Resolvers { using System; using System.Threading.Tasks; - using GraphQL.AspNet.Internal.Resolvers; + using GraphQL.AspNet.Execution.Resolvers; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Execution.Resolvers.ValueResolversTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - using GraphQL.AspNet.Tests.Internal.ValueResolversTestData; using NUnit.Framework; [TestFixture] @@ -27,11 +28,11 @@ public async Task NullSourceData_FailsRequest() .AddType() .Build(); - var builder = server.CreateGraphTypeFieldContextBuilder( + var builder = server.CreateFieldContextBuilder( nameof(ResolverObject.MethodRetrieveData), null); - var resolver = new ObjectMethodGraphFieldResolver(builder.GraphMethod); + var resolver = new ObjectMethodGraphFieldResolver(builder.ResolverMetaData); var context = builder.CreateResolutionContext(); await resolver.ResolveAsync(context); @@ -48,13 +49,13 @@ public async Task SourceDataIsNotOfTheTemplate_FailsRequest() .AddType() .Build(); - var builder = server.CreateGraphTypeFieldContextBuilder( + var builder = server.CreateFieldContextBuilder( nameof(ResolverObject.MethodRetrieveData), new object()); // source data is not of the type the resolver is for builder.AddSourceData(new TwoPropertyObject()); - var resolver = new ObjectMethodGraphFieldResolver(builder.GraphMethod); + var resolver = new ObjectMethodGraphFieldResolver(builder.ResolverMetaData); var context = builder.CreateResolutionContext(); await resolver.ResolveAsync(context); @@ -70,13 +71,13 @@ public async Task MethodThrowsException_FailsRequest() .AddType() .Build(); - var builder = server.CreateGraphTypeFieldContextBuilder( + var builder = server.CreateFieldContextBuilder( nameof(ResolverObject.MethodThrowException), new object()); // source data is not of the type the resolver is for builder.AddSourceData(new ResolverObject()); - var resolver = new ObjectMethodGraphFieldResolver(builder.GraphMethod); + var resolver = new ObjectMethodGraphFieldResolver(builder.ResolverMetaData); var context = builder.CreateResolutionContext(); await resolver.ResolveAsync(context); @@ -94,7 +95,7 @@ public async Task KnownExecutionError_FailsRequest() .AddType() .Build(); - var builder = server.CreateGraphTypeFieldContextBuilder( + var builder = server.CreateFieldContextBuilder( nameof(ResolverObject.MethodWithArgument), new object()); @@ -104,7 +105,7 @@ public async Task KnownExecutionError_FailsRequest() // source data is not of the type the resolver is for builder.AddSourceData(new ResolverObject()); - var resolver = new ObjectMethodGraphFieldResolver(builder.GraphMethod); + var resolver = new ObjectMethodGraphFieldResolver(builder.ResolverMetaData); var context = builder.CreateResolutionContext(); await resolver.ResolveAsync(context); @@ -121,11 +122,11 @@ public async Task AsyncMethod_NullSourceData_FailsRequest() .AddType() .Build(); - var builder = server.CreateGraphTypeFieldContextBuilder( + var builder = server.CreateFieldContextBuilder( nameof(ResolverObject.MethodRetrieveDataAsync), null); - var resolver = new ObjectMethodGraphFieldResolver(builder.GraphMethod); + var resolver = new ObjectMethodGraphFieldResolver(builder.ResolverMetaData); var context = builder.CreateResolutionContext(); await resolver.ResolveAsync(context); @@ -142,13 +143,13 @@ public async Task AsyncMethod_SourceDataIsNotOfTheTemplate_FailsRequest() .AddType() .Build(); - var builder = server.CreateGraphTypeFieldContextBuilder( + var builder = server.CreateFieldContextBuilder( nameof(ResolverObject.MethodRetrieveDataAsync), new object()); // source data is not of the type the resolver is for builder.AddSourceData(new TwoPropertyObject()); - var resolver = new ObjectMethodGraphFieldResolver(builder.GraphMethod); + var resolver = new ObjectMethodGraphFieldResolver(builder.ResolverMetaData); var context = builder.CreateResolutionContext(); await resolver.ResolveAsync(context); @@ -164,13 +165,13 @@ public async Task AsyncMethod_MethodThrowsException_FailsRequest() .AddType() .Build(); - var builder = server.CreateGraphTypeFieldContextBuilder( + var builder = server.CreateFieldContextBuilder( nameof(ResolverObject.MethodThrowExceptionAsync), new object()); // source data is not of the type the resolver is for builder.AddSourceData(new ResolverObject()); - var resolver = new ObjectMethodGraphFieldResolver(builder.GraphMethod); + var resolver = new ObjectMethodGraphFieldResolver(builder.ResolverMetaData); var context = builder.CreateResolutionContext(); await resolver.ResolveAsync(context); @@ -188,7 +189,7 @@ public async Task AsyncMethod_KnownExecutionError_FailsRequest() .AddType() .Build(); - var builder = server.CreateGraphTypeFieldContextBuilder( + var builder = server.CreateFieldContextBuilder( nameof(ResolverObject.MethodWithArgumentAsync), new object()); @@ -198,7 +199,7 @@ public async Task AsyncMethod_KnownExecutionError_FailsRequest() // source data is not of the type the resolver is for builder.AddSourceData(new ResolverObject()); - var resolver = new ObjectMethodGraphFieldResolver(builder.GraphMethod); + var resolver = new ObjectMethodGraphFieldResolver(builder.ResolverMetaData); var context = builder.CreateResolutionContext(); await resolver.ResolveAsync(context); diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/ObjectPropertyResolverTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ObjectPropertyResolverTests.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/ObjectPropertyResolverTests.cs rename to src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ObjectPropertyResolverTests.cs index 26837d0e7..1bc2febca 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/ObjectPropertyResolverTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ObjectPropertyResolverTests.cs @@ -7,16 +7,17 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal +namespace GraphQL.AspNet.Tests.Execution.Resolvers { using System; using System.Threading.Tasks; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal.Resolvers; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Common.Extensions.DiExtensionTestData; + using GraphQL.AspNet.Tests.Execution.Resolvers.ValueResolversTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - using GraphQL.AspNet.Tests.Internal.ValueResolversTestData; using NSubstitute; using NUnit.Framework; @@ -30,14 +31,14 @@ public async Task NullSourceData_FailsRequest() .AddType() .Build(); - var fieldContextBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = server.CreateFieldContextBuilder( nameof(ResolverObject.Address1), null); fieldContextBuilder.AddSourceData(null); var resolutionContext = fieldContextBuilder.CreateResolutionContext(); - var resolver = new ObjectPropertyGraphFieldResolver(fieldContextBuilder.GraphMethod); + var resolver = new ObjectPropertyGraphFieldResolver(fieldContextBuilder.ResolverMetaData); await resolver.ResolveAsync(resolutionContext); Assert.AreEqual(null, resolutionContext.Result); @@ -53,7 +54,7 @@ public async Task TemplateIsInterface_SourceDataDoesImplementInterface_RendersCo .AddType() .Build(); - var fieldContextBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = server.CreateFieldContextBuilder( nameof(ResolverObject.Address1), null); @@ -67,8 +68,8 @@ public async Task TemplateIsInterface_SourceDataDoesImplementInterface_RendersCo var parentMock = Substitute.For(); parentMock.ObjectType.Returns(typeof(IResolverInterface)); - fieldContextBuilder.GraphMethod.Parent.Returns(parentMock); - var resolver = new ObjectPropertyGraphFieldResolver(fieldContextBuilder.GraphMethod); + fieldContextBuilder.ResolverMetaData.ParentObjectType.Returns(parentMock.ObjectType); + var resolver = new ObjectPropertyGraphFieldResolver(fieldContextBuilder.ResolverMetaData); var resolutionContext = fieldContextBuilder.CreateResolutionContext(); await resolver.ResolveAsync(resolutionContext); @@ -83,7 +84,7 @@ public async Task TemplateIsInterface_SourceDataDoesNotImplementInterface_FailsR .AddType() .Build(); - var fieldContextBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = server.CreateFieldContextBuilder( nameof(ResolverObject.Address1), null); @@ -94,9 +95,9 @@ public async Task TemplateIsInterface_SourceDataDoesNotImplementInterface_FailsR var parentMock = Substitute.For(); parentMock.ObjectType.Returns(typeof(ITestInterface)); - fieldContextBuilder.GraphMethod.Parent.Returns(parentMock); + fieldContextBuilder.ResolverMetaData.ParentObjectType.Returns(parentMock.ObjectType); - var resolver = new ObjectPropertyGraphFieldResolver(fieldContextBuilder.GraphMethod); + var resolver = new ObjectPropertyGraphFieldResolver(fieldContextBuilder.ResolverMetaData); var resolutionContext = fieldContextBuilder.CreateResolutionContext(); await resolver.ResolveAsync(resolutionContext); @@ -113,12 +114,12 @@ public async Task SourceDataIsNotOfTheTemplate_FailsRequest() .Build(); // resolving structA, but supplying structB as source - var fieldContextBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = server.CreateFieldContextBuilder( nameof(ResolverStructA.Prop1), new ResolverStructB("struct")); // source data is not of the type the resolver is for - var resolver = new ObjectPropertyGraphFieldResolver(fieldContextBuilder.GraphMethod); + var resolver = new ObjectPropertyGraphFieldResolver(fieldContextBuilder.ResolverMetaData); var resolutionContext = fieldContextBuilder.CreateResolutionContext(); await resolver.ResolveAsync(resolutionContext); @@ -134,13 +135,13 @@ public async Task PropertyThrowsException_FailsRequest() .AddType() .Build(); - var fieldContextBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = server.CreateFieldContextBuilder( nameof(ResolverObject.PropertyThrowException), new ResolverObject()); // source data is not of the type the resolver is for fieldContextBuilder.AddSourceData(new ResolverObject()); - var resolver = new ObjectPropertyGraphFieldResolver(fieldContextBuilder.GraphMethod); + var resolver = new ObjectPropertyGraphFieldResolver(fieldContextBuilder.ResolverMetaData); var resolutionContext = fieldContextBuilder.CreateResolutionContext(); await resolver.ResolveAsync(resolutionContext); @@ -158,13 +159,13 @@ public async Task AsyncProperty_ValidSourceData_ReturnsData() .AddType() .Build(); - var fieldContextBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = server.CreateFieldContextBuilder( nameof(ResolverObject.Address1Async), new ResolverObject()); var resolutionContext = fieldContextBuilder.CreateResolutionContext(); - var resolver = new ObjectPropertyGraphFieldResolver(fieldContextBuilder.GraphMethod); + var resolver = new ObjectPropertyGraphFieldResolver(fieldContextBuilder.ResolverMetaData); await resolver.ResolveAsync(resolutionContext); Assert.IsNotNull(resolutionContext.Result); @@ -179,13 +180,13 @@ public async Task AsyncProperty_ThrowsException_FailsRequest() .AddType() .Build(); - var fieldContextBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldContextBuilder = server.CreateFieldContextBuilder( nameof(ResolverObject.AsyncPropException), new ResolverObject()); // source data is not of the type the resolver is for fieldContextBuilder.AddSourceData(new ResolverObject()); - var resolver = new ObjectPropertyGraphFieldResolver(fieldContextBuilder.GraphMethod); + var resolver = new ObjectPropertyGraphFieldResolver(fieldContextBuilder.ResolverMetaData); var resolutionContext = fieldContextBuilder.CreateResolutionContext(); await resolver.ResolveAsync(resolutionContext); diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/ValueResolversTestData/IResolverInterface.cs b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ValueResolversTestData/IResolverInterface.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Internal/ValueResolversTestData/IResolverInterface.cs rename to src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ValueResolversTestData/IResolverInterface.cs index a1c6b234b..94ea1b717 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/ValueResolversTestData/IResolverInterface.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ValueResolversTestData/IResolverInterface.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.ValueResolversTestData +namespace GraphQL.AspNet.Tests.Execution.Resolvers.ValueResolversTestData { public interface IResolverInterface { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/ValueResolversTestData/ResolvableEnum.cs b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ValueResolversTestData/ResolvableEnum.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Internal/ValueResolversTestData/ResolvableEnum.cs rename to src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ValueResolversTestData/ResolvableEnum.cs index b6ecd0d1d..8204c1849 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/ValueResolversTestData/ResolvableEnum.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ValueResolversTestData/ResolvableEnum.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.ValueResolversTestData +namespace GraphQL.AspNet.Tests.Execution.Resolvers.ValueResolversTestData { public enum ResolvableEnum { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/ValueResolversTestData/ResolverObject.cs b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ValueResolversTestData/ResolverObject.cs similarity index 96% rename from src/unit-tests/graphql-aspnet-tests/Internal/ValueResolversTestData/ResolverObject.cs rename to src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ValueResolversTestData/ResolverObject.cs index a4c817a92..f9e395c9a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/ValueResolversTestData/ResolverObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ValueResolversTestData/ResolverObject.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.ValueResolversTestData +namespace GraphQL.AspNet.Tests.Execution.Resolvers.ValueResolversTestData { using System; using System.Threading.Tasks; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/ValueResolversTestData/ResolverStructA.cs b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ValueResolversTestData/ResolverStructA.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/ValueResolversTestData/ResolverStructA.cs rename to src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ValueResolversTestData/ResolverStructA.cs index 74466ac68..e0eb949f5 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/ValueResolversTestData/ResolverStructA.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ValueResolversTestData/ResolverStructA.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.ValueResolversTestData +namespace GraphQL.AspNet.Tests.Execution.Resolvers.ValueResolversTestData { public struct ResolverStructA { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/ValueResolversTestData/ResolverStructB.cs b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ValueResolversTestData/ResolverStructB.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/ValueResolversTestData/ResolverStructB.cs rename to src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ValueResolversTestData/ResolverStructB.cs index 0133dc0e1..0e6149dfc 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/ValueResolversTestData/ResolverStructB.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/Resolvers/ValueResolversTestData/ResolverStructB.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.ValueResolversTestData +namespace GraphQL.AspNet.Tests.Execution.Resolvers.ValueResolversTestData { public struct ResolverStructB { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/Response/ResponseWriterTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/Response/ResponseWriterTests.cs index ae9f3e551..fe342a0d0 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/Response/ResponseWriterTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/Response/ResponseWriterTests.cs @@ -20,7 +20,7 @@ namespace GraphQL.AspNet.Tests.Execution.Response using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Execution.Response; using GraphQL.AspNet.Schemas; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using NSubstitute; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/Response/ResponseWriterTests_NET60.cs b/src/unit-tests/graphql-aspnet-tests/Execution/Response/ResponseWriterTests_NET60.cs index a65911384..2cd0750d2 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/Response/ResponseWriterTests_NET60.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/Response/ResponseWriterTests_NET60.cs @@ -21,7 +21,7 @@ namespace GraphQL.AspNet.Tests.Execution.Response using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Execution.Response; using GraphQL.AspNet.Schemas; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using NSubstitute; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/RulesEngine/DirectiveValidationRuleCheckTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/RulesEngine/DirectiveValidationRuleCheckTests.cs index 95f9a3379..4bc20733a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/RulesEngine/DirectiveValidationRuleCheckTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/RulesEngine/DirectiveValidationRuleCheckTests.cs @@ -15,9 +15,9 @@ namespace GraphQL.AspNet.Tests.Execution.RulesEngine using GraphQL.AspNet.Execution.RulesEngine; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.RulesEngine.DirectiveTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NSubstitute; using NUnit.Framework; @@ -33,9 +33,11 @@ public void UnknownLocation_FailsValidation() var obj = Substitute.For(); - var context = server.CreateDirectiveExecutionContext( + var context = server.CreateDirectiveContextBuilder( DirectiveLocation.NONE, - obj); + obj) + + .CreateExecutionContext(); var ruleSet = new DirectiveValidationRuleProcessor(); var complete = ruleSet.Execute(context.AsEnumerable()); @@ -53,10 +55,11 @@ public void UnknownPhase_FailsValidation() .Build(); var obj = Substitute.For(); - var context = server.CreateDirectiveExecutionContext( + var context = server.CreateDirectiveContextBuilder( DirectiveLocation.OBJECT, obj, - DirectiveInvocationPhase.Unknown); + DirectiveInvocationPhase.Unknown) + .CreateExecutionContext(); var ruleSet = new DirectiveValidationRuleProcessor(); var complete = ruleSet.Execute(context.AsEnumerable()); @@ -74,10 +77,11 @@ public void LocationMisMatch_FailsValidation() .Build(); var obj = Substitute.For(); - var context = server.CreateDirectiveExecutionContext( + var context = server.CreateDirectiveContextBuilder( DirectiveLocation.FIELD, obj, - DirectiveInvocationPhase.SchemaGeneration); + DirectiveInvocationPhase.SchemaGeneration) + .CreateExecutionContext(); var ruleSet = new DirectiveValidationRuleProcessor(); var complete = ruleSet.Execute(context.AsEnumerable()); @@ -96,10 +100,11 @@ public void NotADirective_FailsValidation() .Build(); var obj = Substitute.For(); - var context = server.CreateDirectiveExecutionContext( + var context = server.CreateDirectiveContextBuilder( DirectiveLocation.OBJECT, obj, - DirectiveInvocationPhase.SchemaGeneration); + DirectiveInvocationPhase.SchemaGeneration) + .CreateExecutionContext(); var ruleSet = new DirectiveValidationRuleProcessor(); var complete = ruleSet.Execute(context.AsEnumerable()); @@ -118,12 +123,13 @@ public void ValidateRequest_PassedValidation() .Build(); var obj = Substitute.For(); - var context = server.CreateDirectiveExecutionContext( + var context = server.CreateDirectiveContextBuilder( DirectiveLocation.OBJECT, obj, DirectiveInvocationPhase.SchemaGeneration, SourceOrigin.None, - new object[] { 5, "someValue" }); + new object[] { 5, "someValue" }) + .CreateExecutionContext(); var ruleSet = new DirectiveValidationRuleProcessor(); var complete = ruleSet.Execute(context.AsEnumerable()); @@ -142,12 +148,13 @@ public void IncorrectNumberOfArguments_FailsValidation() var obj = Substitute.For(); - var context = server.CreateDirectiveExecutionContext( + var context = server.CreateDirectiveContextBuilder( DirectiveLocation.OBJECT, obj, DirectiveInvocationPhase.SchemaGeneration, SourceOrigin.None, - new object[] { 5 }); // directive requires 2 argument, only 1 supplied + new object[] { 5 }) // directive requires 2 argument, only 1 supplied + .CreateExecutionContext(); var ruleSet = new DirectiveValidationRuleProcessor(); var complete = ruleSet.Execute(context.AsEnumerable()); @@ -167,12 +174,13 @@ public void InvalidArgument_FailsValidation() var obj = Substitute.For(); - var context = server.CreateDirectiveExecutionContext( + var context = server.CreateDirectiveContextBuilder( DirectiveLocation.OBJECT, obj, DirectiveInvocationPhase.SchemaGeneration, SourceOrigin.None, - new object[] { "notAInt", "validString" }); // arg 1 should be an int + new object[] { "notAInt", "validString" }) // arg 1 should be an int + .CreateExecutionContext(); var ruleSet = new DirectiveValidationRuleProcessor(); var complete = ruleSet.Execute(context.AsEnumerable()); diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/RulesEngine/EnsureValidationRuleUrls.cs b/src/unit-tests/graphql-aspnet-tests/Execution/RulesEngine/EnsureValidationRuleUrls.cs index 08f0ea3d4..574ba2e57 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/RulesEngine/EnsureValidationRuleUrls.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/RulesEngine/EnsureValidationRuleUrls.cs @@ -16,6 +16,8 @@ namespace GraphQL.AspNet.Tests.Execution.RulesEngine using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Generics; using GraphQL.AspNet.Interfaces.Execution.RulesEngine; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; using NUnit.Framework; [TestFixture] @@ -27,7 +29,7 @@ public void EnsureAllUrlsUniquePerRule() // for all links to specification rules ensure that // each rule number has a valid link and that it is // unique - var ruleTypes = typeof(GraphQLProviders) + var ruleTypes = typeof(GlobalTypes) .Assembly .GetTypes() .Where(Validation.IsCastable) diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/RuntimeDirectiveTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/RuntimeDirectiveTests.cs new file mode 100644 index 000000000..49e092f5c --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/RuntimeDirectiveTests.cs @@ -0,0 +1,406 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution +{ + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations.Schema; + using System.Threading.Tasks; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Controllers.ActionResults; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Execution.Contexts; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Execution.TestData.RuntimeDirectiveTestData; + using GraphQL.AspNet.Tests.Execution.TestData.RuntimeFieldTestData; + using GraphQL.AspNet.Tests.Framework; + using Microsoft.Extensions.DependencyInjection; + using NUnit.Framework; + + [TestFixture] + public class RuntimeDirectiveTests + { + private static Dictionary _values = new Dictionary(); + + [Test] + public void Runtime_TypeSystemDirective_IsInvokedCorrectly() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.AddType(); + o.MapDirective("@myObjectDirective") + .RestrictLocations(DirectiveLocation.OBJECT) + .AddResolver((int a, int b) => + { + _values["generalTypeSystemDirective"] = a + b; + return GraphActionResult.Ok(); + }); + + o.ApplyDirective("myObjectDirective") + .ToItems(x => x.IsObjectGraphType()) + .WithArguments(5, 18); + }); + + var server = serverBuilder.Build(); + Assert.AreEqual(23, _values["generalTypeSystemDirective"]); + } + + [Test] + public void Runtime_TypeSystemDirective_InjectedService_IsInvokedCorrectly() + { + var serverBuilder = new TestServerBuilder(); + serverBuilder.AddTransient(); + + serverBuilder.AddGraphQL(o => + { + o.AddType(); + o.MapDirective("@myObjectDirective") + .RestrictLocations(DirectiveLocation.OBJECT) + .AddResolver((int a, IInjectedService service) => + { + _values["injectedService"] = a + service.FetchValue(); + return GraphActionResult.Ok(); + }); + + o.ApplyDirective("myObjectDirective") + .ToItems(x => x.IsObjectGraphType()) + .WithArguments(5); + }); + + var server = serverBuilder.Build(); + + // injected services supplies 23 + Assert.AreEqual(28, _values["injectedService"]); + } + + [Test] + public void Runtime_TypeSystemDirective_InterfaceAsExplicitSchemaItem_ThrowsException() + { + var serverBuilder = new TestServerBuilder(); + serverBuilder.AddTransient(); + + serverBuilder.AddGraphQL(o => + { + o.AddType(); + o.MapDirective("@myObjectDirective") + .RestrictLocations(DirectiveLocation.OBJECT) + .AddResolver((int a, [FromGraphQL] IInjectedService service) => + { + _values["injectedServiceWrong"] = a + service.FetchValue(); + return GraphActionResult.Ok(); + }); + + o.ApplyDirective("myObjectDirective") + .ToItems(x => x.IsObjectGraphType()) + .WithArguments(5); + }); + + Assert.Throws(() => + { + var server = serverBuilder.Build(); + }); + } + + [Test] + public async Task Runtime_ExecutionDirective_IsInvokedCorrectly() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.AddController(); + o.MapDirective("@myFieldDirective") + .RestrictLocations(DirectiveLocation.FIELD) + .AddResolver(() => + { + _values["fieldDirective"] = 11; + return GraphActionResult.Ok(); + }); + }); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field @myFieldDirective }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field"": 99 + } + }", + result); + + Assert.AreEqual(11, _values["fieldDirective"]); + } + + [Test] + public async Task Runtime_ExecutionDirective_OnMinimalApiField_IsInvokedCorrectly() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("field", () => 3); + + o.MapDirective("@myFieldDirective") + .RestrictLocations(DirectiveLocation.FIELD) + .AddResolver(() => + { + _values["fieldDirective"] = 11; + return GraphActionResult.Ok(); + }); + }); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field @myFieldDirective }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field"": 3 + } + }", + result); + + Assert.AreEqual(11, _values["fieldDirective"]); + } + + [Test] + public async Task Runtime_ExecutionDirective_WithSecurityParams_AndAllowedUser_ResolvesCorrectly() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("field", () => 3); + + o.MapDirective("@secureDirective") + .RestrictLocations(DirectiveLocation.FIELD) + .RequireAuthorization("policy1") + .AddResolver(() => + { + _values["secureDirective1"] = 11; + return GraphActionResult.Ok(); + }); + }); + + serverBuilder.Authorization.AddClaimPolicy("policy1", "policy1Claim", "policy1Value"); + serverBuilder.UserContext + .Authenticate() + .AddUserClaim("policy1Claim", "policy1Value"); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field @secureDirective }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field"": 3 + } + }", + result); + + Assert.AreEqual(11, _values["secureDirective1"]); + } + + [Test] + public async Task Runtime_ExecutionDirective_WithSecurityParams_AndUnAuthenticatedUser_RendersAccessDenied() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("field", () => 3); + + o.MapDirective("@secureDirective") + .RestrictLocations(DirectiveLocation.FIELD) + .RequireAuthorization("policy1") + .AddResolver(() => + { + _values["secureDirective2"] = 11; + return GraphActionResult.Ok(); + }); + }); + + // no user authentication added + serverBuilder.Authorization.AddClaimPolicy("policy1", "policy1Claim", "policy1Value"); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field @secureDirective }"); + + var result = await server.ExecuteQuery(builder); + Assert.IsFalse(_values.ContainsKey("secureDirective2")); + Assert.AreEqual(1, result.Messages.Count); + Assert.AreEqual(GraphMessageSeverity.Critical, result.Messages[0].Severity); + Assert.AreEqual(Constants.ErrorCodes.ACCESS_DENIED, result.Messages[0].Code); + } + + [Test] + public async Task Runtime_ExecutionDirective_WithSecurityParams_AndUnAuthorizedUser_RendersAccessDenied() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("field", () => 3); + + o.MapDirective("@secureDirective") + .RestrictLocations(DirectiveLocation.FIELD) + .RequireAuthorization("policy1") + .AddResolver(() => + { + _values["secureDirective3"] = 11; + return GraphActionResult.Ok(); + }); + }); + + // wrong policy value + serverBuilder.Authorization.AddClaimPolicy("policy1", "policy1Claim", "policy1Value"); + serverBuilder.UserContext + .Authenticate() + .AddUserClaim("policy1Claim", "policy2Value"); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field @secureDirective }"); + + var result = await server.ExecuteQuery(builder); + Assert.IsFalse(_values.ContainsKey("secureDirective3")); + Assert.AreEqual(1, result.Messages.Count); + Assert.AreEqual(GraphMessageSeverity.Critical, result.Messages[0].Severity); + Assert.AreEqual(Constants.ErrorCodes.ACCESS_DENIED, result.Messages[0].Code); + } + + [Test] + public async Task Runtime_ExecutionDirective_WithDirectiveContext_SuppliesContextToDirectiveCorrect() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("field", () => 3); + + o.MapDirective("@injectedContext") + .RestrictLocations(DirectiveLocation.FIELD) + .AddResolver((DirectiveResolutionContext context) => + { + if (context != null) + _values["directiveResolutionContext0"] = 1; + + return GraphActionResult.Ok(); + }); + }); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field @injectedContext }"); + + var result = await server.RenderResult(builder); + + Assert.IsTrue(_values.ContainsKey("directiveResolutionContext0")); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field"": 3 + } + }", + result); + } + + [Test] + public async Task Runtime_ExecutionDirective_WithFieldResolutionContext_ThrowsException() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("field", () => 3); + + o.MapDirective("@injectedContext") + .RestrictLocations(DirectiveLocation.FIELD) + .AddResolver((FieldResolutionContext context) => + { + if (context != null) + _values["directiveResolutionContext1"] = 1; + + return GraphActionResult.Ok(); + }); + }); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field @injectedContext }"); + + var result = await server.ExecuteQuery(builder); + + Assert.IsFalse(_values.ContainsKey("directiveResolutionContext1")); + + Assert.AreEqual(1, result.Messages.Count); + Assert.AreEqual(Constants.ErrorCodes.INTERNAL_SERVER_ERROR, result.Messages[0].Code); + Assert.AreEqual(typeof(GraphExecutionException), result.Messages[0].Exception.GetType()); + + Assert.IsTrue(result.Messages[0].Exception.Message.Contains(nameof(FieldResolutionContext))); + } + + [Test] + public async Task Runtime_ExecutionDirective_WithMultipleDirectiveContext_SuppliesContextToDirectiveCorrect() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("field", () => 3); + + o.MapDirective("@injectedContext") + .RestrictLocations(DirectiveLocation.FIELD) + .AddResolver((DirectiveResolutionContext context, DirectiveResolutionContext context1) => + { + if (context != null && context == context1) + _values["directiveResolutionContext2"] = 1; + + return GraphActionResult.Ok(); + }); + }); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field @injectedContext }"); + + var result = await server.RenderResult(builder); + + Assert.IsTrue(_values.ContainsKey("directiveResolutionContext2")); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field"": 3 + } + }", + result); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/RuntimeFieldTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/RuntimeFieldTests.cs new file mode 100644 index 000000000..6eb17d651 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/RuntimeFieldTests.cs @@ -0,0 +1,742 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Controllers.ActionResults; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Execution.Contexts; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Web; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Execution.TestData.RuntimeFieldTest; + using GraphQL.AspNet.Tests.Execution.TestData.RuntimeFieldTestData; + using GraphQL.AspNet.Tests.Framework; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.DependencyInjection; + using NUnit.Framework; + + [TestFixture] + public class RuntimeFieldTests + { + [Test] + public async Task BasicMappedQuery_ExecutesMethod() + { + var serverBuilder = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapQuery("/field1/field2", (int a, int b) => + { + return a + b; + }); + }); + + var server = serverBuilder.Build(); + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field1 { field2(a: 5, b: 33 } } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field1"": { + ""field2"" : 38 + } + } + }", + result); + } + + [Test] + public async Task BasicMappedQuery_AsAsyncMethod_ExecutesMethod() + { + var serverBuilder = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapQuery("/field1/field2", async (int a, int b) => + { + await Task.Yield(); + return a + b; + }); + }); + + var server = serverBuilder.Build(); + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field1 { field2(a: 5, b: 33 } } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field1"": { + ""field2"" : 38 + } + } + }", + result); + } + + [Test] + public async Task BasicMappedQuery_AddingResolverAfter_ExecutesMethod() + { + var serverBuilder = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapQuery("/field1/field2") + .AddResolver((int a, int b) => + { + return a + b; + }); + }); + + var server = serverBuilder.Build(); + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field1 { field2(a: 5, b: 33 } } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field1"": { + ""field2"" : 38 + } + } + }", + result); + } + + [Test] + public async Task MappedQuery_ViaGroup_ExecutesMethod() + { + var serverBuilder = new TestServerBuilder() + .AddGraphQL(o => + { + var group = o.MapQueryGroup("/field1"); + group.MapField("/field2", (int a, int b) => + { + return a + b; + }); + }); + + var server = serverBuilder.Build(); + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field1 { field2(a: 5, b: 33 } } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field1"": { + ""field2"" : 38 + } + } + }", + result); + } + + [Test] + public async Task BasicMappedMutation_ExecutesMethod() + { + var serverBuilder = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapMutation("/field1/field2", (int a, int b) => + { + return a + b; + }); + }); + + var server = serverBuilder.Build(); + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"mutation { field1 { field2(a: 4, b: 33 } } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field1"": { + ""field2"" : 37 + } + } + }", + result); + } + + [Test] + public async Task BasicMappedMutation_AddingResolverAfter_ExecutesMethod() + { + var serverBuilder = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapMutation("/field1/field2") + .AddResolver((int a, int b) => + { + return a + b; + }); + }); + + var server = serverBuilder.Build(); + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"mutation { field1 { field2(a: 4, b: 33 } } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field1"": { + ""field2"" : 37 + } + } + }", + result); + } + + [Test] + public async Task StaticMethodMappedDelegate_ThrowsValidationException() + { + var serverBuilder = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapQuery("/field1/field2", SampleDelegatesForMinimalApi.StaticMethod); + }); + + var server = serverBuilder.Build(); + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field1 { field2(a: 4, b: 37 } } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field1"": { + ""field2"" : 41 + } + } + }", + result); + } + + [Test] + public async Task InstanceMethodMappedDelegate_ExecutesMethod() + { + var data = new SampleDelegatesForMinimalApi(); + var serverBuilder = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapQuery("/field1/field2", data.InstanceMethod); + }); + + var server = serverBuilder.Build(); + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field1 { field2(a: 4, b: 37 } } }"); + + // supply no values, allowing the defaults to take overand returning the single + // requested "Property1" with the default string defined on the method. + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field1"": { + ""field2"" : 41 + } + } + }", + result); + } + + [Test] + public async Task BasicMappedQuery_ReturningActionResult_ResolvesCorrectly() + { + var serverBuilder = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapQuery("/field1/field2") + .AddResolver((int a, int b) => + { + return GraphActionResult.Ok(a + b); + }); + }); + + var server = serverBuilder.Build(); + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field1 { field2(a: 5, b: 33 } } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field1"": { + ""field2"" : 38 + } + } + }", + result); + } + + [Test] + public async Task BasicMappedQuery_WithExplicitlyDeclaredInjectedService_ReturningValueResult_ResolvesCorrectly() + { + var serverBuilder = new TestServerBuilder(); + serverBuilder.AddTransient(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("/field1/field2") + .AddResolver((int a, int b, [FromServices] IInjectedService injectedService) => + { + // injected srvice returns 23 + return a + b + injectedService.FetchValue(); + }); + }); + + var server = serverBuilder.Build(); + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field1 { field2(a: 6, b: 10 } } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field1"": { + ""field2"" : 39 + } + } + }", + result); + } + + [Test] + public async Task BasicMappedQuery_WithExplicitlyDeclaredInjectedService_ReturningActionResult_ResolvesCorrectly() + { + var serverBuilder = new TestServerBuilder(); + serverBuilder.AddTransient(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("/field1/field2") + .AddResolver((int a, int b, [FromServices] IInjectedService injectedService) => + { + // injected srvice returns 23 + return GraphActionResult.Ok(a + b + injectedService.FetchValue()); + }); + }); + + var server = serverBuilder.Build(); + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field1 { field2(a: 5, b: 33 } } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field1"": { + ""field2"" : 61 + } + } + }", + result); + } + + [Test] + public async Task BasicMappedQuery_WithImplicitlyDeclaredInjectedService_ReturningValueResult_ResolvesCorrectly() + { + var serverBuilder = new TestServerBuilder(); + serverBuilder.AddTransient(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("/field1/field2") + .AddResolver((int a, int b, IInjectedService injectedService) => + { + // injected srvice returns 23 + return a + b + injectedService.FetchValue(); + }); + }); + + var server = serverBuilder.Build(); + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field1 { field2(a: 6, b: 10 } } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field1"": { + ""field2"" : 39 + } + } + }", + result); + } + + [Test] + public async Task BasicMappedQuery_WithImplicitlyDeclaredInjectedService_ReturningActionResult_ResolvesCorrectly() + { + var serverBuilder = new TestServerBuilder(); + serverBuilder.AddTransient(); + + serverBuilder.AddGraphQL(o => + { + o.DeclarationOptions.ArgumentBindingRule = SchemaArgumentBindingRules.ParametersPreferQueryResolution; + + o.MapQuery("/field1/field2") + .AddResolver((int a, int b, IInjectedService injectedService) => + { + // injected srvice returns 23 + return GraphActionResult.Ok(a + b + injectedService.FetchValue()); + }); + }); + + var server = serverBuilder.Build(); + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field1 { field2(a: 5, b: 33 } } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field1"": { + ""field2"" : 61 + } + } + }", + result); + } + + [Test] + public async Task ServiceInjectedOnControllerAction_ResolvesCorrectly() + { + var serverBuilder = new TestServerBuilder(); + serverBuilder.AddTransient(); + serverBuilder.AddController(); + + var server = serverBuilder.Build(); + var builder = server.CreateQueryContextBuilder(); + + // injected service will supply 23 + builder.AddQueryText(@"query { add(arg1: 5) }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""add"" : 28 + } + }", + result); + } + + [Test] + public async Task Runtime_StandardField_WithSecurityParams_AndAllowedUser_ResolvesCorrectly() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("secureField", () => 3) + .RequireAuthorization("policy1"); + }); + + serverBuilder.Authorization.AddClaimPolicy("policy1", "policy1Claim", "policy1Value"); + serverBuilder.UserContext + .Authenticate() + .AddUserClaim("policy1Claim", "policy1Value"); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { secureField }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""secureField"": 3 + } + }", + result); + } + + [Test] + public async Task Runtime_StandardField_WithSecurityParams_AndUnAuthenticatedUser_RendersAccessDenied() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("secureField", () => 3) + .RequireAuthorization("policy1"); + }); + + // no user authentication added + serverBuilder.Authorization.AddClaimPolicy("policy1", "policy1Claim", "policy1Value"); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { secureField }"); + + var result = await server.ExecuteQuery(builder); + Assert.AreEqual(1, result.Messages.Count); + Assert.AreEqual(GraphMessageSeverity.Critical, result.Messages[0].Severity); + Assert.AreEqual(Constants.ErrorCodes.ACCESS_DENIED, result.Messages[0].Code); + } + + [Test] + public async Task Runtime_StandardField_WithSecurityParams_AndUnAuthorizedUser_RendersAccessDenied() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("secureField", () => 3) + .RequireAuthorization("policy1"); + }); + + // wrong claim value on user + serverBuilder.Authorization.AddClaimPolicy("policy1", "policy1Claim", "policy1Value"); + serverBuilder.UserContext + .Authenticate() + .AddUserClaim("policy1Claim", "policy2Value"); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { secureField }"); + + var result = await server.ExecuteQuery(builder); + Assert.AreEqual(1, result.Messages.Count); + Assert.AreEqual(GraphMessageSeverity.Critical, result.Messages[0].Severity); + Assert.AreEqual(Constants.ErrorCodes.ACCESS_DENIED, result.Messages[0].Code); + } + + [Test] + public async Task Runtime_StandardField_ReturnsNullableT_RendersValue() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("field", () => new int?(3)); + }); + + var server = serverBuilder.Build(); + + var field = server.Schema.Operations[GraphOperationType.Query].Fields.Single(x => x.Name == "field"); + Assert.AreEqual(typeof(int), field.ObjectType); + Assert.AreEqual("Int", field.TypeExpression.ToString()); // no !, not required + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field"": 3 + } + }", + result); + } + + [Test] + public async Task Runtime_StandardField_ReturnsNullableT_RendersNull() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("field", () => + { + int? value = null; + return value; + }); + }); + + var server = serverBuilder.Build(); + + var field = server.Schema.Operations[GraphOperationType.Query].Fields.Single(x => x.Name == "field"); + Assert.AreEqual(typeof(int), field.ObjectType); + Assert.AreEqual("Int", field.TypeExpression.ToString()); // no !, not required + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field"": null + } + }", + result); + } + + [Test] + public async Task Runtime_StandardField_ReturnsNullableT_ThroughActionResult() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("field", () => + { + return GraphActionResult.Ok(3); + }) + .AddPossibleTypes(typeof(int?)); + }); + + var server = serverBuilder.Build(); + + var field = server.Schema.Operations[GraphOperationType.Query].Fields.Single(x => x.Name == "field"); + Assert.AreEqual(typeof(int), field.ObjectType); + Assert.AreEqual("Int", field.TypeExpression.ToString()); // no !, not required + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field"": 3 + } + }", + result); + } + + [Test] + public async Task Runtime_StandardField_FieldResolutionContext_IsInjected_WhenRequested() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("field", (FieldResolutionContext context) => + { + if (context?.Request?.Field != null && context.Request.Field.Name == "field") + return 1; + + return 0; + }); + }); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field"": 1 + } + }", + result); + } + + [Test] + public async Task Runtime_StandardField_FieldResolutionContext_WhenSuppliedMultipleTimes_IsInjected_WhenRequested() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.MapQuery("field", (FieldResolutionContext context, FieldResolutionContext context1) => + { + if (context != null && context == context1) + return 1; + + return 0; + }); + }); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field"": 1 + } + }", + result); + } + + [Test] + public async Task Runtime_StandardField_SupplyingADirectiveResolutionContext_WithoutDefaultValue_ThrowsException() + { + var serverBuilder = new TestServerBuilder(TestOptions.IncludeExceptions); + + // TODO: Read https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/lambda-method-group-defaults + serverBuilder.AddGraphQL(o => + { + o.MapQuery("field", (DirectiveResolutionContext context) => + { + return 0; + }); + }); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field }"); + + var result = await server.ExecuteQuery(builder); + Assert.AreEqual(1, result.Messages.Count); + Assert.AreEqual(Constants.ErrorCodes.INTERNAL_SERVER_ERROR, result.Messages[0].Code); + Assert.AreEqual(typeof(GraphExecutionException), result.Messages[0].Exception.GetType()); + + Assert.IsTrue(result.Messages[0].Exception.Message.Contains(nameof(DirectiveResolutionContext))); + } + + [Test] + public async Task Runtime_StandardField_HttpContext_WhenNotSupplied_ReturnsNull_WhenRulesSetToNullableItems() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.ExecutionOptions.ResolverParameterResolutionRule = ResolverParameterResolutionRules.UseNullorDefault; + + o.MapQuery("field", (HttpContext context) => + { + if (context != null) + return 1; + + return 0; + }); + }); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field"": 0 + } + }", + result); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/RuntimeTypeExtensionTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/RuntimeTypeExtensionTests.cs new file mode 100644 index 000000000..da69f9c59 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/RuntimeTypeExtensionTests.cs @@ -0,0 +1,406 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Controllers.ActionResults; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Execution.TestData.RuntimeTypeExtensionTestData; + using GraphQL.AspNet.Tests.Framework; + using Microsoft.AspNetCore.Hosting.Server; + using Newtonsoft.Json.Linq; + using NUnit.Framework; + + [TestFixture] + public class RuntimeTypeExtensionTests + { + [Test] + public async Task TypeExtension_OfObject_ResolvesDuringExecution() + { + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddController(); + + o.MapTypeExtension("Property3", (TwoPropertyObject source) => + { + return $"{source.Property1}-{source.Property2}"; + }); + }) + .Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { retrieveObject { property1 property2 property3 } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""retrieveObject"": { + ""property1"" : ""Prop1"", + ""property2"" : 101, + ""property3"" : ""Prop1-101"" + } + } + }", + result); + } + + [Test] + public void TypeExtension_OfObject_WithMetaNameField_ThrowsDeclarationException() + { + var builder = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddController(); + + o.MapTypeExtension("[Action]", (TwoPropertyObject source) => + { + return $"{source.Property1}-{source.Property2}"; + }); + }); + + var ex = Assert.Throws(() => + { + builder.Build(); + }); + + // ensure the field name is in the message + Assert.IsTrue(ex.Message.Contains("[Action]")); + } + + [Test] + public async Task TypeExtension_NoSourceParameterDeclared_ResolvesCorrectly() + { + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddController(); + + o.MapTypeExtension("property5", (int argument) => + { + return argument; + }); + }).Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { retrieveObject { property1 property2 property5(argument: 37) } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""retrieveObject"": { + ""property1"" : ""Prop1"", + ""property2"" : 101, + ""property5"" : 37 + } + } + }", + result); + } + + [Test] + public async Task TypeExtension_OfStruct_ResolvesDuringExecution() + { + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddController(); + + o.MapTypeExtension("Property3", (TwoPropertyStruct source) => + { + return $"{source.Property1}-{source.Property2}"; + }); + }) + .Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { retrieveStruct { property1 property2 property3 } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""retrieveStruct"": { + ""property1"" : ""Prop1"", + ""property2"" : 101, + ""property3"" : ""Prop1-101"" + } + } + }", + result); + } + + [Test] + public async Task BatchExtension_OfObject_ResolvesDuringExecution() + { + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddController(); + + o.MapTypeExtension("Property4") + .WithBatchProcessing() + .AddResolver((IEnumerable source) => + { + // leaf property dictionary + var dic = new Dictionary(); + foreach (var item in source) + { + dic.Add(item, $"{item.Property1}-{item.Property2}-Batched"); + } + + return dic; + }); + }) + .Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { retrieveObjects { property1 property2 property4 } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""retrieveObjects"": [{ + ""property1"" : ""Prop1A"", + ""property2"" : 101, + ""property4"" : ""Prop1A-101-Batched"" + },{ + ""property1"" : ""Prop1B"", + ""property2"" : 102, + ""property4"" : ""Prop1B-102-Batched"" + },{ + ""property1"" : ""Prop1C"", + ""property2"" : 103, + ""property4"" : ""Prop1C-103-Batched"" + },] + } + }", + result); + } + + [Test] + public async Task BatchExtension_OfStructs_ResolvesDuringExecution() + { + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddController(); + + o.MapTypeExtension("Property4") + .WithBatchProcessing() + .AddResolver((IEnumerable source) => + { + var dic = new Dictionary(); + foreach (var item in source) + { + dic.Add(item, $"{item.Property1}-{item.Property2}-Batched"); + } + + return dic; + }); + }) + .Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { retrieveStructs { property1 property2 property4 } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""retrieveStructs"": [{ + ""property1"" : ""Prop1A"", + ""property2"" : 101, + ""property4"" : ""Prop1A-101-Batched"" + },{ + ""property1"" : ""Prop1B"", + ""property2"" : 102, + ""property4"" : ""Prop1B-102-Batched"" + },{ + ""property1"" : ""Prop1C"", + ""property2"" : 103, + ""property4"" : ""Prop1C-103-Batched"" + },] + } + }", + result); + } + + [Test] + public async Task BatchExtension_OfObject_ThatUsesStartBatch_ResolvesDuringExecution() + { + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddController(); + + o.MapTypeExtension("Property5") + .WithBatchProcessing() + .AddPossibleTypes(typeof(ChildObject)) + .AddResolver((IEnumerable source) => + { + // using static batch builder for child objects + return GraphActionResult.StartBatch() + .FromSource(source, x => x.Property1) + .WithResults( + source.Select(x => new ChildObject() + { + ParentId = x.Property1, + Property1 = $"Child-Prop1-{x.Property1}", + }), + y => y.ParentId) + .Complete(); + }); + }) + .Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { retrieveObjects { property1 property2 property5 { property1 } } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""retrieveObjects"": [{ + ""property1"" : ""Prop1A"", + ""property2"" : 101, + ""property5"" : { + ""property1"": ""Child-Prop1-Prop1A"" + } + },{ + ""property1"" : ""Prop1B"", + ""property2"" : 102, + ""property5"" : { + ""property1"": ""Child-Prop1-Prop1B"" + } + },{ + ""property1"" : ""Prop1C"", + ""property2"" : 103, + ""property5"" : { + ""property1"": ""Child-Prop1-Prop1C"" + } + },] + } + }", + result); + } + + [Test] + public async Task Runtime_TypeExtension_WithSecurityParams_AndAllowedUser_ResolvesCorrectly() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.AddController(); + + o.MapTypeExtension("Property3", (TwoPropertyStruct source) => + { + return $"{source.Property1}-{source.Property2}"; + }) + .RequireAuthorization("policy1"); + }); + + serverBuilder.Authorization.AddClaimPolicy("policy1", "policy1Claim", "policy1Value"); + serverBuilder.UserContext + .Authenticate() + .AddUserClaim("policy1Claim", "policy1Value"); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { retrieveStruct { property1 property3 } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""retrieveStruct"" : { + ""property1"": ""Prop1"", + ""property3"" : ""Prop1-101"" + } + } + }", + result); + } + + [Test] + public async Task Runtime_ExecutionDirective_WithSecurityParams_AndUnAuthenticatedUser_RendersAccessDenied() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.AddController(); + + o.MapTypeExtension("Property3", (TwoPropertyStruct source) => + { + return $"{source.Property1}-{source.Property2}"; + }) + .RequireAuthorization("policy1"); + }); + + // user not authenticated + serverBuilder.Authorization.AddClaimPolicy("policy1", "policy1Claim", "policy1Value"); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { retrieveStruct { property1 property3 } }"); + + var result = await server.ExecuteQuery(builder); + Assert.AreEqual(1, result.Messages.Count); + Assert.AreEqual(GraphMessageSeverity.Critical, result.Messages[0].Severity); + Assert.AreEqual(Constants.ErrorCodes.ACCESS_DENIED, result.Messages[0].Code); + } + + [Test] + public async Task Runtime_ExecutionDirective_WithSecurityParams_AndUnAuthorizedUser_RendersAccessDenied() + { + var serverBuilder = new TestServerBuilder(); + + serverBuilder.AddGraphQL(o => + { + o.AddController(); + + o.MapTypeExtension("Property3", (TwoPropertyStruct source) => + { + return $"{source.Property1}-{source.Property2}"; + }) + .RequireAuthorization("policy1"); + }); + + // incorrect claim + serverBuilder.Authorization.AddClaimPolicy("policy1", "policy1Claim", "policy1Value"); + serverBuilder.UserContext + .Authenticate() + .AddUserClaim("policy1Claim", "policy2Value"); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { retrieveStruct { property1 property3 } }"); + + var result = await server.ExecuteQuery(builder); + Assert.AreEqual(1, result.Messages.Count); + Assert.AreEqual(GraphMessageSeverity.Critical, result.Messages[0].Severity); + Assert.AreEqual(Constants.ErrorCodes.ACCESS_DENIED, result.Messages[0].Code); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/RuntimeTypeInferenceTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/RuntimeTypeInferenceTests.cs index af7a2e53b..17b50e064 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/RuntimeTypeInferenceTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/RuntimeTypeInferenceTests.cs @@ -10,9 +10,9 @@ namespace GraphQL.AspNet.Tests.Execution { using System.Threading.Tasks; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/BatchResolverTestData/BatchInterfaceController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/BatchResolverTestData/BatchInterfaceController.cs index 3a99b1def..13dbf9177 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/BatchResolverTestData/BatchInterfaceController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/BatchResolverTestData/BatchInterfaceController.cs @@ -14,7 +14,8 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.BatchResolverTestData using GraphQL.AspNet.Common; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Common.Interfaces; using GraphQL.AspNet.Tests.Framework.Interfaces; [GraphRoute("batch")] diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/BatchResolverTestData/BatchObjectController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/BatchResolverTestData/BatchObjectController.cs index 1b91e5c10..97dd28d42 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/BatchResolverTestData/BatchObjectController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/BatchResolverTestData/BatchObjectController.cs @@ -15,7 +15,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.BatchResolverTestData using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphRoute("batch")] public class BatchObjectController : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/BatchResolverTestData/BatchStructController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/BatchResolverTestData/BatchStructController.cs index 578ccef3d..485b66845 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/BatchResolverTestData/BatchStructController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/BatchResolverTestData/BatchStructController.cs @@ -15,7 +15,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.BatchResolverTestData using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphRoute("batch")] public class BatchStructController : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/DirectiveProcessorTypeSystemLocationTestData/UniontTestObject.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/DirectiveProcessorTypeSystemLocationTestData/UniontTestObject.cs index 9720daeaa..9c42958f7 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/DirectiveProcessorTypeSystemLocationTestData/UniontTestObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/DirectiveProcessorTypeSystemLocationTestData/UniontTestObject.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.DirectiveProcessorTypeSystemLo { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [ApplyDirective(typeof(LocationTestDirective))] public class UniontTestObject : GraphUnionProxy diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionDirectiveInvocationTestData/ExecutionDirectiveTestController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionDirectiveInvocationTestData/ExecutionDirectiveTestController.cs index 935abba8c..bb790c2ed 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionDirectiveInvocationTestData/ExecutionDirectiveTestController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionDirectiveInvocationTestData/ExecutionDirectiveTestController.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.ExecutionDirectiveInvocationTe { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ExecutionDirectiveTestController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionDirectiveTestData/AdjustBatchDataDirective.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionDirectiveTestData/AdjustBatchDataDirective.cs index 710395d97..4cd5ab89e 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionDirectiveTestData/AdjustBatchDataDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionDirectiveTestData/AdjustBatchDataDirective.cs @@ -18,7 +18,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.ExecutionDirectiveTestData using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Interfaces.Execution.QueryPlans.DocumentParts; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class AdjustBatchDataDirective : GraphDirective { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionDirectiveTestData/DirectiveTestController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionDirectiveTestData/DirectiveTestController.cs index b235e89e7..b9cad7fdc 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionDirectiveTestData/DirectiveTestController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionDirectiveTestData/DirectiveTestController.cs @@ -13,7 +13,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.ExecutionDirectiveTestData using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class DirectiveTestController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayAsEnumerableController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayAsEnumerableController.cs index 427eb6a19..32f643119 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayAsEnumerableController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayAsEnumerableController.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData using System.Collections.Generic; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ArrayAsEnumerableController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayOnReturnObjectMethodController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayOnReturnObjectMethodController.cs index edc1e0ddf..42a577800 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayOnReturnObjectMethodController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayOnReturnObjectMethodController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ArrayOnReturnObjectMethodController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayOnReturnObjectPropertyController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayOnReturnObjectPropertyController.cs index 42db58335..d99687280 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayOnReturnObjectPropertyController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayOnReturnObjectPropertyController.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData using System.Collections.Generic; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ArrayOnReturnObjectPropertyController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayThroughArrayDeclarationController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayThroughArrayDeclarationController.cs index 6782c1d0f..d984a638f 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayThroughArrayDeclarationController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayThroughArrayDeclarationController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ArrayThroughArrayDeclarationController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayThroughGraphActionAsEnumerableController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayThroughGraphActionAsEnumerableController.cs index 0b86ceb27..7ad6920f1 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayThroughGraphActionAsEnumerableController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ArrayThroughGraphActionAsEnumerableController.cs @@ -13,7 +13,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ArrayThroughGraphActionAsEnumerableController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ExternalItemCollectionController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ExternalItemCollectionController.cs index 1198aaf6e..3ea0a9179 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ExternalItemCollectionController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/ExternalItemCollectionController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ExternalItemCollectionController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/InputObjectArrayController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/InputObjectArrayController.cs index 2f3386f61..6e0bb9407 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/InputObjectArrayController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/InputObjectArrayController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class InputObjectArrayController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/NullableFieldArgumentTestController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/NullableFieldArgumentTestController.cs index 9c707b2d1..4523ae8ea 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/NullableFieldArgumentTestController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/NullableFieldArgumentTestController.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData { using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class NullableFieldArgumentTestController : GraphIdController { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/SimpleExecutionController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/SimpleExecutionController.cs index ad01ea95d..9feec1f4c 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/SimpleExecutionController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/SimpleExecutionController.cs @@ -18,7 +18,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData using GraphQL.AspNet.Execution; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphRoute("simple")] public class SimpleExecutionController : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/TypeExtensionOnTwoPropertyObjectController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/TypeExtensionOnTwoPropertyObjectController.cs index f0bd1cb42..f3e6475c3 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/TypeExtensionOnTwoPropertyObjectController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/TypeExtensionOnTwoPropertyObjectController.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData using System.Collections.Generic; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class TypeExtensionOnTwoPropertyObjectController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/UnionController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/UnionController.cs index 72a1fc458..6213ff24e 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/UnionController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/ExecutionPlanTestData/UnionController.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.ExecutionPlanTestData using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class UnionController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/InputVariableExecutionTestData/InputValueController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/InputVariableExecutionTestData/InputValueController.cs index f7fd58f95..0c248c58d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/InputVariableExecutionTestData/InputValueController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/InputVariableExecutionTestData/InputValueController.cs @@ -13,7 +13,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.InputVariableExecutionTestData using System.Linq; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphRoot] public class InputValueController : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/InputVariableExecutionTestData/NullableVariableObjectController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/InputVariableExecutionTestData/NullableVariableObjectController.cs index fda8ab697..43ef112cb 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/InputVariableExecutionTestData/NullableVariableObjectController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/InputVariableExecutionTestData/NullableVariableObjectController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.InputVariableExecutionTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class NullableVariableObjectController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/NotRequiredNonNullableNotSetClassObject.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/NotRequiredNonNullableNotSetClassObject.cs index 1b5112b36..57404be05 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/NotRequiredNonNullableNotSetClassObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/NotRequiredNonNullableNotSetClassObject.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.IntrospecetionInputFieldTestDa { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class NotRequiredNonNullableNotSetClassObject { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/NotRequiredNotSetClassObject.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/NotRequiredNotSetClassObject.cs index c5fa5f354..05ab61973 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/NotRequiredNotSetClassObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/NotRequiredNotSetClassObject.cs @@ -9,7 +9,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.IntrospecetionInputFieldTestData { - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class NotRequiredNotSetClassObject { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/NotRequiredSetClassObject.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/NotRequiredSetClassObject.cs index 2b8772626..339d7cba0 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/NotRequiredSetClassObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/NotRequiredSetClassObject.cs @@ -9,7 +9,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.IntrospecetionInputFieldTestData { - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class NotRequiredSetClassObject { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/RequiredClassObject.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/RequiredClassObject.cs index dd5187529..4df9024c4 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/RequiredClassObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/RequiredClassObject.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.IntrospecetionInputFieldTestData { using System.ComponentModel.DataAnnotations; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class RequiredClassObject { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/StructNotRequiredNotSetClassObject.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/StructNotRequiredNotSetClassObject.cs index 18dd7e57f..41f4e2999 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/StructNotRequiredNotSetClassObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/StructNotRequiredNotSetClassObject.cs @@ -9,7 +9,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.IntrospecetionInputFieldTestData { - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public struct StructNotRequiredNotSetClassObject { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/StructNotRequiredSetClassObject.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/StructNotRequiredSetClassObject.cs index 0fbc63560..f5558139c 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/StructNotRequiredSetClassObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/StructNotRequiredSetClassObject.cs @@ -9,7 +9,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.IntrospecetionInputFieldTestData { - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public struct StructNotRequiredSetClassObject { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/StructRequiredClassObject.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/StructRequiredClassObject.cs index 744115c9b..3c0203ba4 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/StructRequiredClassObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospecetionInputFieldTestData/StructRequiredClassObject.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.IntrospecetionInputFieldTestData { using System.ComponentModel.DataAnnotations; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public struct StructRequiredClassObject { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/ARepeatableDirective.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/ARepeatableDirective.cs index b97829246..506f8740f 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/ARepeatableDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/ARepeatableDirective.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.IntrospectionTestData using GraphQL.AspNet.Directives; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [Repeatable] public class ARepeatableDirective : GraphDirective diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/CustomSpecifiedScalar.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/CustomSpecifiedScalar.cs index 27242b297..20e621bc8 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/CustomSpecifiedScalar.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/CustomSpecifiedScalar.cs @@ -24,8 +24,6 @@ public CustomSpecifiedScalar() public override ScalarValueType ValueType => ScalarValueType.String; - public override TypeCollection OtherKnownTypes { get; } = TypeCollection.Empty; - public override object Resolve(ReadOnlySpan data) { return null; diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/InputTestObject.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/InputTestObject.cs index 4df7f9e48..b3829946b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/InputTestObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/InputTestObject.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.IntrospectionTestData using System.ComponentModel; using System.ComponentModel.DataAnnotations; using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphType(InputName = "InputObject")] [Description("input obj desc")] diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/IntrospectableObject.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/IntrospectableObject.cs index a9e3513ae..c01c70b29 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/IntrospectableObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/IntrospectableObject.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.IntrospectionTestData { using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class IntrospectableObject { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/NonRepeatableDirective.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/NonRepeatableDirective.cs index d3b69d1cc..d9a1d84cc 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/NonRepeatableDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/NonRepeatableDirective.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.IntrospectionTestData using GraphQL.AspNet.Directives; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class NonRepeatableDirective : GraphDirective { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/SodaTypeUnionProxy.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/SodaTypeUnionProxy.cs index 9b6d8d85a..35583ee29 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/SodaTypeUnionProxy.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/SodaTypeUnionProxy.cs @@ -23,6 +23,8 @@ public class SodaTypeUnionProxy : IGraphUnionProxy public bool Publish { get; set; } = true; + public string InternalName { get; } = "InternalSodaTypes"; + public Type MapType(Type runtimeObjectType) { return runtimeObjectType; diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/TypeExtensionDescriptionController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/TypeExtensionDescriptionController.cs index d005c3988..c661a5bb6 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/TypeExtensionDescriptionController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/IntrospectionTestData/TypeExtensionDescriptionController.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.IntrospectionTestData using System.ComponentModel; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class TypeExtensionDescriptionController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeDirectiveTestData/SingleFieldController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeDirectiveTestData/SingleFieldController.cs new file mode 100644 index 000000000..5874654de --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeDirectiveTestData/SingleFieldController.cs @@ -0,0 +1,23 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution.TestData.RuntimeDirectiveTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Controllers; + + public class SingleFieldController : GraphController + { + [QueryRoot] + public int Field() + { + return 99; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeFieldTestData/ControllerWithInjectedService.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeFieldTestData/ControllerWithInjectedService.cs new file mode 100644 index 000000000..f1f8535ee --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeFieldTestData/ControllerWithInjectedService.cs @@ -0,0 +1,23 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution.TestData.RuntimeFieldTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Controllers; + + public class ControllerWithInjectedService : GraphController + { + [QueryRoot] + public int Add(int arg1, IInjectedService arg2) + { + return arg1 + arg2.FetchValue(); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeFieldTestData/IInjectedService.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeFieldTestData/IInjectedService.cs new file mode 100644 index 000000000..5918b3882 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeFieldTestData/IInjectedService.cs @@ -0,0 +1,16 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution.TestData.RuntimeFieldTestData +{ + public interface IInjectedService + { + int FetchValue(); + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeFieldTestData/InjectedService.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeFieldTestData/InjectedService.cs new file mode 100644 index 000000000..e346b010a --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeFieldTestData/InjectedService.cs @@ -0,0 +1,19 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution.TestData.RuntimeFieldTestData +{ + public class InjectedService : IInjectedService + { + public int FetchValue() + { + return 23; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeFieldTestData/SampleDelegatesForMinimalApi.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeFieldTestData/SampleDelegatesForMinimalApi.cs new file mode 100644 index 000000000..61209a2d5 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeFieldTestData/SampleDelegatesForMinimalApi.cs @@ -0,0 +1,27 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution.TestData.RuntimeFieldTest +{ + public class SampleDelegatesForMinimalApi + { + public static int StaticMethod(int a, int b) + { + return a + b; + } + + public int InstanceMethod(int a, int b) + { + this.InstanceMethodCalls += 1; + return a + b; + } + + public int InstanceMethodCalls { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/TestSchemaWithDescription.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeFieldTestData/TestSchemaWithDescription.cs similarity index 100% rename from src/unit-tests/graphql-aspnet-tests/Execution/TestData/TestSchemaWithDescription.cs rename to src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeFieldTestData/TestSchemaWithDescription.cs diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeTypeExtensionTestData/ChildObject.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeTypeExtensionTestData/ChildObject.cs new file mode 100644 index 000000000..2697897e0 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeTypeExtensionTestData/ChildObject.cs @@ -0,0 +1,18 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution.TestData.RuntimeTypeExtensionTestData +{ + public class ChildObject + { + public string ParentId { get; set; } + + public string Property1 { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeTypeExtensionTestData/RuntimeFieldTypeExtensionTestsController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeTypeExtensionTestData/RuntimeFieldTypeExtensionTestsController.cs new file mode 100644 index 000000000..39ae094fb --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeTypeExtensionTestData/RuntimeFieldTypeExtensionTestsController.cs @@ -0,0 +1,85 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution.TestData.RuntimeTypeExtensionTestData +{ + using System.Collections.Generic; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class RuntimeFieldTypeExtensionTestsController : GraphController + { + [QueryRoot] + public TwoPropertyObject RetrieveObject() + { + return new TwoPropertyObject() + { + Property1 = "Prop1", + Property2 = 101, + }; + } + + [QueryRoot] + public TwoPropertyStruct RetrieveStruct() + { + return new TwoPropertyStruct() + { + Property1 = "Prop1", + Property2 = 101, + }; + } + + [QueryRoot] + public IEnumerable RetrieveObjects() + { + var list = new List(); + list.Add(new TwoPropertyObject() + { + Property1 = "Prop1A", + Property2 = 101, + }); + list.Add(new TwoPropertyObject() + { + Property1 = "Prop1B", + Property2 = 102, + }); + list.Add(new TwoPropertyObject() + { + Property1 = "Prop1C", + Property2 = 103, + }); + + return list; + } + + [QueryRoot] + public IEnumerable RetrieveStructs() + { + var list = new List(); + list.Add(new TwoPropertyStruct() + { + Property1 = "Prop1A", + Property2 = 101, + }); + list.Add(new TwoPropertyStruct() + { + Property1 = "Prop1B", + Property2 = 102, + }); + list.Add(new TwoPropertyStruct() + { + Property1 = "Prop1C", + Property2 = 103, + }); + + return list; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/TypeSystemDirectiveTestData/MarkedScalarByAttribute.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/TypeSystemDirectiveTestData/MarkedScalarByAttribute.cs index 758da8dc9..21feada77 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/TypeSystemDirectiveTestData/MarkedScalarByAttribute.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/TypeSystemDirectiveTestData/MarkedScalarByAttribute.cs @@ -33,7 +33,5 @@ public override object Serialize(object item) } public override ScalarValueType ValueType => ScalarValueType.Boolean; - - public override TypeCollection OtherKnownTypes => TypeCollection.Empty; } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/TypeSystemDirectiveTestData/MarkedUnion.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/TypeSystemDirectiveTestData/MarkedUnion.cs index 8d609f7e8..9a94b55db 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/TypeSystemDirectiveTestData/MarkedUnion.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/TypeSystemDirectiveTestData/MarkedUnion.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.TypeSystemDirectiveTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [ApplyDirective(typeof(UnionMarkerDirective))] public class MarkedUnion : GraphUnionProxy diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/TypeSystemDirectiveTestData/ToUpperWrapperResolver.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/TypeSystemDirectiveTestData/ToUpperWrapperResolver.cs index ddaac8c46..79e32cf45 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/TypeSystemDirectiveTestData/ToUpperWrapperResolver.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/TypeSystemDirectiveTestData/ToUpperWrapperResolver.cs @@ -24,11 +24,11 @@ public ToUpperWrapperResolver(IGraphFieldResolver originalResolver) _originalResolver = originalResolver; } - public Type ObjectType { get; } - public async Task ResolveAsync(FieldResolutionContext context, CancellationToken cancelToken = default) { await _originalResolver.ResolveAsync(context, cancelToken); } + + public IGraphFieldResolverMetaData MetaData => _originalResolver.MetaData; } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/Home.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/Home.cs new file mode 100644 index 000000000..da06094a1 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/Home.cs @@ -0,0 +1,22 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution.TestData.UnionTypeExecutionTestData +{ + public class Home : IBuilding + { + public string Name { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + + public int Id { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/IBuilding.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/IBuilding.cs new file mode 100644 index 000000000..753cb6171 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/IBuilding.cs @@ -0,0 +1,20 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution.TestData.UnionTypeExecutionTestData +{ + public interface IBuilding + { + int Id { get; } + + int Width { get; set; } + + int Height { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/PersonOrTeacher.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/PersonOrTeacher.cs index 6309f11d7..df90c39f5 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/PersonOrTeacher.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/PersonOrTeacher.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.UnionTypeExecutionTestData using System; using GraphQL.AspNet.Common; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class PersonOrTeacher : GraphUnionProxy { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/SchoolController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/SchoolController.cs index c681993a6..82d5019a8 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/SchoolController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/SchoolController.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Execution.TestData.UnionTypeExecutionTestData using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class SchoolController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/Television.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/Television.cs new file mode 100644 index 000000000..73aa0d457 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/Television.cs @@ -0,0 +1,16 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution.TestData.UnionTypeExecutionTestData +{ + public class Television + { + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/UnrelatedItemsController.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/UnrelatedItemsController.cs new file mode 100644 index 000000000..33e0462be --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/UnionTypeExecutionTestData/UnrelatedItemsController.cs @@ -0,0 +1,70 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Execution.TestData.UnionTypeExecutionTestData +{ + using System.Collections.Generic; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Interfaces.Controllers; + + public class UnrelatedItemsController : GraphController + { + [QueryRoot("retrieveItems", "TheThingsUnion", typeof(Television), typeof(Person), typeof(Home), TypeExpression = "[Type]")] + public IGraphActionResult RetrieveItems() + { + var list = new List(); + list.Add(new Person() + { + Id = 1, + Name = "Bob Smith", + }); + + list.Add(new Television() + { + Name = "Tv 1", + }); + + list.Add(new Home() + { + Id = 1, + Name = "Home 1", + Width = 200, + Height = 300, + }); + + list.Add(new Home() + { + Id = 2, + Name = "Home 2", + Width = 300, + Height = 400, + }); + + return this.Ok(list); + } + + [TypeExtension(typeof(IBuilding), "squareFeet", typeof(int))] + public int BuildingSquareFeet(IBuilding building) + { + return building.Width * building.Height; + } + + [BatchTypeExtension(typeof(IBuilding), "perimeter", typeof(int))] + public IGraphActionResult BuildingPerimeter(IEnumerable buildings) + { + var dic = new Dictionary(); + + foreach (var building in buildings) + dic.Add(building, (building.Width * 2) + (building.Height * 2)); + + return this.Ok(dic); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TypeExtensionExecutionTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TypeExtensionExecutionTests.cs index 690292617..5064fc9e5 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TypeExtensionExecutionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TypeExtensionExecutionTests.cs @@ -14,11 +14,11 @@ namespace GraphQL.AspNet.Tests.Execution using System.Threading.Tasks; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NSubstitute; using NUnit.Framework; using Microsoft.Extensions.DependencyInjection; using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.BatchResolverTestData; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TypeSystemDirectiveCommonTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TypeSystemDirectiveCommonTests.cs index 039a710a9..f499c7937 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TypeSystemDirectiveCommonTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TypeSystemDirectiveCommonTests.cs @@ -30,10 +30,12 @@ public async Task ExtendAResolver_DirectiveDeclaredByType() LastName = "Smith", }; - var context = server.CreateFieldExecutionContext( - nameof(TestPersonWithResolverExtensionDirectiveByType.Name), - person); + var contextBuilder = server.CreateFieldContextBuilder( + nameof(TestPersonWithResolverExtensionDirectiveByType.Name)); + contextBuilder.AddSourceData(person); + + var context = contextBuilder.CreateExecutionContext(); await server.ExecuteField(context); var data = context.Result?.ToString(); @@ -54,10 +56,12 @@ public async Task ExtendAResolver_DirectiveDeclaredByName() LastName = "Smith", }; - var context = server.CreateFieldExecutionContext( - nameof(TestPersonWithResolverExtensionDirectiveByName.Name), - person); + var contextBuilder = server.CreateFieldContextBuilder( + nameof(TestPersonWithResolverExtensionDirectiveByName.Name)); + + contextBuilder.AddSourceData(person); + var context = contextBuilder.CreateExecutionContext(); await server.ExecuteField(context); var data = context.Result?.ToString(); @@ -80,10 +84,12 @@ public async Task DirectiveDeclaredByName_AndDirectiveHasCustomName_IsFoundAndEx LastName = "Smith", }; - var context = server.CreateFieldExecutionContext( - nameof(TestPersonWithResolverExtensionDirectiveByName.Name), - person); + var contextBuilder = server.CreateFieldContextBuilder( + nameof(TestPersonWithResolverExtensionDirectiveByName.Name)); + contextBuilder.AddSourceData(person); + + var context = contextBuilder.CreateExecutionContext(); await server.ExecuteField(context); var data = context.Result?.ToString(); @@ -104,9 +110,10 @@ public async Task ExtendAnObjectType_AddField_DirectiveDeclaredByType() Property2 = "prop 2", }; - var context = server.CreateFieldExecutionContext( - "property3", - obj); + var contextBuilder = server.CreateFieldContextBuilder("property3") + .AddSourceData(obj); + + var context = contextBuilder.CreateExecutionContext(); await server.ExecuteField(context); diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TypeSystemDirectiveDiscoveryTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TypeSystemDirectiveDiscoveryTests.cs index 9826557f2..64a080766 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TypeSystemDirectiveDiscoveryTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TypeSystemDirectiveDiscoveryTests.cs @@ -123,13 +123,10 @@ public void UnionDirective_DeclaredByType_WhenNotExplicitlyIncluded_IsLocatedAnd [Test] public void ScalarDirective_DeclaredByType_WhenNotExplicitlyIncluded_IsLocatedAndIncluded() { - using var restorePoint = new GraphQLGlobalRestorePoint(); - - GraphQLProviders.ScalarProvider.RegisterCustomScalar(typeof(MarkedScalarByAttribute)); - // the object has a property that returns the custom scalar // forcing the enclusion of the scalar and thus the directive on said scalar var server = new TestServerBuilder(TestOptions.UseCodeDeclaredNames) + .AddType() .AddType() .Build(); diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/TypeSystemDirectiveInvocationTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/TypeSystemDirectiveInvocationTests.cs index 0ed5c62ee..edd89b3d3 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TypeSystemDirectiveInvocationTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TypeSystemDirectiveInvocationTests.cs @@ -10,9 +10,9 @@ namespace GraphQL.AspNet.Tests.Execution { using System.Threading.Tasks; using GraphQL.AspNet.Configuration.Exceptions; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.TypeSystemDirectiveInvocationTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/UnionTypeExecutionTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/UnionTypeExecutionTests.cs index ec3b590ca..f5001ebf0 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/UnionTypeExecutionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/UnionTypeExecutionTests.cs @@ -10,9 +10,9 @@ namespace GraphQL.AspNet.Tests.Execution { using System; using System.Threading.Tasks; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.UnionTypeExecutionTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] @@ -282,5 +282,111 @@ ... on Person { Assert.Fail("Expected a specific unhandled exception from the failed union, got none"); } + + [Test] + public async Task WhenMultipleUnrelatedTypesAreReturnedGenerally_AndOneExecutesABatchExtensionForAnImplementedInterface_QueryExecutesCorrectly() + { + var server = new TestServerBuilder(TestOptions.IncludeExceptions) + .AddType() + .AddType() + .AddType() + .AddType() + .AddGraphController() + .Build(); + + // The Items on retrieveItems are not related (Person, Home, TV) + // The method returns the unioned items as a List to the runtime + // + // IBuilding, which home implements, has `.perimeter` built as a batch extension + // and needs to take inthe IEnumerable + // + // The runtime must properlty detect and cast the right items of List + // to a single IEnumerable to correctly invoke the batch extension + var builder = server.CreateQueryContextBuilder() + .AddQueryText( + @"query { + retrieveItems { + ... on Home { + id + name + + #this is a batch extension + perimeter + } + } + }"); + + var expectedOutput = + @"{ + ""data"": { + ""retrieveItems"" : [{ + ""id"" : 1, + ""name"": ""Home 1"", + ""perimeter"": 1000 + }, + { + ""id"" : 2, + ""name"": ""Home 2"", + ""perimeter"": 1400 + }] + } + }"; + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings(expectedOutput, result); + } + + [Test] + public async Task WhenMultipleUnrelatedTypesAreReturnedGenerally_AndOneExecutesATypeExtensionForAnImplementedInterface_QueryExecutesCorrectly() + { + var server = new TestServerBuilder() + .AddType() + .AddType() + .AddType() + .AddType() + .AddGraphController() + .Build(); + + // The items on retrieveItems are not related (Person, Home, TV) + // The method returns the unioned items as a List to the runtime + // + // IBuilding, which home implements, has `.squareFeet` built as a type extension + // and needs to take in an IBuilding + // + // The runtime must detect and properly cast the right objects in the unioned list + // to IBuilding to correctly invoke the type extension + var builder = server.CreateQueryContextBuilder() + .AddQueryText( + @"query { + retrieveItems { + ... on Home { + id + name + + #this is a single item type extension + squareFeet + } + } + }"); + + var expectedOutput = + @"{ + ""data"": { + ""retrieveItems"" : [{ + ""id"" : 1, + ""name"": ""Home 1"", + ""squareFeet"": 60000 + }, + { + ""id"" : 2, + ""name"": ""Home 2"", + ""squareFeet"": 120000 + }] + } + }"; + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings(expectedOutput, result); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/VariableExecutionTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/VariableExecutionTests.cs index ab31b83e1..60288ddf8 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/VariableExecutionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/VariableExecutionTests.cs @@ -13,10 +13,10 @@ namespace GraphQL.AspNet.Tests.Execution using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.InputVariableExecutionTestData; using GraphQL.AspNet.Tests.Execution.Variables; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Execution/VariableKitchenSinkTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/VariableKitchenSinkTests.cs index 2cff83eea..3c36b12ad 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/VariableKitchenSinkTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/VariableKitchenSinkTests.cs @@ -13,9 +13,9 @@ namespace GraphQL.AspNet.Tests.Execution using System.Collections.Generic; using System.Runtime.InteropServices; using System.Threading.Tasks; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.TestData.VariableExecutionTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using Microsoft.Extensions.Hosting; using NSubstitute; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Integration/MusicIndustryTests.cs b/src/unit-tests/graphql-aspnet-tests/Integration/MusicIndustryTests.cs index 62b714366..6ffc58c3b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Integration/MusicIndustryTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Integration/MusicIndustryTests.cs @@ -11,8 +11,8 @@ namespace GraphQL.AspNet.Tests.Integration { using System.IO; using System.Threading.Tasks; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Tests.Integration.Model; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTemplateTests.cs deleted file mode 100644 index f4a87fb91..000000000 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTemplateTests.cs +++ /dev/null @@ -1,142 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Tests.Internal.Templating -{ - using System.Linq; - using GraphQL.AspNet.Common.Extensions; - using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Internal.TypeTemplates; - using GraphQL.AspNet.Schemas; - using GraphQL.AspNet.Schemas.Structural; - using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - using GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; - using NUnit.Framework; - - [TestFixture] - public class ControllerTemplateTests - { - [Test] - public void Parse_GraphRootController_UsesEmptyRoutePath() - { - var template = new GraphControllerTemplate(typeof(DeclaredGraphRootController)); - template.Parse(); - template.ValidateOrThrow(); - - Assert.AreEqual(SchemaItemPath.Empty, template.Route); - } - - [Test] - public void Parse_MismatchedRouteFragmentConfiguration_ThrowsException() - { - var template = new GraphControllerTemplate(typeof(InvalidRouteController)); - template.Parse(); - - Assert.Throws(() => - { - template.ValidateOrThrow(); - }); - } - - [Test] - public void Parse_OverloadedMethodsOnDifferentRoots_ParsesCorrectly() - { - var template = new GraphControllerTemplate(typeof(TwoMethodsDifferentRootsController)); - template.Parse(); - template.ValidateOrThrow(); - - Assert.AreEqual(3, template.FieldTemplates.Count()); - Assert.AreEqual(2, template.Actions.Count()); - Assert.AreEqual(1, template.Extensions.Count()); - Assert.IsTrue(template.FieldTemplates.Any(x => x.Route.Path == $"[mutation]/TwoMethodsDifferentRoots/{nameof(TwoMethodsDifferentRootsController.ActionMethodNoAttributes)}")); - Assert.IsTrue(template.FieldTemplates.Any(x => x.Route.Path == $"[query]/TwoMethodsDifferentRoots/{nameof(TwoMethodsDifferentRootsController.ActionMethodNoAttributes)}")); - Assert.IsTrue(template.FieldTemplates.Any(x => x.Route.Path == $"[type]/TwoPropertyObject/Property3")); - } - - [Test] - public void Parse_ReturnArrayOnAction_ParsesCorrectly() - { - var expectedTypeExpression = new GraphTypeExpression( - "String", - MetaGraphTypes.IsList); - - var template = new GraphControllerTemplate(typeof(ArrayReturnController)); - template.Parse(); - template.ValidateOrThrow(); - - Assert.AreEqual(1, template.Actions.Count()); - - var action = template.Actions.ElementAt(0); - Assert.AreEqual(typeof(string[]), action.DeclaredReturnType); - Assert.AreEqual(expectedTypeExpression, action.TypeExpression); - } - - [Test] - public void Parse_ArrayOnInputParameter_ThrowsException() - { - var expectedTypeExpression = new GraphTypeExpression( - "Input_" + typeof(TwoPropertyObject).FriendlyName(), - MetaGraphTypes.IsList); - - var template = new GraphControllerTemplate(typeof(ArrayInputParamController)); - template.Parse(); - template.ValidateOrThrow(); - - Assert.AreEqual(1, template.Actions.Count()); - - var action = template.Actions.ElementAt(0); - Assert.AreEqual(typeof(string), action.DeclaredReturnType); - Assert.AreEqual(1, action.Arguments.Count); - Assert.AreEqual(expectedTypeExpression, action.Arguments[0].TypeExpression); - } - - [Test] - public void Parse_AssignedDirective_IsTemplatized() - { - var template = new GraphControllerTemplate(typeof(ControllerWithDirective)); - template.Parse(); - template.ValidateOrThrow(); - - Assert.AreEqual(1, template.AppliedDirectives.Count()); - - var appliedDirective = template.AppliedDirectives.First(); - Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); - Assert.AreEqual(new object[] { 101, "controller arg" }, appliedDirective.Arguments); - } - - [Test] - public void Parse_InheritedAction_IsIncludedInTheTemplate() - { - var template = new GraphControllerTemplate(typeof(ControllerWithInheritedAction)); - template.Parse(); - template.ValidateOrThrow(); - - Assert.AreEqual(2, template.Actions.Count()); - - Assert.IsNotNull(template.Actions.Single(x => x.Name.EndsWith(nameof(BaseControllerWithAction.BaseControllerAction)))); - Assert.IsNotNull(template.Actions.Single(x => x.Name.EndsWith(nameof(ControllerWithInheritedAction.ChildControllerAction)))); - } - - [Test] - public void Parse_BatchExtensionWithCustomNamedObject_HasAppropriateTypeExpression() - { - var template = new GraphControllerTemplate(typeof(ControllerWithActionAsTypeExtensionForCustomNamedObject)); - template.Parse(); - template.ValidateOrThrow(); - - var field = template.FieldTemplates[0]; - - // type expression should be the custom name on the type - // not the default name - Assert.AreEqual("[Custom_Named_Object]", field.TypeExpression.ToString()); - } - } -} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DefaultUnionTemplateProviderTests.cs b/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DefaultUnionTemplateProviderTests.cs deleted file mode 100644 index 87f9473b0..000000000 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DefaultUnionTemplateProviderTests.cs +++ /dev/null @@ -1,69 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Tests.Internal.Templating -{ - using System; - using System.Collections.Generic; - using GraphQL.AspNet.Engine; - using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Schemas.TypeSystem; - using NUnit.Framework; - - [TestFixture] - public class DefaultUnionTemplateProviderTests - { - public class FakeProxy : IGraphUnionProxy - { - public string Name - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public string Description - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public HashSet Types => throw new NotImplementedException(); - - public bool Publish - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public Type MapType(Type runtimeObjectType) => throw new NotImplementedException(); - } - - [Test] - public void ParseAKnownScalar_ThrowsException() - { - var templateProvider = new DefaultTypeTemplateProvider(); - Assert.Throws(() => - { - var template = templateProvider.ParseType(typeof(int), TypeKind.SCALAR); - }); - } - - [Test] - public void ParseAUnionProxy_ThrowsException() - { - var templateProvider = new DefaultTypeTemplateProvider(); - - Assert.Throws(() => - { - var template = templateProvider.ParseType(typeof(FakeProxy), TypeKind.UNION); - }); - } - } -} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Logging/ActionMethodModelStateValidatedLogEntryTests.cs b/src/unit-tests/graphql-aspnet-tests/Logging/ActionMethodModelStateValidatedLogEntryTests.cs index 8d15dea34..2547587c9 100644 --- a/src/unit-tests/graphql-aspnet-tests/Logging/ActionMethodModelStateValidatedLogEntryTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Logging/ActionMethodModelStateValidatedLogEntryTests.cs @@ -40,7 +40,6 @@ private ExecutionArgument CreateArgument( argTemplate.Name.Returns(name); argTemplate.TypeExpression.Returns(new GraphTypeExpression(name, wrappers)); - argTemplate.ArgumentModifiers.Returns(GraphArgumentModifiers.None); argTemplate.ObjectType.Returns(concreteType); argTemplate.ParameterName.Returns(name); @@ -48,14 +47,14 @@ private ExecutionArgument CreateArgument( } private void ValidateModelDictionaryToLogEntry( - IGraphFieldResolverMethod graphMethod, + IGraphFieldResolverMetaData resolverMetaData, IGraphFieldRequest fieldRequest, InputModelStateDictionary dictionary, ActionMethodModelStateValidatedLogEntry logEntry) { Assert.AreEqual(fieldRequest.Id.ToString(), logEntry.PipelineRequestId); - Assert.AreEqual(graphMethod.Parent.ObjectType.FriendlyName(true), logEntry.ControllerName); - Assert.AreEqual(graphMethod.Name, logEntry.ActionName); + Assert.AreEqual(resolverMetaData.ParentInternalName, logEntry.ControllerName); + Assert.AreEqual(resolverMetaData.InternalName, logEntry.ActionName); Assert.AreEqual(dictionary.IsValid, logEntry.ModelDataIsValid); foreach (var kvp in dictionary) @@ -111,7 +110,7 @@ public void InvalidModelItem_BuildsLogEntry() .AddType() .Build(); - var builder = server.CreateGraphTypeFieldContextBuilder( + var builder = server.CreateFieldContextBuilder( nameof(LogTestController.ValidatableInputObject)); var context = builder.CreateExecutionContext(); @@ -127,11 +126,11 @@ public void InvalidModelItem_BuildsLogEntry() var dictionary = generator.CreateStateDictionary(argumentToTest); var entry = new ActionMethodModelStateValidatedLogEntry( - builder.GraphMethod, + builder.ResolverMetaData, context.Request, dictionary); - this.ValidateModelDictionaryToLogEntry(builder.GraphMethod, context.Request, dictionary, entry); + this.ValidateModelDictionaryToLogEntry(builder.ResolverMetaData, context.Request, dictionary, entry); } [Test] @@ -141,7 +140,7 @@ public void ValidModelItem_BuildsLogEntry() .AddType() .Build(); - var builder = server.CreateGraphTypeFieldContextBuilder( + var builder = server.CreateFieldContextBuilder( nameof(LogTestController.ValidatableInputObject)); var context = builder.CreateExecutionContext(); @@ -157,11 +156,11 @@ public void ValidModelItem_BuildsLogEntry() var dictionary = generator.CreateStateDictionary(argumentToTest); var entry = new ActionMethodModelStateValidatedLogEntry( - builder.GraphMethod, + builder.ResolverMetaData, context.Request, dictionary); - this.ValidateModelDictionaryToLogEntry(builder.GraphMethod, context.Request, dictionary, entry); + this.ValidateModelDictionaryToLogEntry(builder.ResolverMetaData, context.Request, dictionary, entry); } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Logging/GeneralEventLogEntryPropertyChecks.cs b/src/unit-tests/graphql-aspnet-tests/Logging/GeneralEventLogEntryPropertyChecks.cs index 03a74f02c..797ea65b0 100644 --- a/src/unit-tests/graphql-aspnet-tests/Logging/GeneralEventLogEntryPropertyChecks.cs +++ b/src/unit-tests/graphql-aspnet-tests/Logging/GeneralEventLogEntryPropertyChecks.cs @@ -241,7 +241,7 @@ public void FieldResolutionStartedLogEntry() .AddType() .Build(); - var package = server.CreateGraphTypeFieldContextBuilder( + var package = server.CreateFieldContextBuilder( nameof(LogTestController.ExecuteField2)); var resolutionContext = package.CreateResolutionContext(); @@ -261,7 +261,7 @@ public void FieldResolutionCompletedLogEntry() .AddType() .Build(); - var package = server.CreateGraphTypeFieldContextBuilder( + var package = server.CreateFieldContextBuilder( nameof(LogTestController.ExecuteField2)); var resolutionContext = package.CreateResolutionContext(); var fieldRequest = package.FieldRequest; @@ -287,7 +287,7 @@ public void FieldAuthorizationStartedLogEntry() builder.UserContext.Authenticate("bobSmith"); var server = builder.Build(); - var package = server.CreateGraphTypeFieldContextBuilder( + var package = server.CreateFieldContextBuilder( nameof(LogTestController.ExecuteField2)); var fieldRequest = package.FieldRequest; @@ -312,7 +312,7 @@ public void FieldAuthorizationCompletedLogEntry() builder.UserContext.Authenticate("bobSmith"); var server = builder.Build(); - var package = server.CreateGraphTypeFieldContextBuilder( + var package = server.CreateFieldContextBuilder( nameof(LogTestController.ExecuteField2)); var fieldRequest = package.FieldRequest; var authContext = package.CreateSecurityContext(); @@ -339,7 +339,7 @@ public void FieldAuthenticationStartedLogEntry() builder.UserContext.Authenticate("bobSmith"); var server = builder.Build(); - var package = server.CreateGraphTypeFieldContextBuilder( + var package = server.CreateFieldContextBuilder( nameof(LogTestController.ExecuteField2)); var fieldRequest = package.FieldRequest; @@ -372,7 +372,7 @@ public void FieldAuthenticationCompletedLogEntry() authResult.AuthenticationScheme.Returns("testScheme"); authResult.Suceeded.Returns(true); - var package = server.CreateGraphTypeFieldContextBuilder( + var package = server.CreateFieldContextBuilder( nameof(LogTestController.ExecuteField2)); var fieldRequest = package.FieldRequest; @@ -399,20 +399,21 @@ public void ActionMethodInvocationStartedLogEntry() var server = new TestServerBuilder(TestOptions.UseCodeDeclaredNames) .AddType() .Build(); - var graphMethod = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(LogTestController.ExecuteField2)) as IGraphFieldResolverMethod; - var package = server.CreateGraphTypeFieldContextBuilder( + var resolverMetaData = GraphQLTemplateHelper + .CreateActionMethodTemplate(nameof(LogTestController.ExecuteField2)) + .CreateResolverMetaData(); + + var package = server.CreateFieldContextBuilder( nameof(LogTestController.ExecuteField2)); var fieldRequest = package.FieldRequest; - var entry = new ActionMethodInvocationStartedLogEntry(graphMethod, fieldRequest); + var entry = new ActionMethodInvocationStartedLogEntry(resolverMetaData, fieldRequest); Assert.AreEqual(LogEventIds.ControllerInvocationStarted.Id, entry.EventId); Assert.AreEqual(fieldRequest.Id.ToString(), entry.PipelineRequestId); - Assert.AreEqual(graphMethod.Parent.InternalFullName, entry.ControllerName); - Assert.AreEqual(graphMethod.Name, entry.ActionName); - Assert.AreEqual(graphMethod.Route.Path, entry.FieldPath); - Assert.AreEqual(graphMethod.ObjectType.ToString(), entry.SourceObjectType); - Assert.AreEqual(graphMethod.IsAsyncField, entry.IsAsync); + Assert.AreEqual(resolverMetaData.ParentInternalName, entry.ControllerName); + Assert.AreEqual(resolverMetaData.InternalName, entry.ActionName); + Assert.AreEqual(resolverMetaData.IsAsyncField, entry.IsAsync); Assert.IsNotNull(entry.ToString()); } @@ -423,21 +424,20 @@ public void ActionMethodInvocationCompletedLogEntry() .AddType() .Build(); - var graphMethod = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(LogTestController.ExecuteField2)) as IGraphFieldResolverMethod; - var package = server.CreateGraphTypeFieldContextBuilder( + var metaData = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(LogTestController.ExecuteField2)).CreateResolverMetaData(); + var package = server.CreateFieldContextBuilder( nameof(LogTestController.ExecuteField2)); var resolutionContext = package.CreateResolutionContext(); var fieldRequest = package.FieldRequest; var result = new object(); - var entry = new ActionMethodInvocationCompletedLogEntry(graphMethod, fieldRequest, result); + var entry = new ActionMethodInvocationCompletedLogEntry(metaData, fieldRequest, result); Assert.AreEqual(LogEventIds.ControllerInvocationCompleted.Id, entry.EventId); Assert.AreEqual(fieldRequest.Id.ToString(), entry.PipelineRequestId); - Assert.AreEqual(graphMethod.Parent.InternalFullName, entry.ControllerName); - Assert.AreEqual(graphMethod.Name, entry.ActionName); - Assert.AreEqual(graphMethod.Route.Path, entry.FieldPath); + Assert.AreEqual(metaData.ParentInternalName, entry.ControllerName); + Assert.AreEqual(metaData.InternalName, entry.ActionName); Assert.AreEqual(result.GetType().FriendlyName(true), entry.ResultTypeName); Assert.IsNotNull(entry.ToString()); } @@ -449,8 +449,8 @@ public void ActionMethodInvocationExceptionLogEntry() .AddType() .Build(); - var graphMethod = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(LogTestController.ExecuteField2)) as IGraphFieldResolverMethod; - var package = server.CreateGraphTypeFieldContextBuilder( + var metaData = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(LogTestController.ExecuteField2)).CreateResolverMetaData(); + var package = server.CreateFieldContextBuilder( nameof(LogTestController.ExecuteField2)); var fieldRequest = package.FieldRequest; @@ -458,12 +458,12 @@ public void ActionMethodInvocationExceptionLogEntry() var inner = new Exception("inner error"); var exception = new TargetInvocationException("invocation error message", inner); - var entry = new ActionMethodInvocationExceptionLogEntry(graphMethod, fieldRequest, exception); + var entry = new ActionMethodInvocationExceptionLogEntry(metaData, fieldRequest, exception); Assert.AreEqual(LogEventIds.ControllerInvocationException.Id, entry.EventId); Assert.AreEqual(fieldRequest.Id.ToString(), entry.PipelineRequestId); - Assert.AreEqual(graphMethod.Parent.InternalFullName, entry.ControllerTypeName); - Assert.AreEqual(graphMethod.Name, entry.ActionName); + Assert.AreEqual(metaData.ParentInternalName, entry.ControllerTypeName); + Assert.AreEqual(metaData.InternalName, entry.ActionName); Assert.IsNotNull(entry.ToString()); var exceptionEntry = entry.Exception as ExceptionLogItem; @@ -480,20 +480,21 @@ public void ActionMethodUnhandledExceptionLogEntry() .AddType() .Build(); - var package = server.CreateGraphTypeFieldContextBuilder( + var package = server.CreateFieldContextBuilder( nameof(LogTestController.ExecuteField2)); - var graphMethod = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(LogTestController.ExecuteField2)) as IGraphFieldResolverMethod; + var template = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(LogTestController.ExecuteField2)); + var metaData = template.CreateResolverMetaData(); var fieldRequest = package.FieldRequest; var result = new object(); var exception = new Exception("inner error"); - var entry = new ActionMethodUnhandledExceptionLogEntry(graphMethod, fieldRequest, exception); + var entry = new ActionMethodUnhandledExceptionLogEntry(metaData, fieldRequest, exception); Assert.AreEqual(LogEventIds.ControllerUnhandledException.Id, entry.EventId); Assert.AreEqual(fieldRequest.Id.ToString(), entry.PipelineRequestId); - Assert.AreEqual(graphMethod.Parent.InternalFullName, entry.ControllerTypeName); - Assert.AreEqual(graphMethod.Name, entry.ActionName); + Assert.AreEqual(metaData.ParentInternalName, entry.ControllerTypeName); + Assert.AreEqual(metaData.InternalName, entry.ActionName); Assert.IsNotNull(entry.ToString()); var exceptionEntry = entry.Exception as ExceptionLogItem; diff --git a/src/unit-tests/graphql-aspnet-tests/Middleware/DirectiveMiddlewareTestData/PipelineTestDirective.cs b/src/unit-tests/graphql-aspnet-tests/Middleware/DirectiveMiddlewareTestData/PipelineTestDirective.cs index e5988bcc0..55907e9fa 100644 --- a/src/unit-tests/graphql-aspnet-tests/Middleware/DirectiveMiddlewareTestData/PipelineTestDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Middleware/DirectiveMiddlewareTestData/PipelineTestDirective.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Middleware.DirectiveMiddlewareTestData using GraphQL.AspNet.Directives; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; internal class PipelineTestDirective : GraphDirective { diff --git a/src/unit-tests/graphql-aspnet-tests/Middleware/DirectivePipelineMiddlewareTests.cs b/src/unit-tests/graphql-aspnet-tests/Middleware/DirectivePipelineMiddlewareTests.cs index d9b8636d7..2467bb67a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Middleware/DirectivePipelineMiddlewareTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Middleware/DirectivePipelineMiddlewareTests.cs @@ -16,8 +16,8 @@ namespace GraphQL.AspNet.Tests.Middleware using GraphQL.AspNet.Middleware.DirectiveExecution.Components; using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Tests.Middleware.DirectiveMiddlewareTestData; using NUnit.Framework; @@ -37,12 +37,13 @@ public async Task ValidationMiddlewareForwardsRequestToRuleSet() .AddType() .Build(); - var context = server.CreateDirectiveExecutionContext( + var context = server.CreateDirectiveContextBuilder( DirectiveLocation.OBJECT, new TwoPropertyObject(), DirectiveInvocationPhase.SchemaGeneration, SourceOrigin.None, - new object[] { 5 }); // directive requires 2 argument, only 1 supplied + new object[] { 5 }) // directive requires 2 argument, only 1 supplied + .CreateExecutionContext(); Assert.IsTrue(context.IsValid); @@ -62,12 +63,13 @@ public async Task InvocationMiddlewareCallsResolver() var testObject = new TwoPropertyObject(); - var context = server.CreateDirectiveExecutionContext( + var context = server.CreateDirectiveContextBuilder( DirectiveLocation.OBJECT, testObject, DirectiveInvocationPhase.SchemaGeneration, SourceOrigin.None, - new object[] { "testValue", 5 }); + new object[] { "testValue", 5 }) + .CreateExecutionContext(); Assert.IsTrue(context.IsValid); diff --git a/src/unit-tests/graphql-aspnet-tests/Middleware/MiddlewarePipelineTests.cs b/src/unit-tests/graphql-aspnet-tests/Middleware/MiddlewarePipelineTests.cs index 78facfa6d..0ed39c3a3 100644 --- a/src/unit-tests/graphql-aspnet-tests/Middleware/MiddlewarePipelineTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Middleware/MiddlewarePipelineTests.cs @@ -60,7 +60,7 @@ public async Task SingularPipelineInvokesComponentsInOrder() // fake a graph ql request context var server = serverBuilder.Build(); - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(MiddlewareController.FieldOfData)); var executionContext = fieldBuilder.CreateExecutionContext(); @@ -111,7 +111,7 @@ public void NoFoundMiddlewareComponent_ThrowsException() // fake a graph ql request context var server = serverBuilder.Build(); - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(MiddlewareController.FieldOfData)); var executionContext = fieldBuilder.CreateExecutionContext(); @@ -143,7 +143,7 @@ public async Task MiddlewareComponentThrowsExceptions_MiddlewareInvokerShouldUnw // fake a graph ql request context var server = serverBuilder.Build(); - var builder = server.CreateGraphTypeFieldContextBuilder( + var builder = server.CreateFieldContextBuilder( nameof(MiddlewareController.FieldOfData)); var context = builder.CreateExecutionContext(); @@ -188,7 +188,7 @@ public async Task SingletonMiddlewareComponent_IsNeverInstantiatedMoreThanOnce() // fake a graph ql request context var server = serverBuilder.Build(); - var builder = server.CreateGraphTypeFieldContextBuilder( + var builder = server.CreateFieldContextBuilder( nameof(MiddlewareController.FieldOfData)); // execute the pipeline multiple times @@ -238,7 +238,7 @@ public async Task SingletonMiddlewareWithUserProvidedInstance_NeverAttemptsToCre Assert.IsNotNull(pipeline); var server = serverBuilder.Build(); - var builder = server.CreateGraphTypeFieldContextBuilder( + var builder = server.CreateFieldContextBuilder( nameof(MiddlewareController.FieldOfData)); // make an empty service collection (preventing creation if the middleware isnt found) diff --git a/src/unit-tests/graphql-aspnet-tests/Middleware/QueryPipelineIntegrationTestData/SimpleExecutionController.cs b/src/unit-tests/graphql-aspnet-tests/Middleware/QueryPipelineIntegrationTestData/SimpleExecutionController.cs index d50589d5f..935d38441 100644 --- a/src/unit-tests/graphql-aspnet-tests/Middleware/QueryPipelineIntegrationTestData/SimpleExecutionController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Middleware/QueryPipelineIntegrationTestData/SimpleExecutionController.cs @@ -17,7 +17,7 @@ namespace GraphQL.AspNet.Tests.Middleware.QueryPipelineIntegrationTestData using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphRoute("simple")] public class SimpleExecutionController : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/DefaultTypeTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/DefaultTypeTests.cs index 98a47fdff..cc4d908cd 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/DefaultTypeTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/DefaultTypeTests.cs @@ -12,13 +12,15 @@ namespace GraphQL.AspNet.Tests.Schemas using System.Linq; using System.Reflection; using GraphQL.AspNet.Engine; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; using NUnit.Framework; [TestFixture] public class DefaultTypeTests { [Test] - public void Scalars_EnsureAllScalarNamesHaveAnAssociatedType() + public void Scalars_EnsureAllGlobalScalarNamesHaveAnAssociatedType() { var fields = typeof(Constants.ScalarNames) .GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy) @@ -27,8 +29,8 @@ public void Scalars_EnsureAllScalarNamesHaveAnAssociatedType() foreach (FieldInfo fi in fields) { Assert.IsTrue( - GraphQLProviders.ScalarProvider.IsScalar(fi.GetRawConstantValue()?.ToString()), - $"The scalar name '{fi.GetRawConstantValue()}' does not exist in the {{{nameof(DefaultScalarGraphTypeProvider)}}} collection."); + GlobalTypes.IsBuiltInScalar(fi.GetRawConstantValue()?.ToString()), + $"The scalar name '{fi.GetRawConstantValue()}' does not exist in the {{{nameof(GlobalTypes)}}} collection."); } } } diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/EnumValueCollectionTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/EnumValueCollectionTests.cs index 2b1e34044..cd1f4e39d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/EnumValueCollectionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/EnumValueCollectionTests.cs @@ -36,13 +36,13 @@ public void Add_DuplicateEnumValueNameThrowsException() var enumValue = Substitute.For(); enumValue.Name.Returns("VALUE1"); - enumValue.InternalValue.Returns(EnumValueTestEnum.Value1); - enumValue.InternalLabel.Returns(EnumValueTestEnum.Value1.ToString()); + enumValue.DeclaredValue.Returns(EnumValueTestEnum.Value1); + enumValue.DeclaredLabel.Returns(EnumValueTestEnum.Value1.ToString()); var enumValue2 = Substitute.For(); enumValue2.Name.Returns("VALUE1"); - enumValue2.InternalValue.Returns(EnumValueTestEnum.Value1); - enumValue.InternalLabel.Returns(EnumValueTestEnum.Value1.ToString()); + enumValue2.DeclaredValue.Returns(EnumValueTestEnum.Value1); + enumValue.DeclaredLabel.Returns(EnumValueTestEnum.Value1.ToString()); var collection = new EnumValueCollection(owner); collection.Add(enumValue); @@ -69,8 +69,8 @@ public void FindEnumValue(EnumValueTestEnum testValue, bool doesValidate, bool s var enumValue = Substitute.For(); enumValue.Name.Returns("VALUE1"); - enumValue.InternalValue.Returns(EnumValueTestEnum.Value1); - enumValue.InternalLabel.Returns(EnumValueTestEnum.Value1.ToString()); + enumValue.DeclaredValue.Returns(EnumValueTestEnum.Value1); + enumValue.DeclaredLabel.Returns(EnumValueTestEnum.Value1.ToString()); var collection = new EnumValueCollection(owner); collection.Add(enumValue); @@ -83,6 +83,26 @@ public void FindEnumValue(EnumValueTestEnum testValue, bool doesValidate, bool s Assert.IsNull(result); } + [Test] + public void FindEnumValue_NullPassed_RetunsNull() + { + var owner = Substitute.For(); + owner.Name.Returns("graphType"); + owner.ValidateObject(Arg.Any()).Returns(true); + owner.ObjectType.Returns(typeof(EnumValueTestEnum)); + + var enumValue = Substitute.For(); + enumValue.Name.Returns("VALUE1"); + enumValue.DeclaredValue.Returns(EnumValueTestEnum.Value1); + enumValue.DeclaredLabel.Returns(EnumValueTestEnum.Value1.ToString()); + + var collection = new EnumValueCollection(owner); + collection.Add(enumValue); + + var result = collection.FindByEnumValue(null); + Assert.IsNull(result); + } + [TestCase("VALUE1", true)] [TestCase("ValUE1", true)] // not case sensitive [TestCase("VALUE2", false)] @@ -94,8 +114,8 @@ public void ContainsKey(string testValue, bool shouldBeFound) var enumValue = Substitute.For(); enumValue.Name.Returns("VALUE1"); - enumValue.InternalValue.Returns(EnumValueTestEnum.Value1); - enumValue.InternalLabel.Returns(EnumValueTestEnum.Value1.ToString()); + enumValue.DeclaredValue.Returns(EnumValueTestEnum.Value1); + enumValue.DeclaredLabel.Returns(EnumValueTestEnum.Value1.ToString()); var collection = new EnumValueCollection(owner); collection.Add(enumValue); @@ -115,8 +135,8 @@ public void ThisByName(string name, bool shouldBeFound) var enumValue = Substitute.For(); enumValue.Name.Returns("VALUE1"); - enumValue.InternalValue.Returns(EnumValueTestEnum.Value1); - enumValue.InternalLabel.Returns(EnumValueTestEnum.Value1.ToString()); + enumValue.DeclaredValue.Returns(EnumValueTestEnum.Value1); + enumValue.DeclaredLabel.Returns(EnumValueTestEnum.Value1.ToString()); var collection = new EnumValueCollection(owner); collection.Add(enumValue); @@ -136,5 +156,75 @@ public void ThisByName(string name, bool shouldBeFound) }); } } + + [TestCase("VALUE1", true)] + [TestCase("VALUE2", false)] + public void TryGetValue(string name, bool shouldBeFound) + { + var owner = Substitute.For(); + owner.Name.Returns("graphType"); + owner.ObjectType.Returns(typeof(EnumValueTestEnum)); + + var enumValue = Substitute.For(); + enumValue.Name.Returns("VALUE1"); + enumValue.DeclaredValue.Returns(EnumValueTestEnum.Value1); + enumValue.DeclaredLabel.Returns(EnumValueTestEnum.Value1.ToString()); + + var collection = new EnumValueCollection(owner); + collection.Add(enumValue); + + var result = collection.TryGetValue(name, out var item); + if (shouldBeFound) + { + Assert.IsTrue(result); + Assert.IsNotNull(item); + Assert.AreEqual(enumValue, item); + } + else + { + Assert.IsFalse(result); + Assert.IsNull(item); + } + } + + [Test] + public void Remove_ValidValueIsRemoved() + { + var owner = Substitute.For(); + owner.Name.Returns("graphType"); + owner.ObjectType.Returns(typeof(EnumValueTestEnum)); + + var enumValue = Substitute.For(); + enumValue.Name.Returns("VALUE1"); + enumValue.DeclaredValue.Returns(EnumValueTestEnum.Value1); + enumValue.DeclaredLabel.Returns(EnumValueTestEnum.Value1.ToString()); + + var collection = new EnumValueCollection(owner); + collection.Add(enumValue); + + Assert.AreEqual(1, collection.Count); + collection.Remove("VALUE1"); + Assert.AreEqual(0, collection.Count); + } + + [Test] + public void Remove_InvalidValueIsIgnored() + { + var owner = Substitute.For(); + owner.Name.Returns("graphType"); + owner.ObjectType.Returns(typeof(EnumValueTestEnum)); + + var enumValue = Substitute.For(); + enumValue.Name.Returns("VALUE1"); + enumValue.DeclaredValue.Returns(EnumValueTestEnum.Value1); + enumValue.DeclaredLabel.Returns(EnumValueTestEnum.Value1.ToString()); + + var collection = new EnumValueCollection(owner); + collection.Add(enumValue); + + Assert.AreEqual(1, collection.Count); + collection.Remove("VALUE1DDD"); + Assert.AreEqual(1, collection.Count); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GeneralSchemaTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GeneralSchemaTests.cs new file mode 100644 index 000000000..0e4c524ba --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GeneralSchemaTests.cs @@ -0,0 +1,46 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas +{ + using System.Globalization; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Framework; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData; + using NUnit.Framework; + + [TestFixture] + public class GeneralSchemaTests + { + [Test] + public void InternalName_OnControllerAction_IsRendered() + { + var schema = new TestServerBuilder() + .AddController() + .Build().Schema; + + var field = schema.Operations[GraphOperationType.Query].Fields.FindField("actionField"); + Assert.AreEqual("ActionWithInternalName", field.InternalName); + } + + [Test] + public void InternalName_OnTypeExension_IsRendered() + { + var schema = new TestServerBuilder() + .AddController() + .Build().Schema; + + var twoProp = schema.KnownTypes.FindGraphType(typeof(TwoPropertyObject)) as IObjectGraphType; + var field = twoProp.Fields.FindField("field1"); + Assert.AreEqual("TypeExtensionInternalName", field.InternalName); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedDirectiveTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedDirectiveTemplateTests.cs new file mode 100644 index 000000000..44740a9e8 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedDirectiveTemplateTests.cs @@ -0,0 +1,196 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.RuntimeFieldDeclarations +{ + using System.Linq; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.TypeSystem; + using Microsoft.AspNetCore.Authorization; + using Microsoft.Extensions.DependencyInjection; + using NSubstitute; + using NUnit.Framework; + + [TestFixture] + public class MappedDirectiveTemplateTests + { + [Test] + public void MapDirective_ByOptions_AddsDirectiveToOptions() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var directive = options.MapDirective("@myDirective"); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeDirectiveDefinition), directive); + + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + Assert.IsNotNull(options.RuntimeTemplates.FirstOrDefault(x => x == directive)); + Assert.AreEqual("[directive]/myDirective", directive.Route.Path); + Assert.IsNull(directive.ReturnType); + Assert.IsNull(directive.Resolver); + + // by default ALL locations are allowed + Assert.AreEqual(1, directive.Attributes.Count()); + var locationAttib = directive.Attributes.First() as DirectiveLocationsAttribute; + Assert.IsNotNull(locationAttib); + + Assert.AreEqual(DirectiveLocation.AllExecutionLocations | DirectiveLocation.AllTypeSystemLocations, locationAttib.Locations); + } + + [Test] + public void MapDirective_WhenNameApplied_NameIsAttchedToFieldDeclaration() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var directive = builderMock + .MapDirective("@myDirective") + .WithInternalName("internalDirectiveName"); + + Assert.AreEqual("internalDirectiveName", directive.InternalName); + } + + [Test] + public void MapDirective_ByBuilder_AddsDirectiveToOptions() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var directive = builderMock.MapDirective("@myDirective"); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeDirectiveDefinition), directive); + + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + Assert.IsNotNull(options.RuntimeTemplates.FirstOrDefault(x => x == directive)); + } + + [Test] + public void MapDirective_ByOptions_WithResolver_AddsDirectiveToOptions() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var directive = options.MapDirective("@myDirective", (string a) => 1); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeDirectiveDefinition), directive); + + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + Assert.IsNotNull(options.RuntimeTemplates.FirstOrDefault(x => x == directive)); + Assert.AreEqual("[directive]/myDirective", directive.Route.Path); + Assert.IsNull(directive.ReturnType); + Assert.AreEqual(typeof(int), directive.Resolver.Method.ReturnType); + } + + [Test] + public void MapDirective_ByBuilder_WithResolver_AddsDirectiveToOptions() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var directive = builderMock.MapDirective("@myDirective", (string a) => 1); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeDirectiveDefinition), directive); + + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + Assert.IsNotNull(options.RuntimeTemplates.FirstOrDefault(x => x == directive)); + Assert.IsNull(directive.ReturnType); + Assert.AreEqual(typeof(int), directive.Resolver.Method.ReturnType); + } + + [Test] + public void MappedDirective_WhenAllowAnonymousAdded_AddsAnonymousAttribute() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var directive = options.MapDirective("@mydirective", (string a) => 1); + + directive.AllowAnonymous(); + + // [AllowANonymous] [DirectiveLocations] + Assert.AreEqual(2, directive.Attributes.Count()); + Assert.IsNotNull(directive.Attributes.FirstOrDefault(x => x is AllowAnonymousAttribute)); + } + + [Test] + public void MappedDirective_WhenRequireAuthAdded_AddsAuthAttribute() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var directive = options.MapDirective("@mydirective", (string a) => 1); + + directive.RequireAuthorization("policy1", "roles1"); + + // [AllowANonymous] [DirectiveLocations] + Assert.AreEqual(2, directive.Attributes.Count()); + var attrib = directive.Attributes.FirstOrDefault(x => x is AuthorizeAttribute) as AuthorizeAttribute; + Assert.IsNotNull(attrib); + Assert.AreEqual("policy1", attrib.Policy); + Assert.AreEqual("roles1", attrib.Roles); + } + + [Test] + public void MappedDirective_WhenLocationRestricted_AddsAttribute() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var directive = options.MapDirective("@mydirective", (string a) => 1); + + directive.RestrictLocations(DirectiveLocation.QUERY | DirectiveLocation.MUTATION); + + Assert.AreEqual(1, directive.Attributes.Count()); + var attrib = directive.Attributes.FirstOrDefault(x => x is DirectiveLocationsAttribute) as DirectiveLocationsAttribute; + Assert.IsNotNull(attrib); + Assert.IsTrue(attrib.Locations.HasFlag(DirectiveLocation.QUERY)); + Assert.IsTrue(attrib.Locations.HasFlag(DirectiveLocation.MUTATION)); + } + + [Test] + public void MappedDirective_WhenRepeatableAdded_AddsAttribute() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var directive = options.MapDirective("@mydirective", (string a) => 1); + + directive.IsRepeatable(); + + // [AllowANonymous] [DirectiveLocations] + Assert.AreEqual(2, directive.Attributes.Count()); + var attrib = directive.Attributes.FirstOrDefault(x => x is RepeatableAttribute) as RepeatableAttribute; + Assert.IsNotNull(attrib); + } + + [Test] + public void MappedDirective_WhenResolverIsChangedWithExplicitType_NewResolverIsUsedAndTypeIsUsed() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var directive = options.MapDirective("@mydirective", (string a) => 1); + Assert.AreEqual(typeof(int), directive.Resolver.Method.ReturnType); + + directive.AddResolver((string a) => "bob"); + Assert.AreEqual(typeof(string), directive.Resolver.Method.ReturnType); + Assert.IsNull(directive.ReturnType); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedMutationGroupTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedMutationGroupTests.cs new file mode 100644 index 000000000..2ad31e643 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedMutationGroupTests.cs @@ -0,0 +1,145 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.RuntimeFieldDeclarations +{ + using System.Linq; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Schemas; + using Microsoft.AspNetCore.Authorization; + using Microsoft.Extensions.DependencyInjection; + using NSubstitute; + using NUnit.Framework; + + [TestFixture] + public class MappedMutationGroupTests + { + [Test] + public void MapMutationGroup_WhenAllowAnonymousAdded_AddsAnonymousAttribute() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapMutationGroup("/path1/path2"); + + field.AllowAnonymous(); + + Assert.AreEqual(1, field.Attributes.Count()); + Assert.IsNotNull(field.Attributes.FirstOrDefault(x => x is AllowAnonymousAttribute)); + } + + [Test] + public void MapMutationGroup_WhenRequireAuthAdded_AddsAuthAttribute() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapMutationGroup("/path1/path2"); + + field.RequireAuthorization("policy1", "roles1"); + + Assert.AreEqual(1, field.Attributes.Count()); + var attrib = field.Attributes.FirstOrDefault(x => x is AuthorizeAttribute) as AuthorizeAttribute; + Assert.IsNotNull(attrib); + Assert.AreEqual("policy1", attrib.Policy); + Assert.AreEqual("roles1", attrib.Roles); + } + + [Test] + public void MapMutationGroup_WhenUnresolvedChildFieldIsAdded_PathIsCorrect() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapMutationGroup("/path1/path2"); + + var childField = field.MapChildGroup("/path3/path4"); + + Assert.AreEqual("[mutation]/path1/path2/path3/path4", childField.Route.Path); + } + + [Test] + public void MapMutationGroup_WhenAllowAnonymousAdded_ThenResolvedField_AddsAnonymousAttributeToField() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapMutationGroup("/path1/path2"); + + field.AllowAnonymous(); + + var chidlField = field.MapField("/path3/path4", (string a) => 1); + + Assert.AreEqual(2, chidlField.Attributes.Count()); + Assert.IsNotNull(chidlField.Attributes.FirstOrDefault(x => x is AllowAnonymousAttribute)); + Assert.IsNotNull(chidlField.Attributes.FirstOrDefault(x => x is MutationRootAttribute)); + } + + [Test] + public void MapMutationGroup_WhenResolvedChildFieldIsAdded_PathIsCorrect() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapMutationGroup("/path1/path2"); + + var childField = field.MapField("/path3/path4", (string a) => 1); + + Assert.AreEqual("[mutation]/path1/path2/path3/path4", childField.Route.Path); + Assert.AreEqual(1, childField.Attributes.Count(x => x is MutationRootAttribute)); + } + + [Test] + public void MapMutationGroup_WhenResolvedChildFieldIsAddedToUnresolvedChildField_PathIsCorrect() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapMutationGroup("/path1/path2"); + var childField = field.MapChildGroup("/path3/path4"); + var resolvedField = childField.MapField("/path5/path6", (string a) => 1); + + Assert.AreEqual("[mutation]/path1/path2/path3/path4/path5/path6", resolvedField.Route.Path); + Assert.AreEqual(1, resolvedField.Attributes.Count(x => x is MutationRootAttribute)); + Assert.IsNotNull(resolvedField.Resolver); + } + + [Test] + public void MapMutationGroup_WhenResolvedChildFieldIsAdded_AndParentPathIsChanged_PathIsCorrect() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapMutationGroup("/path1/path2"); + + var childField = field.MapField("/path3/path4", (string a) => 1); + + Assert.AreEqual("[mutation]/path1/path2/path3/path4", childField.Route.Path); + Assert.AreEqual(1, childField.Attributes.Count(x => x is MutationRootAttribute)); + } + + [Test] + public void MapField_FromSchemaBuilder_WithNoResovler_FieldIsMade_ResolverIsNotSet() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var childField = builderMock.MapMutationGroup("/path1/path2") + .MapField("myField"); + + Assert.IsNull(childField.Resolver); + Assert.AreEqual(1, childField.Attributes.Count(x => x is MutationRootAttribute)); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedMutationTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedMutationTemplateTests.cs new file mode 100644 index 000000000..f09785355 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedMutationTemplateTests.cs @@ -0,0 +1,167 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.RuntimeFieldDeclarations +{ + using System.Linq; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas; + using Microsoft.Extensions.DependencyInjection; + using NSubstitute; + using NUnit.Framework; + + [TestFixture] + public class MappedMutationTemplateTests + { + public int TestDelegate(string a) + { + return 0; + } + + [Test] + public void MapMutation_FromSchemaOptions_WithDelegate_DoesAddFieldToSchema() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapMutation("/path1/path2", TestDelegate); + + Assert.IsNotNull(field); + Assert.AreEqual("[mutation]/path1/path2", field.Route.Path); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeResolvedFieldDefinition), field); + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + + Assert.AreEqual(1, field.Attributes.Count()); + var mutationRootAttrib = field.Attributes.FirstOrDefault(x => x.GetType() == typeof(MutationRootAttribute)); + Assert.IsNotNull(mutationRootAttrib); + } + + [Test] + public void MapMutation_FromBuilder_WithDelegate_DoesAddFieldToSchema() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var field = builderMock.MapMutation("/path1/path2", TestDelegate); + + Assert.IsNotNull(field); + Assert.AreEqual("[mutation]/path1/path2", field.Route.Path); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeResolvedFieldDefinition), field); + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + Assert.IsNotNull(field.Resolver); + + Assert.AreEqual(1, field.Attributes.Count()); + var mutationRootAttrib = field.Attributes.FirstOrDefault(x => x.GetType() == typeof(MutationRootAttribute)); + Assert.IsNotNull(mutationRootAttrib); + } + + [Test] + public void MapMutation_FromBuilder_WithNoDelegate_DoesAddFieldToSchema() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var field = builderMock.MapMutation("/path1/path2"); + + Assert.IsNotNull(field); + Assert.AreEqual("[mutation]/path1/path2", field.Route.Path); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeResolvedFieldDefinition), field); + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + + Assert.AreEqual(1, field.Attributes.Count()); + var mutationRootAttrib = field.Attributes.FirstOrDefault(x => x.GetType() == typeof(MutationRootAttribute)); + Assert.IsNotNull(mutationRootAttrib); + } + + [Test] + public void MapMutation_FromBuilder_WithUnionName0_AddsUnionNameToType() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var field = builderMock.MapMutation("myField", "myUnion", (string a) => 1); + + var attrib = field.Attributes.OfType().SingleOrDefault(); + + Assert.AreEqual("myUnion", attrib.UnionName); + Assert.AreEqual(1, field.Attributes.Count(x => x is MutationRootAttribute)); + } + + [Test] + public void MapMutation_WithUnionNameSetToNull_AddsUnionNameToType() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapMutation("myField", null, (string a) => 1); + + var attrib = field.Attributes.OfType().SingleOrDefault(); + + Assert.IsNull(attrib); + Assert.AreEqual(1, field.Attributes.Count(x => x is MutationRootAttribute)); + } + + [Test] + public void MapMutation_WithUnionName0_AddsUnionNameToType() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapMutation("myField", "myUnion", (string a) => 1); + + var attrib = field.Attributes.OfType().SingleOrDefault(); + + Assert.AreEqual("myUnion", attrib.UnionName); + Assert.AreEqual(1, field.Attributes.Count(x => x is MutationRootAttribute)); + } + + [Test] + public void MapMutation_WithNoResolver_IsCreated() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapMutation("myField"); + + Assert.IsNull(field.Resolver); + Assert.IsNull(field.ReturnType); + + Assert.IsTrue(options.RuntimeTemplates.Contains(field)); + Assert.AreEqual(1, field.Attributes.Count(x => x is MutationRootAttribute)); + } + + [Test] + public void MapMutation_WithResolver_AndUnion_IsCreated() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapMutation("myField", "myUnion", () => 1); + + Assert.IsNotNull(field.Resolver); + + Assert.AreEqual(1, field.Attributes.OfType().Count()); + Assert.AreEqual("myUnion", field.Attributes.OfType().Single().UnionName); + Assert.IsTrue(options.RuntimeTemplates.Contains(field)); + Assert.AreEqual(1, field.Attributes.Count(x => x is MutationRootAttribute)); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedQueryGroupTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedQueryGroupTests.cs new file mode 100644 index 000000000..58591ba45 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedQueryGroupTests.cs @@ -0,0 +1,328 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.RuntimeFieldDeclarations +{ + using System.Linq; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Schemas; + using Microsoft.AspNetCore.Authorization; + using Microsoft.Extensions.DependencyInjection; + using NSubstitute; + using NUnit.Framework; + + [TestFixture] + public class MappedQueryGroupTests + { + [Test] + public void MapQueryGroup_WhenAllowAnonymousAdded_AddsAnonymousAttribute() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQueryGroup("/path1/path2"); + + field.AllowAnonymous(); + + Assert.AreEqual(1, field.Attributes.Count()); + Assert.IsNotNull(field.Attributes.FirstOrDefault(x => x is AllowAnonymousAttribute)); + } + + [Test] + public void MapQueryGroup_WhenRequireAuthAdded_AddsAuthAttribute() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQueryGroup("/path1/path2"); + + field.RequireAuthorization("policy1", "roles1"); + + Assert.AreEqual(1, field.Attributes.Count()); + var attrib = field.Attributes.FirstOrDefault(x => x is AuthorizeAttribute) as AuthorizeAttribute; + Assert.IsNotNull(attrib); + Assert.AreEqual("policy1", attrib.Policy); + Assert.AreEqual("roles1", attrib.Roles); + } + + [Test] + public void MapQueryGroup_WhenUnresolvedChildFieldIsAdded_PathIsCorrect() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQueryGroup("/path1/path2"); + + var childField = field.MapChildGroup("/path3/path4"); + + Assert.AreEqual("[query]/path1/path2/path3/path4", childField.Route.Path); + } + + [Test] + public void MapQueryGroup_WhenResolvedChildFieldIsAdded_PathIsCorrect() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQueryGroup("/path1/path2"); + + var childField = field.MapField("/path3/path4", (string a) => 1); + + Assert.AreEqual("[query]/path1/path2/path3/path4", childField.Route.Path); + Assert.AreEqual(1, childField.Attributes.Count(x => x is QueryRootAttribute)); + } + + [Test] + public void MapMutationGroup_WhenAllowAnonymousAdded_ThenResolvedField_AddsAnonymousAttributeToField() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQueryGroup("/path1/path2"); + + field.AllowAnonymous(); + + var chidlField = field.MapField("/path3/path4", (string a) => 1); + + Assert.AreEqual(2, chidlField.Attributes.Count()); + Assert.IsNotNull(chidlField.Attributes.FirstOrDefault(x => x is AllowAnonymousAttribute)); + Assert.IsNotNull(chidlField.Attributes.FirstOrDefault(x => x is QueryRootAttribute)); + } + + [Test] + public void MapQueryGroup_WhenResolvedChildFieldIsAddedToUnresolvedChildField_PathIsCorrect() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQueryGroup("/path1/path2"); + var childField = field.MapChildGroup("/path3/path4"); + var resolvedField = childField.MapField("/path5/path6", (string a) => 1); + + Assert.AreEqual("[query]/path1/path2/path3/path4/path5/path6", resolvedField.Route.Path); + Assert.IsNotNull(resolvedField.Resolver); + Assert.AreEqual(1, resolvedField.Attributes.Count(x => x is QueryRootAttribute)); + } + + [Test] + public void MapQueryGroup_WhenResolvedChildFieldIsAdded_AndParentPathIsChanged_PathIsCorrect() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQueryGroup("/path1/path2"); + + var childField = field.MapField("/path3/path4", (string a) => 1); + + Assert.AreEqual("[query]/path1/path2/path3/path4", childField.Route.Path); + Assert.AreEqual(1, childField.Attributes.Count(x => x is QueryRootAttribute)); + } + + [Test] + public void MapQueryGroup_SingleCopyAttribute_AppliedToGroupAndField_IsOnlyAppliedOnce() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQueryGroup("/path1/path2") + .AllowAnonymous(); + + var childField = field.MapField("/path3/path4", (string a) => 1) + .AllowAnonymous(); + + var anonCount = childField.Attributes.Where(x => x is AllowAnonymousAttribute).Count(); + Assert.AreEqual(1, anonCount); + Assert.AreEqual(1, childField.Attributes.Count(x => x is QueryRootAttribute)); + } + + [Test] + public void MapField_WithNoResovler_FieldIsMade_ResolverIsNotSet() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var childField = options.MapQueryGroup("/path1/path2") + .MapField("myField"); + + Assert.IsNull(childField.Resolver); + Assert.AreEqual(1, childField.Attributes.Count(x => x is QueryRootAttribute)); + } + + [Test] + public void MapField_WithResovler_AndUnion_BothAreSet() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var childField = options.MapQueryGroup("/path1/path2") + .MapField("myField", "myUnion", () => 1); + + Assert.IsNotNull(childField.Resolver); + Assert.AreEqual(1, childField.Attributes.OfType().Count()); + Assert.AreEqual("myUnion", childField.Attributes.OfType().Single().UnionName); + Assert.AreEqual(1, childField.Attributes.Count(x => x is QueryRootAttribute)); + } + + [Test] + public void MapField_WithResovler_AndExplicitReturnType_IsMapped() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var childField = options.MapQueryGroup("/path1/path2") + .MapField("myField", () => 1); + + Assert.IsNotNull(childField.Resolver); + Assert.AreEqual(typeof(int?), childField.ReturnType); + Assert.AreEqual(1, childField.Attributes.Count(x => x is QueryRootAttribute)); + } + + [Test] + public void MapQueryGroup_MultipleCopyAttribute_AppliedToGroupAndField_IsAppliedMultipleTimes() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQueryGroup("/path1/path2") + .RequireAuthorization("policy1"); + + var childField = field.MapField("/path3/path4", (string a) => 1) + .RequireAuthorization("policy2"); + + Assert.AreEqual(3, childField.Attributes.Count()); + var authCount = childField.Attributes.Where(x => x is AuthorizeAttribute).Count(); + + Assert.AreEqual(2, authCount); + Assert.IsNotNull(childField.Attributes.SingleOrDefault(x => x is AuthorizeAttribute a && a.Policy == "policy1")); + Assert.IsNotNull(childField.Attributes.SingleOrDefault(x => x is AuthorizeAttribute a && a.Policy == "policy2")); + Assert.AreEqual(1, childField.Attributes.Count(x => x is QueryRootAttribute)); + + // ensure the order of applied attributes is parent field then child field + var i = 0; + foreach (var attrib in childField.Attributes) + { + if (attrib is AuthorizeAttribute a && a.Policy == "policy1") + { + Assert.AreEqual(0, i); + i++; + } + else if (attrib is AuthorizeAttribute b && b.Policy == "policy2") + { + Assert.AreEqual(1, i); + i++; + } + } + + Assert.AreEqual(2, i); + } + + [Test] + public void MapQueryGroup_MultipleSeperateChildGrous_AttributesAreAppliedCorrectly() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQueryGroup("/path1/path2") + .RequireAuthorization("policy1"); + + var childGroup1 = field.MapChildGroup("path3") + .RequireAuthorization("policy2"); + var childField1 = childGroup1.MapField("/path4", (string a) => 1) + .RequireAuthorization("policy3"); + + var childGroup2 = field.MapChildGroup("path5") + .RequireAuthorization("policy4"); + var childField2 = childGroup2.MapField("path6", (string a) => 1) + .RequireAuthorization("policy5"); + + // three authorize attribs + primary + Assert.AreEqual(4, childField1.Attributes.Count()); + var authCount = childField1.Attributes.Where(x => x is AuthorizeAttribute).Count(); + + Assert.AreEqual(3, authCount); + Assert.IsNotNull(childField1.Attributes.SingleOrDefault(x => x is AuthorizeAttribute a && a.Policy == "policy1")); + Assert.IsNotNull(childField1.Attributes.SingleOrDefault(x => x is AuthorizeAttribute a && a.Policy == "policy2")); + Assert.IsNotNull(childField1.Attributes.SingleOrDefault(x => x is AuthorizeAttribute a && a.Policy == "policy3")); + Assert.AreEqual(1, childField1.Attributes.Count(x => x is QueryRootAttribute)); + + // ensure the order of applied attributes is parent field then child field + var i = 0; + foreach (var attrib in childField1.Attributes) + { + if (attrib is AuthorizeAttribute a && a.Policy == "policy1") + { + Assert.AreEqual(0, i); + i++; + } + else if (attrib is AuthorizeAttribute b && b.Policy == "policy2") + { + Assert.AreEqual(1, i); + i++; + } + else if (attrib is AuthorizeAttribute c && c.Policy == "policy3") + { + Assert.AreEqual(2, i); + i++; + } + } + + Assert.AreEqual(3, i); + + // three authorize attribs + primary + Assert.AreEqual(4, childField2.Attributes.Count()); + authCount = childField2.Attributes.Where(x => x is AuthorizeAttribute).Count(); + + Assert.AreEqual(3, authCount); + Assert.IsNotNull(childField2.Attributes.SingleOrDefault(x => x is AuthorizeAttribute a && a.Policy == "policy1")); + Assert.IsNotNull(childField2.Attributes.SingleOrDefault(x => x is AuthorizeAttribute a && a.Policy == "policy4")); + Assert.IsNotNull(childField2.Attributes.SingleOrDefault(x => x is AuthorizeAttribute a && a.Policy == "policy5")); + Assert.AreEqual(1, childField2.Attributes.Count(x => x is QueryRootAttribute)); + + i = 0; + foreach (var attrib in childField2.Attributes) + { + if (attrib is AuthorizeAttribute a && a.Policy == "policy1") + { + Assert.AreEqual(0, i); + i++; + } + else if (attrib is AuthorizeAttribute b && b.Policy == "policy4") + { + Assert.AreEqual(1, i); + i++; + } + else if (attrib is AuthorizeAttribute c && c.Policy == "policy5") + { + Assert.AreEqual(2, i); + i++; + } + } + + Assert.AreEqual(3, i); + } + + [Test] + public void MapField_FromSchemaBuilder_WithNoResovler_FieldIsMade_ResolverIsNotSet() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var childField = builderMock.MapQueryGroup("/path1/path2") + .MapField("myField"); + + Assert.IsNull(childField.Resolver); + Assert.AreEqual(1, childField.Attributes.Count(x => x is QueryRootAttribute)); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedQueryTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedQueryTemplateTests.cs new file mode 100644 index 000000000..71e153bda --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedQueryTemplateTests.cs @@ -0,0 +1,137 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.RuntimeFieldDeclarations +{ + using System.Linq; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Tests.Framework; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.DependencyInjection; + using NSubstitute; + using NUnit.Framework; + + [TestFixture] + public class MappedQueryTemplateTests + { + public int TestDelegate(string a) + { + return 0; + } + + [Test] + public void MapQuery_FromSchemaOptions_WithDelegate_DoesAddFieldToSchema() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQuery("/path1/path2", TestDelegate); + + Assert.IsNotNull(field); + Assert.AreEqual(SchemaItemCollections.Query, field.Route.RootCollection); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeResolvedFieldDefinition), field); + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + + var queryRootAttrib = field.Attributes.FirstOrDefault(x => x.GetType() == typeof(QueryRootAttribute)); + Assert.IsNotNull(queryRootAttrib); + } + + [Test] + public void MapQuery_FromBuilder_WithDelegate_DoesAddFieldToSchema() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var field = builderMock.MapQuery("/path1/path2", TestDelegate); + + Assert.IsNotNull(field); + Assert.AreEqual(SchemaItemCollections.Query, field.Route.RootCollection); + + Assert.IsInstanceOf(typeof(IGraphQLRuntimeResolvedFieldDefinition), field); + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + + var queryRootAttrib = field.Attributes.FirstOrDefault(x => x.GetType() == typeof(QueryRootAttribute)); + Assert.IsNotNull(queryRootAttrib); + } + + [Test] + public void MapQuery_WithUnionNameSetToNull_AddsUnionNameToType() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapQuery("myField", null, (string a) => 1); + + var attrib = typeExt.Attributes.OfType().SingleOrDefault(); + + Assert.IsNull(attrib); + } + + [Test] + public void MapQuery_WithUnionName0_AddsUnionNameToType() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapQuery("myField", "myUnion", (string a) => 1); + + var attrib = typeExt.Attributes.OfType().SingleOrDefault(); + + Assert.AreEqual("myUnion", attrib.UnionName); + } + + [Test] + public void MapMutation_FromBuilder_WithNoDelegate_DoesAddFieldToSchema() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var field = builderMock.MapQuery("/path1/path2"); + + Assert.IsNotNull(field); + Assert.AreEqual("[query]/path1/path2", field.Route.Path); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeResolvedFieldDefinition), field); + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + + Assert.AreEqual(1, field.Attributes.Count()); + var rootAttrib = field.Attributes.FirstOrDefault(x => x.GetType() == typeof(QueryRootAttribute)); + Assert.IsNotNull(rootAttrib); + } + + [Test] + public void MapMutation_FromBuilder_WithUnionName0_AddsUnionNameToType() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var field = builderMock.MapQuery("myField", "myUnion", (string a) => 1); + + var attrib = field.Attributes.OfType().SingleOrDefault(); + + Assert.AreEqual("myUnion", attrib.UnionName); + + var rootAttrib = field.Attributes.FirstOrDefault(x => x.GetType() == typeof(QueryRootAttribute)); + Assert.IsNotNull(rootAttrib); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedTypeExtensionTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedTypeExtensionTemplateTests.cs new file mode 100644 index 000000000..c07c4d018 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/MappedTypeExtensionTemplateTests.cs @@ -0,0 +1,366 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.RuntimetypeExtDeclarations +{ + using System.Linq; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using Microsoft.AspNetCore.Authorization; + using Microsoft.Extensions.DependencyInjection; + using NSubstitute; + using NUnit.Framework; + + [TestFixture] + public class MappedTypeExtensionTemplateTests + { + [Test] + public void MapTypeExtension_ByOptions_AddsTypeExtensionToOptions() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapTypeExtension("mytypeExt"); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeTypeExtensionDefinition), typeExt); + + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + Assert.IsNotNull(options.RuntimeTemplates.FirstOrDefault(x => x == typeExt)); + Assert.AreEqual("[type]/TwoPropertyObject/mytypeExt", typeExt.Route.Path); + Assert.IsNull(typeExt.ReturnType); + Assert.IsNull(typeExt.Resolver); + Assert.AreEqual(FieldResolutionMode.PerSourceItem, typeExt.ExecutionMode); + + Assert.AreEqual(1, typeExt.Attributes.Count()); + + var typeExtensionAttrib = typeExt.Attributes.FirstOrDefault(x => x.GetType() == typeof(TypeExtensionAttribute)); + Assert.IsNotNull(typeExtensionAttrib); + } + + [Test] + public void MapTypeExtension_ByOptions_AndTypeDeclaration_AddsTypeExtensionToOptions() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapTypeExtension(typeof(TwoPropertyObject), "mytypeExt", (int x) => 0); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeTypeExtensionDefinition), typeExt); + + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + Assert.IsNotNull(options.RuntimeTemplates.FirstOrDefault(x => x == typeExt)); + Assert.AreEqual("[type]/TwoPropertyObject/mytypeExt", typeExt.Route.Path); + Assert.IsNull(typeExt.ReturnType); + Assert.IsNotNull(typeExt.Resolver); + Assert.AreEqual(FieldResolutionMode.PerSourceItem, typeExt.ExecutionMode); + + Assert.AreEqual(1, typeExt.Attributes.Count()); + + var typeExtensionAttrib = typeExt.Attributes.FirstOrDefault(x => x.GetType() == typeof(TypeExtensionAttribute)); + Assert.IsNotNull(typeExtensionAttrib); + } + + [Test] + public void MapTypeExtension_ByOptions_WithTypeParameter_AddsTypeExtensionToOptions() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapTypeExtension(typeof(TwoPropertyObject), "mytypeExt"); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeTypeExtensionDefinition), typeExt); + + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + Assert.IsNotNull(options.RuntimeTemplates.FirstOrDefault(x => x == typeExt)); + Assert.AreEqual("[type]/TwoPropertyObject/mytypeExt", typeExt.Route.Path); + Assert.IsNull(typeExt.ReturnType); + Assert.IsNull(typeExt.Resolver); + Assert.AreEqual(FieldResolutionMode.PerSourceItem, typeExt.ExecutionMode); + + Assert.AreEqual(1, typeExt.Attributes.Count()); + + var typeExtensionAttrib = typeExt.Attributes.FirstOrDefault(x => x.GetType() == typeof(TypeExtensionAttribute)); + Assert.IsNotNull(typeExtensionAttrib); + } + + [Test] + public void MapTypeExtension_WithName_AddsInternalName() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var typeExt = builderMock.MapTypeExtension("mytypeExt") + .WithInternalName("internaltypeExtName"); + + Assert.AreEqual("internaltypeExtName", typeExt.InternalName); + } + + [Test] + public void MapTypeExtension_ByBuilder_AddsTypeExtensionToOptions() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var typeExt = builderMock.MapTypeExtension("mytypeExt"); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeTypeExtensionDefinition), typeExt); + + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + Assert.IsNotNull(options.RuntimeTemplates.FirstOrDefault(x => x == typeExt)); + Assert.AreEqual(FieldResolutionMode.PerSourceItem, typeExt.ExecutionMode); + } + + [Test] + public void MapTypeExtension_ByOptions_WithResolver_AddsTypeExtensionToOptions() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapTypeExtension("mytypeExt", (string a) => 1); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeTypeExtensionDefinition), typeExt); + + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + Assert.IsNotNull(options.RuntimeTemplates.FirstOrDefault(x => x == typeExt)); + Assert.AreEqual("[type]/TwoPropertyObject/mytypeExt", typeExt.Route.Path); + Assert.IsNull(typeExt.ReturnType); + Assert.AreEqual(typeof(int), typeExt.Resolver.Method.ReturnType); + } + + [Test] + public void MapTypeExtension_ByBuilder_WithResolver_AddsTypeExtensionToOptions() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var typeExt = builderMock.MapTypeExtension("mytypeExt", (string a) => 1); + Assert.IsInstanceOf(typeof(IGraphQLRuntimeTypeExtensionDefinition), typeExt); + + Assert.AreEqual(1, options.RuntimeTemplates.Count()); + Assert.IsNotNull(options.RuntimeTemplates.FirstOrDefault(x => x == typeExt)); + Assert.IsNull(typeExt.ReturnType); + Assert.AreEqual(typeof(int), typeExt.Resolver.Method.ReturnType); + } + + [Test] + public void MappedTypeExtension_WhenAllowAnonymousAdded_AddsAnonymousAttribute() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapTypeExtension("mytypeExt", (string a) => 1); + + typeExt.AllowAnonymous(); + + Assert.AreEqual(2, typeExt.Attributes.Count()); + Assert.IsNotNull(typeExt.Attributes.FirstOrDefault(x => x is AllowAnonymousAttribute)); + } + + [Test] + public void MappedTypeExtension_WhenRequireAuthAdded_AddsAuthAttribute() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapTypeExtension("mytypeExt", (string a) => 1); + + typeExt.RequireAuthorization("policy1", "roles1"); + + Assert.AreEqual(2, typeExt.Attributes.Count()); + var attrib = typeExt.Attributes.FirstOrDefault(x => x is AuthorizeAttribute) as AuthorizeAttribute; + Assert.IsNotNull(attrib); + Assert.AreEqual("policy1", attrib.Policy); + Assert.AreEqual("roles1", attrib.Roles); + } + + [Test] + public void MappedTypeExtension_WhenResolverIsChangedWithExplicitType_NewResolverIsUsedAndTypeIsUsed() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapTypeExtension("mytypeExt", (string a) => 1); + Assert.AreEqual(typeof(int), typeExt.Resolver.Method.ReturnType); + + typeExt.AddResolver((string a) => "bob"); + Assert.AreEqual(typeof(string), typeExt.Resolver.Method.ReturnType); + Assert.AreEqual(typeof(decimal), typeExt.ReturnType); + } + + [Test] + public void MappedTypeExtension_WithBatchProcessing_ChangesExecutionMode() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapTypeExtension("mytypeExt", (string a) => 1); + Assert.AreEqual(FieldResolutionMode.PerSourceItem, typeExt.ExecutionMode); + + typeExt.WithBatchProcessing(); + Assert.AreEqual(FieldResolutionMode.Batch, typeExt.ExecutionMode); + + var typeExtensionAttrib = typeExt.Attributes.FirstOrDefault(x => x.GetType() == typeof(BatchTypeExtensionAttribute)); + Assert.IsNotNull(typeExtensionAttrib); + } + + [Test] + public void MappedTypeExtension_AddPossibleTypes_AddsAppropriateAttribute() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapTypeExtension("mytypeExt", (string a) => 1); + typeExt.AddPossibleTypes(typeof(TwoPropertyObjectV2), typeof(TwoPropertyObjectV3)); + + Assert.AreEqual(2, typeExt.Attributes.Count()); + var attrib = typeExt.Attributes.FirstOrDefault(x => x is PossibleTypesAttribute) as PossibleTypesAttribute; + + Assert.AreEqual(2, attrib.PossibleTypes.Count); + Assert.IsNotNull(attrib.PossibleTypes.FirstOrDefault(x => x == typeof(TwoPropertyObjectV2))); + Assert.IsNotNull(attrib.PossibleTypes.FirstOrDefault(x => x == typeof(TwoPropertyObjectV3))); + } + + [Test] + public void MappedTypeExtension_WithoutUnionName_AddsUnionNameToType() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapTypeExtension("mytypeExt", null, (string a) => 1); + + var attrib = typeExt.Attributes.OfType().SingleOrDefault(); + + Assert.IsNull(attrib); + } + + [Test] + public void MappedTypeExtension_WithUnionName0_AddsUnionNameToType() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapTypeExtension("mytypeExt", "myUnion", (string a) => 1); + + var attrib = typeExt.Attributes.OfType().SingleOrDefault(); + + Assert.AreEqual("myUnion", attrib.UnionName); + } + + [Test] + public void MappedTypeExension_SwappingOutResolvers_RemovesUnion() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapTypeExtension("mytypeExt", "myUnion", (string a) => 1); + Assert.AreEqual(1, typeExt.Attributes.Count(x => x is UnionAttribute)); + + // union is removed when resolver is re-declared + typeExt.AddResolver((int a) => 0); + Assert.AreEqual(0, typeExt.Attributes.Count(x => x is UnionAttribute)); + } + + [Test] + public void MappedTypeExension_ViaOptions_WithUnion() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapTypeExtension("mytypeExt", "myUnion", (string a) => 1); + Assert.AreEqual(1, typeExt.Attributes.Count(x => x is UnionAttribute)); + Assert.IsNotNull(typeExt.Resolver); + } + + [Test] + public void MappedTypeExension_ViaBuilder_WithUnion() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var builderMock = Substitute.For(); + builderMock.Options.Returns(options); + + var typeExt = builderMock.MapTypeExtension("mytypeExt", "myUnion", (string a) => 1); + Assert.AreEqual(1, typeExt.Attributes.Count(x => x is UnionAttribute)); + Assert.IsNotNull(typeExt.Resolver); + } + + [Test] + public void MappedTypeExension_NoResolver_CreatesField() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapTypeExtension("mytypeExt"); + Assert.AreEqual(1, typeExt.Attributes.Count()); + + // No Resolver is Set + Assert.IsNull(typeExt.Resolver); + } + + [Test] + public void MappedTypeExension_AddingUnionViaAddResolver_UnionIsApplied() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + // no union + var typeExt = options.MapTypeExtension("path2", (string a) => (int?)1); + + // union added + typeExt.AddResolver("myUnion", () => 0); + Assert.AreEqual(1, typeExt.Attributes.Count(x => x is UnionAttribute)); + Assert.AreEqual("myUnion", typeExt.Attributes.OfType().Single().UnionName); + } + + [Test] + public void MappedTypeExension_AddingResolver_WithExplicitReturnType_WithUnion_UnionIsApplied() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + // no union + var typeExt = options.MapTypeExtension("path2", (string a) => (int?)1); + + // union added + typeExt.AddResolver("myUnion", () => 0); + Assert.AreEqual(1, typeExt.Attributes.Count(x => x is UnionAttribute)); + Assert.AreEqual("myUnion", typeExt.Attributes.OfType().Single().UnionName); + } + + [Test] + public void MappedTypeExension_PossibleTypeSwapping() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var typeExt = options.MapTypeExtension("mytypeExt", (string a) => 1); + typeExt.AddPossibleTypes(typeof(TwoPropertyObject), typeof(TwoPropertyObjectV2), typeof(TwoPropertyObjectV3)); + typeExt.ClearPossibleTypes(); + typeExt.AddPossibleTypes(typeof(TwoPropertyObject), typeof(TwoPropertyObjectV2)); + typeExt.ClearPossibleTypes(); + typeExt.AddPossibleTypes(typeof(TwoPropertyObject)); + + Assert.AreEqual(2, typeExt.Attributes.Count()); + var possibleTypesAttrib = typeExt.Attributes.SingleOrDefault(x => x is PossibleTypesAttribute) as PossibleTypesAttribute; + + Assert.AreEqual(1, possibleTypesAttrib.PossibleTypes.Count); + Assert.IsNotNull(possibleTypesAttrib.PossibleTypes.FirstOrDefault(x => x == typeof(TwoPropertyObject))); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/ResolvedFieldTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/ResolvedFieldTemplateTests.cs new file mode 100644 index 000000000..7c3354b32 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/RuntimeFieldDeclarations/ResolvedFieldTemplateTests.cs @@ -0,0 +1,215 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.RuntimeFieldDeclarations +{ + using System.Linq; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using Microsoft.AspNetCore.Authorization; + using Microsoft.Extensions.DependencyInjection; + using NUnit.Framework; + + [TestFixture] + public class ResolvedFieldTemplateTests + { + [Test] + public void ResolvedField_WhenAllowAnonymousAdded_AddsAnonymousAttribute() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQuery("/path1/path2", (string a) => 1); + + field.AllowAnonymous(); + + Assert.AreEqual(2, field.Attributes.Count()); + Assert.IsNotNull(field.Attributes.FirstOrDefault(x => x is AllowAnonymousAttribute)); + } + + [Test] + public void ResolvedField_WhenAllowAnonymousAdded_AllAuthorizeAreRemoved() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQuery("/path1/path2", (string a) => 1); + field.AddAttribute(new AuthorizeAttribute("policy1")); + Assert.AreEqual(1, field.Attributes.Count(x => x is AuthorizeAttribute)); + + field.AllowAnonymous(); + + Assert.AreEqual(2, field.Attributes.Count()); + Assert.IsNotNull(field.Attributes.FirstOrDefault(x => x is AllowAnonymousAttribute)); + Assert.AreEqual(0, field.Attributes.Count(x => x is AuthorizeAttribute)); + } + + [Test] + public void ResolvedField_WhenNameApplied_NameIsAttchedToFieldDeclaration() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options + .MapQuery("/path1/path2", (string a) => 1) + .WithInternalName("theName"); + + Assert.AreEqual("theName", field.InternalName); + } + + [Test] + public void ResolvedField_WhenRequireAuthAdded_AddsAuthAttribute() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQuery("/path1/path2", (string a) => 1); + + field.RequireAuthorization("policy1", "roles1"); + + Assert.AreEqual(2, field.Attributes.Count()); + + var attrib = field.Attributes.FirstOrDefault(x => x is AuthorizeAttribute) as AuthorizeAttribute; + Assert.IsNotNull(attrib); + Assert.AreEqual("policy1", attrib.Policy); + Assert.AreEqual("roles1", attrib.Roles); + } + + [Test] + public void ResolvedField_WhenRequireAuthAdded_AllowAnonIsRemoved() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQuery("/path1/path2", (string a) => 1); + field.AddAttribute(new AllowAnonymousAttribute()); + Assert.AreEqual(1, field.Attributes.Count(x => x is AllowAnonymousAttribute)); + + field.RequireAuthorization("policy1", "roles1"); + + Assert.AreEqual(2, field.Attributes.Count()); + + var attrib = field.Attributes.FirstOrDefault(x => x is AuthorizeAttribute) as AuthorizeAttribute; + Assert.IsNotNull(attrib); + Assert.AreEqual("policy1", attrib.Policy); + Assert.AreEqual("roles1", attrib.Roles); + + Assert.AreEqual(0, field.Attributes.Count(x => x is AllowAnonymousAttribute)); + } + + [Test] + public void ResolvedField_WhenResolverIsChanged_NewResolverIsUsed() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQuery("/path1/path2", (string a) => 1); + Assert.AreEqual(typeof(int), field.Resolver.Method.ReturnType); + + field.AddResolver((string a) => "bob"); + Assert.AreEqual(typeof(string), field.Resolver.Method.ReturnType); + Assert.IsNull(field.ReturnType); + } + + [Test] + public void ResolvedField_WhenResolverIsChangedWithExplicitType_NewResolverIsUsedAndTypeIsUsed() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQuery("/path1/path2", (string a) => 1); + Assert.AreEqual(typeof(int), field.Resolver.Method.ReturnType); + + field.AddResolver((string a) => "bob"); + Assert.AreEqual(typeof(string), field.Resolver.Method.ReturnType); + Assert.AreEqual(typeof(decimal), field.ReturnType); + } + + [Test] + public void ResolvedField_AddPossibleTypes_AddsAppropriateAttribute() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapMutation("/path1/path2", (string a) => 1); + field.AddPossibleTypes(typeof(TwoPropertyObject), typeof(TwoPropertyObjectV2)); + + Assert.AreEqual(2, field.Attributes.Count()); + var possibleTypesAttrib = field.Attributes.FirstOrDefault(x => x is PossibleTypesAttribute) as PossibleTypesAttribute; + + Assert.AreEqual(2, possibleTypesAttrib.PossibleTypes.Count); + Assert.IsNotNull(possibleTypesAttrib.PossibleTypes.FirstOrDefault(x => x == typeof(TwoPropertyObject))); + Assert.IsNotNull(possibleTypesAttrib.PossibleTypes.FirstOrDefault(x => x == typeof(TwoPropertyObjectV2))); + } + + [Test] + public void ResolvedField_ResolverReturnsNullableT_ItsPreserved() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQuery("/path1/path2", (string a) => (int?)1); + + // nullable int + Assert.AreEqual(typeof(int?), field.Resolver.Method.ReturnType); + } + + [Test] + public void ResolvedField_SwappingOutResolvers_RemovesUnion() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQuery("/path1/path2", "myUnion", (string a) => (int?)1); + Assert.AreEqual(1, field.Attributes.Count(x => x is UnionAttribute)); + + // union is removed when resolver is re-declared + field.AddResolver((int a) => 0); + Assert.AreEqual(0, field.Attributes.Count(x => x is UnionAttribute)); + } + + [Test] + public void ResolvedField_PossibleTypeSwapping() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + var field = options.MapQuery("/path1/path2", (string a) => (int?)1); + field.AddPossibleTypes(typeof(TwoPropertyObject), typeof(TwoPropertyObjectV2), typeof(TwoPropertyObjectV3)); + field.ClearPossibleTypes(); + field.AddPossibleTypes(typeof(TwoPropertyObject), typeof(TwoPropertyObjectV2)); + field.ClearPossibleTypes(); + field.AddPossibleTypes(typeof(TwoPropertyObject)); + + Assert.AreEqual(2, field.Attributes.Count()); + var possibleTypesAttrib = field.Attributes.SingleOrDefault(x => x is PossibleTypesAttribute) as PossibleTypesAttribute; + + Assert.AreEqual(1, possibleTypesAttrib.PossibleTypes.Count); + Assert.IsNotNull(possibleTypesAttrib.PossibleTypes.FirstOrDefault(x => x == typeof(TwoPropertyObject))); + } + + [Test] + public void ResolvedField_AddingUnionViaAddResolver_UnionIsApplied() + { + var services = new ServiceCollection(); + var options = new SchemaOptions(services); + + // no union + var field = options.MapQuery("/path1/path2", (string a) => (int?)1); + + // union added + field.AddResolver("myUnion", () => 0); + + Assert.AreEqual(1, field.Attributes.Count(x => x is UnionAttribute)); + Assert.AreEqual("myUnion", field.Attributes.OfType().Single().UnionName); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/SchemaItemValidators/GraphArgumentValidatorTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/SchemaItemValidators/GraphArgumentValidatorTests.cs new file mode 100644 index 000000000..8ecc6a3fe --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/SchemaItemValidators/GraphArgumentValidatorTests.cs @@ -0,0 +1,88 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.SchemaItemValidators +{ + using System; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.SchemaItemValidators; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Common.Interfaces; + using NSubstitute; + using NUnit.Framework; + + [TestFixture] + public class GraphArgumentValidatorTests + { + private enum EnumNotInSchema + { + value1, + Value2, + } + + [Test] + public void NullArgument_ThrowsCastException() + { + var validator = GraphArgumentValidator.Instance; + + var item = Substitute.For(); + var schema = new GraphSchema(); + + Assert.Throws(() => + { + validator.ValidateOrThrow(item, schema); + }); + } + + [Test] + public void ObjectTypeIsInterface_ThrowsDeclarationException() + { + var validator = GraphArgumentValidator.Instance; + + var item = Substitute.For(); + item.ObjectType.Returns(typeof(ISinglePropertyObject)); + item.Parent.Returns(null as ISchemaItem); + + var schema = new GraphSchema(); + Assert.Throws(() => + { + validator.ValidateOrThrow(item, schema); + }); + } + + [Test] + public void ObjectTypeIsNotInSchema_ThrowsDeclarationException() + { + var validator = GraphArgumentValidator.Instance; + + var item = Substitute.For(); + item.Parent.Returns(null as ISchemaItem); + item.ObjectType.Returns(typeof(TwoPropertyObject)); + item.Name.Returns("theName"); + + var schema = Substitute.For(); + var typesCollection = Substitute.For(); + + // the tested type is not found + typesCollection + .FindGraphType(Arg.Any(), Arg.Any()) + .Returns(null as IGraphType); + + schema.KnownTypes.Returns(typesCollection); + + Assert.Throws(() => + { + validator.ValidateOrThrow(item, schema); + }); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/DirectiveTypeMakerTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/DirectiveTypeMakerTests.cs new file mode 100644 index 000000000..17e04e5b8 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/DirectiveTypeMakerTests.cs @@ -0,0 +1,187 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers +{ + using System; + using System.Linq; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution.Resolvers; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.CommonHelpers; + using GraphQL.AspNet.Tests.Framework; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData; + using NUnit.Framework; + + [TestFixture] + public class DirectiveTypeMakerTests : GraphTypeMakerTestBase + { + [Test] + public void Directive_BasicPropertyCheck() + { + var builder = new TestServerBuilder(); + var server = builder.Build(); + + var template = new GraphDirectiveTemplate(typeof(MultiMethodDirective)); + template.Parse(); + template.ValidateOrThrow(); + + var typeMaker = new DirectiveMaker(server.Schema, new GraphArgumentMaker(server.Schema)); + + var directive = typeMaker.CreateGraphType(template).GraphType as IDirective; + + Assert.AreEqual("multiMethod", directive.Name); + Assert.AreEqual("A Multi Method Directive", directive.Description); + Assert.AreEqual(TypeKind.DIRECTIVE, directive.Kind); + Assert.IsTrue(directive.Publish); + Assert.AreEqual(DirectiveLocation.FIELD | DirectiveLocation.SCALAR, directive.Locations); + Assert.AreEqual(typeof(GraphDirectiveActionResolver), directive.Resolver.GetType()); + + Assert.AreEqual(2, directive.Arguments.Count); + + var arg0 = directive.Arguments.FirstOrDefault(); + var arg1 = directive.Arguments.Skip(1).FirstOrDefault(); + + Assert.IsNotNull(arg0); + Assert.AreEqual("firstArg", arg0.Name); + Assert.AreEqual(typeof(int), arg0.ObjectType); + + Assert.IsNotNull(arg1); + Assert.AreEqual("secondArg", arg1.Name); + Assert.AreEqual(typeof(TwoPropertyObject), arg1.ObjectType); + } + + [Test] + public void Directive_RepeatableAttributeIsSetWhenPresent() + { + var builder = new TestServerBuilder(); + var server = builder.Build(); + + var template = new GraphDirectiveTemplate(typeof(RepeatableDirective)); + template.Parse(); + template.ValidateOrThrow(); + + var typeMaker = new DirectiveMaker(server.Schema, new GraphArgumentMaker(server.Schema)); + + var directive = typeMaker.CreateGraphType(template).GraphType as IDirective; + + Assert.IsTrue(directive.IsRepeatable); + Assert.AreEqual("repeatable", directive.Name); + Assert.AreEqual(TypeKind.DIRECTIVE, directive.Kind); + Assert.IsTrue(directive.Publish); + Assert.AreEqual(DirectiveLocation.SCALAR, directive.Locations); + + Assert.AreEqual(2, directive.Arguments.Count); + + var arg0 = directive.Arguments.FirstOrDefault(); + var arg1 = directive.Arguments.Skip(1).FirstOrDefault(); + + Assert.IsNotNull(arg0); + Assert.AreEqual("firstArg", arg0.Name); + Assert.AreEqual(typeof(int), arg0.ObjectType); + + Assert.IsNotNull(arg1); + Assert.AreEqual("secondArg", arg1.Name); + Assert.AreEqual(typeof(TwoPropertyObject), arg1.ObjectType); + } + + [TestCase( + SchemaArgumentBindingRules.ParametersPreferQueryResolution, + typeof(ArgCheckImplicitSchemaItemDirective), + true)] + [TestCase( + SchemaArgumentBindingRules.ParametersPreferQueryResolution, + typeof(ArgCheckImplicitInjectedItemDirective), + false)] + [TestCase( + SchemaArgumentBindingRules.ParametersPreferQueryResolution, + typeof(ArgCheckExplicitValidSchemaItemDirective), + true)] + [TestCase( + SchemaArgumentBindingRules.ParametersPreferQueryResolution, + typeof(ArgCheckExplicitInjectedItemDirective), + false)] + + [TestCase( + SchemaArgumentBindingRules.ParametersRequireFromServicesDeclaration, + typeof(ArgCheckImplicitSchemaItemDirective), + true)] + [TestCase( + SchemaArgumentBindingRules.ParametersRequireFromServicesDeclaration, + typeof(ArgCheckExplicitValidSchemaItemDirective), + true)] + [TestCase( + SchemaArgumentBindingRules.ParametersRequireFromServicesDeclaration, + typeof(ArgCheckExplicitInjectedItemDirective), + false)] + + [TestCase( + SchemaArgumentBindingRules.ParametersRequireFromGraphQLDeclaration, + typeof(ArgCheckImplicitSchemaItemDirective), + false)] + [TestCase( + SchemaArgumentBindingRules.ParametersRequireFromGraphQLDeclaration, + typeof(ArgCheckImplicitInjectedItemDirective), + false)] + [TestCase( + SchemaArgumentBindingRules.ParametersRequireFromGraphQLDeclaration, + typeof(ArgCheckExplicitValidSchemaItemDirective), + true)] + [TestCase( + SchemaArgumentBindingRules.ParametersRequireFromGraphQLDeclaration, + typeof(ArgCheckExplicitInjectedItemDirective), + false)] + public void ArgInclusionCheck(SchemaArgumentBindingRules bindingRule, Type directiveType, bool shouldBeIncluded) + { + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.DeclarationOptions.ArgumentBindingRule = bindingRule; + }) + .Build(); + + var template = new GraphDirectiveTemplate(directiveType); + template.Parse(); + template.ValidateOrThrow(); + + var typeMaker = new DirectiveMaker(server.Schema, new GraphArgumentMaker(server.Schema)); + + var directive = typeMaker.CreateGraphType(template).GraphType as IDirective; + + Assert.AreEqual(TypeKind.DIRECTIVE, directive.Kind); + + if (shouldBeIncluded) + Assert.AreEqual(1, directive.Arguments.Count); + else + Assert.AreEqual(0, directive.Arguments.Count); + } + + [Test] + public void Directive_InternalName_PropertyCheck() + { + var builder = new TestServerBuilder(); + var server = builder.Build(); + + var factory = server.CreateMakerFactory(); + + var template = new GraphDirectiveTemplate(typeof(DirectiveWithInternalName)); + template.Parse(); + template.ValidateOrThrow(); + + var typeMaker = new DirectiveMaker(server.Schema, new GraphArgumentMaker(server.Schema)); + var directive = typeMaker.CreateGraphType(template).GraphType as IDirective; + + Assert.AreEqual("DirectiveInternalName_33", directive.InternalName); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/EnumGraphTypeMakerTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/EnumGraphTypeMakerTests.cs similarity index 57% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/EnumGraphTypeMakerTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/EnumGraphTypeMakerTests.cs index ce45e0726..8fe357084 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/EnumGraphTypeMakerTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/EnumGraphTypeMakerTests.cs @@ -6,19 +6,21 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers { using System; using System.Linq; using GraphQL.AspNet.Configuration; using GraphQL.AspNet.Configuration.Formatting; - using GraphQL.AspNet.Engine.TypeMakers; using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Engine.TypeMakers.TestData; using GraphQL.AspNet.Tests.Framework; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData; using NUnit.Framework; [TestFixture] @@ -34,12 +36,13 @@ public void Parse_EnumWithUndeclaredValues_WhenConfigRequiresDeclaration_DoesntI .Build() .Schema; - var maker = new EnumGraphTypeMaker(schema); + var maker = new EnumGraphTypeMaker(schema.Configuration); + var template = GraphQLTemplateHelper.CreateEnumTemplate(); - var graphType = maker.CreateGraphType(typeof(EnumWithUndeclaredValues)).GraphType as IEnumGraphType; + var graphType = maker.CreateGraphType(template).GraphType as IEnumGraphType; Assert.AreEqual(2, graphType.Values.Count); - Assert.IsTrue((bool)graphType.Values.ContainsKey("DECLAREDVALUE1")); - Assert.IsTrue((bool)graphType.Values.ContainsKey("VALUE_AWESOME")); + Assert.IsTrue(graphType.Values.ContainsKey("DECLAREDVALUE1")); + Assert.IsTrue(graphType.Values.ContainsKey("VALUE_AWESOME")); } [Test] @@ -52,16 +55,17 @@ public void Parse_EnumWithUndeclaredValues_WhenConfigDoesNotRequireDeclaration_D .Build() .Schema; - var maker = new EnumGraphTypeMaker(schema); - var graphType = maker.CreateGraphType(typeof(EnumWithUndeclaredValues)).GraphType as IEnumGraphType; + var maker = new EnumGraphTypeMaker(schema.Configuration); + var template = GraphQLTemplateHelper.CreateEnumTemplate(); + var graphType = maker.CreateGraphType(template).GraphType as IEnumGraphType; Assert.AreEqual(3, graphType.Values.Count); - Assert.IsTrue((bool)graphType.Values.ContainsKey("DECLAREDVALUE1")); + Assert.IsTrue(graphType.Values.ContainsKey("DECLAREDVALUE1")); - Assert.IsTrue((bool)graphType.Values.ContainsKey("VALUE_AWESOME")); - Assert.IsFalse((bool)graphType.Values.ContainsKey("DECLAREDVALUE2")); + Assert.IsTrue(graphType.Values.ContainsKey("VALUE_AWESOME")); + Assert.IsFalse(graphType.Values.ContainsKey("DECLAREDVALUE2")); - Assert.IsTrue((bool)graphType.Values.ContainsKey("UNDECLAREDVALUE1")); + Assert.IsTrue(graphType.Values.ContainsKey("UNDECLAREDVALUE1")); } [Test] @@ -69,8 +73,10 @@ public void Parse_EnumWithCustomGraphTypeName_YieldsName_InGraphType() { var schema = new GraphSchema(); - var maker = new EnumGraphTypeMaker(schema); - var graphType = maker.CreateGraphType(typeof(EnumWithGraphName)).GraphType as IEnumGraphType; + var maker = new EnumGraphTypeMaker(schema.Configuration); + var template = GraphQLTemplateHelper.CreateEnumTemplate(); + + var graphType = maker.CreateGraphType(template).GraphType as IEnumGraphType; Assert.AreEqual("ValidGraphName", graphType.Name); } @@ -88,8 +94,8 @@ public void CreateGraphType_ParsesAsExpected() var template = GraphQLTemplateHelper.CreateEnumTemplate(); - var maker = new EnumGraphTypeMaker(schema); - var graphType = maker.CreateGraphType(typeof(EnumWithDescriptionOnValues)).GraphType as IEnumGraphType; + var maker = new EnumGraphTypeMaker(schema.Configuration); + var graphType = maker.CreateGraphType(template).GraphType as IEnumGraphType; Assert.IsNotNull(graphType); Assert.AreEqual(template.Name, graphType.Name); @@ -103,12 +109,14 @@ public void AppliedDirectives_TransferFromTemplate() { var schema = new GraphSchema(); - var maker = new EnumGraphTypeMaker(schema); - var graphType = maker.CreateGraphType(typeof(EnumWithDirective)).GraphType as IEnumGraphType; + var template = GraphQLTemplateHelper.CreateEnumTemplate(); + var maker = new EnumGraphTypeMaker(schema.Configuration); + + var graphType = maker.CreateGraphType(template).GraphType as IEnumGraphType; Assert.AreEqual(graphType, graphType.AppliedDirectives.Parent); - var appliedDirective = Enumerable.Single(graphType.AppliedDirectives); + var appliedDirective = graphType.AppliedDirectives.Single(); Assert.IsNotNull(appliedDirective); Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); CollectionAssert.AreEqual(new object[] { 23, "enum arg" }, appliedDirective.ArgumentValues); @@ -119,8 +127,10 @@ public void DirectivesAreTransferedToGraphType() { var schema = new GraphSchema(); - var maker = new EnumGraphTypeMaker(schema); - var graphType = maker.CreateGraphType(typeof(EnumValueWithDirective)).GraphType as IEnumGraphType; + var maker = new EnumGraphTypeMaker(schema.Configuration); + var template = GraphQLTemplateHelper.CreateEnumTemplate(); + + var graphType = maker.CreateGraphType(template).GraphType as IEnumGraphType; Assert.AreEqual(0, graphType.AppliedDirectives.Count); @@ -130,7 +140,7 @@ public void DirectivesAreTransferedToGraphType() Assert.AreEqual(0, value1.AppliedDirectives.Count); Assert.AreEqual(1, value2.AppliedDirectives.Count); - var appliedDirective = Enumerable.FirstOrDefault(value2.AppliedDirectives); + var appliedDirective = value2.AppliedDirectives.FirstOrDefault(); Assert.IsNotNull(appliedDirective); Assert.AreEqual(value2, value2.AppliedDirectives.Parent); Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); @@ -144,9 +154,10 @@ public void EnumValueIsKeyword_ButFormattingDoesNotMatchKeyword_WorksAsExpected( { var schema = new GraphSchema(); - var maker = new EnumGraphTypeMaker(schema); + var maker = new EnumGraphTypeMaker(schema.Configuration); + var template = GraphQLTemplateHelper.CreateGraphTypeTemplate(enumType, TypeKind.ENUM) as IGraphTypeTemplate; - var graphType = maker.CreateGraphType(enumType).GraphType as IEnumGraphType; + var graphType = maker.CreateGraphType(template).GraphType as IEnumGraphType; var value1 = graphType.Values[enumValue]; Assert.IsNotNull(value1); @@ -164,11 +175,12 @@ public void EnumValueIsKeyword_AndFormattingMatchesKeyword_ThrowsException(Type .Build() .Schema; - var maker = new EnumGraphTypeMaker(schema); + var maker = new EnumGraphTypeMaker(schema.Configuration); + var template = GraphQLTemplateHelper.CreateGraphTypeTemplate(enumType, TypeKind.ENUM) as IGraphTypeTemplate; try { - var graphType = maker.CreateGraphType(enumType).GraphType as IEnumGraphType; + var graphType = maker.CreateGraphType(template).GraphType as IEnumGraphType; } catch (GraphTypeDeclarationException) { @@ -177,5 +189,32 @@ public void EnumValueIsKeyword_AndFormattingMatchesKeyword_ThrowsException(Type Assert.Fail($"Expected {nameof(GraphTypeDeclarationException)} exception"); } + + [Test] + public void EnumType_InternalNameCheck() + { + var schema = new GraphSchema(); + + var maker = new EnumGraphTypeMaker(schema.Configuration); + var template = GraphQLTemplateHelper.CreateGraphTypeTemplate(typeof(EnumWithInternalNames), TypeKind.ENUM) as IGraphTypeTemplate; + + var graphType = maker.CreateGraphType(template).GraphType as IEnumGraphType; + + Assert.AreEqual("EnumInternalName", graphType.InternalName); + } + + [Test] + public void EnumValue_InternalNameCheck() + { + var schema = new GraphSchema(); + + var maker = new EnumGraphTypeMaker(schema.Configuration); + var template = GraphQLTemplateHelper.CreateGraphTypeTemplate(typeof(EnumWithInternalNames), TypeKind.ENUM) as IGraphTypeTemplate; + + var graphType = maker.CreateGraphType(template).GraphType as IEnumGraphType; + var value = graphType.Values.Single().Value; + + Assert.AreEqual("Value1InternalName", value.InternalName); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/FieldMaker_InputFieldTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/FieldMaker_InputFieldTests.cs similarity index 74% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/FieldMaker_InputFieldTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/FieldMaker_InputFieldTests.cs index 086feca65..5f46e2f5f 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/FieldMaker_InputFieldTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/FieldMaker_InputFieldTests.cs @@ -7,13 +7,13 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers { using System.Linq; - using GraphQL.AspNet.Engine.TypeMakers; - using GraphQL.AspNet.Tests.Engine.TypeMakers.TestData; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData; using NUnit.Framework; public class FieldMaker_InputFieldTests : GraphTypeMakerTestBase @@ -29,20 +29,20 @@ public void Parse_NotRequiredValueTypePropertyCheck() .Values .Single(x => x.Name == nameof(InputTestObject.NotRequiredValueTypeField)); - var graphField = new GraphFieldMaker(server.Schema).CreateField(fieldTemplate).Field; + var graphField = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)).CreateField(fieldTemplate).Field; Assert.AreEqual("notRequiredValueTypeField", graphField.Name); - Assert.IsFalse((bool)graphField.IsRequired); + Assert.IsFalse(graphField.IsRequired); // even though its not required, its still "Int!" because its a // non-nullable value type Assert.AreEqual("Int!", graphField.TypeExpression.ToString()); Assert.AreEqual("[type]/Input_InputTestObject/NotRequiredValueTypeField", graphField.Route.ToString()); - Assert.AreEqual("NotRequiredValueTypeField", graphField.InternalName); + Assert.AreEqual("NotRequiredValueTypeField", graphField.DeclaredName); Assert.AreEqual(typeof(int), graphField.DeclaredReturnType); Assert.AreEqual(1, graphField.AppliedDirectives.Count); - Assert.AreEqual("DirectiveForNotRequiredValueTypeField", Enumerable.First(graphField.AppliedDirectives).DirectiveName); + Assert.AreEqual("DirectiveForNotRequiredValueTypeField", graphField.AppliedDirectives.First().DirectiveName); } [Test] @@ -56,14 +56,14 @@ public void Parse_RequiredValueTypePropertyCheck() .Values .Single(x => x.Name == nameof(InputTestObject.RequiredValueTypeField)); - var graphField = new GraphFieldMaker(server.Schema).CreateField(fieldTemplate).Field; + var graphField = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)).CreateField(fieldTemplate).Field; Assert.AreEqual("requiredValueTypeField", graphField.Name); - Assert.IsTrue((bool)graphField.IsRequired); + Assert.IsTrue(graphField.IsRequired); Assert.AreEqual("Int!", graphField.TypeExpression.ToString()); Assert.AreEqual("[type]/Input_InputTestObject/RequiredValueTypeField", graphField.Route.ToString()); - Assert.AreEqual("RequiredValueTypeField", graphField.InternalName); + Assert.AreEqual("RequiredValueTypeField", graphField.DeclaredName); Assert.AreEqual(typeof(int), graphField.DeclaredReturnType); Assert.AreEqual(0, graphField.AppliedDirectives.Count); @@ -80,16 +80,18 @@ public void Parse_NotRequiredReferenceTypePropertyCheck() .Values .Single(x => x.Name == nameof(InputTestObject.NotRequiredReferenceTypeField)); - var graphField = new GraphFieldMaker(server.Schema).CreateField(fieldTemplate).Field; + var graphField = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)) + .CreateField(fieldTemplate).Field; Assert.AreEqual("notRequiredReferenceTypeField", graphField.Name); - Assert.IsFalse((bool)graphField.IsRequired); + Assert.IsFalse(graphField.IsRequired); // teh field is not required and the type is a reference type (which is nullable) // meaning the type expression should also be "nullable" Assert.AreEqual("Input_TwoPropertyObject", graphField.TypeExpression.ToString()); Assert.AreEqual("[type]/Input_InputTestObject/NotRequiredReferenceTypeField", graphField.Route.ToString()); - Assert.AreEqual("NotRequiredReferenceTypeField", graphField.InternalName); + Assert.AreEqual("InputTestObject.NotRequiredReferenceTypeField", graphField.InternalName); + Assert.AreEqual("NotRequiredReferenceTypeField", graphField.DeclaredName); Assert.AreEqual(typeof(TwoPropertyObject), graphField.DeclaredReturnType); Assert.AreEqual(0, graphField.AppliedDirectives.Count); @@ -106,18 +108,18 @@ public void Parse_RequiredReferenceTypePropertyCheck() .Values .Single(x => x.Name == nameof(InputTestObject.RequiredReferenceTypeField)); - var graphField = new GraphFieldMaker(server.Schema).CreateField(fieldTemplate).Field; + var graphField = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)).CreateField(fieldTemplate).Field; Assert.AreEqual("requiredReferenceTypeField", graphField.Name); // a nullable type expression can never be "required" - Assert.IsFalse((bool)graphField.IsRequired); + Assert.IsFalse(graphField.IsRequired); // because its marked as required, even though its a reference type (which is nullable) // the type expression is automatically hoisted to be "non-nullable" Assert.AreEqual("Input_TwoPropertyObject", graphField.TypeExpression.ToString()); Assert.AreEqual("[type]/Input_InputTestObject/RequiredReferenceTypeField", graphField.Route.ToString()); - Assert.AreEqual("RequiredReferenceTypeField", graphField.InternalName); + Assert.AreEqual("RequiredReferenceTypeField", graphField.DeclaredName); Assert.AreEqual(typeof(TwoPropertyObject), graphField.DeclaredReturnType); Assert.AreEqual(0, graphField.AppliedDirectives.Count); @@ -134,16 +136,16 @@ public void Parse_RequiredNonNullableReferenceTypePropertyCheck() .Values .Single(x => x.Name == nameof(InputTestObject.RequiredReferenceExplicitNonNullTypeField)); - var graphField = new GraphFieldMaker(server.Schema).CreateField(fieldTemplate).Field; + var graphField = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)).CreateField(fieldTemplate).Field; Assert.AreEqual("requiredReferenceExplicitNonNullTypeField", graphField.Name); - Assert.IsTrue((bool)graphField.IsRequired); + Assert.IsTrue(graphField.IsRequired); // because its marked as required, even though its a reference type (which is nullable) // the type expression is automatically hoisted to be "non-nullable" Assert.AreEqual("Input_TwoPropertyObject!", graphField.TypeExpression.ToString()); Assert.AreEqual("[type]/Input_InputTestObject/RequiredReferenceExplicitNonNullTypeField", graphField.Route.ToString()); - Assert.AreEqual("RequiredReferenceExplicitNonNullTypeField", graphField.InternalName); + Assert.AreEqual("RequiredReferenceExplicitNonNullTypeField", graphField.DeclaredName); Assert.AreEqual(typeof(TwoPropertyObject), graphField.DeclaredReturnType); Assert.AreEqual(0, graphField.AppliedDirectives.Count); @@ -160,15 +162,15 @@ public void Parse_RequiredGraphIdPropertyCheck() .Values .Single(x => x.Name == nameof(InputTestObject.GraphIdRequired)); - var graphField = new GraphFieldMaker(server.Schema).CreateField(fieldTemplate).Field; + var graphField = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)).CreateField(fieldTemplate).Field; Assert.AreEqual("graphIdRequired", graphField.Name); - Assert.IsTrue((bool)graphField.IsRequired); + Assert.IsTrue(graphField.IsRequired); // scalar is required by default Assert.AreEqual("ID!", graphField.TypeExpression.ToString()); Assert.AreEqual("[type]/Input_InputTestObject/GraphIdRequired", graphField.Route.ToString()); - Assert.AreEqual("GraphIdRequired", graphField.InternalName); + Assert.AreEqual("GraphIdRequired", graphField.DeclaredName); Assert.AreEqual(typeof(GraphId), graphField.DeclaredReturnType); Assert.AreEqual(0, graphField.AppliedDirectives.Count); @@ -185,14 +187,14 @@ public void Parse_NotRequiredGraphIdPropertyCheck() .Values .Single(x => x.Name == nameof(InputTestObject.GraphIdNotRequired)); - var graphField = new GraphFieldMaker(server.Schema).CreateField(fieldTemplate).Field; + var graphField = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)).CreateField(fieldTemplate).Field; Assert.AreEqual("graphIdNotRequired", graphField.Name); - Assert.IsFalse((bool)graphField.IsRequired); + Assert.IsFalse(graphField.IsRequired); Assert.AreEqual("ID!", graphField.TypeExpression.ToString()); Assert.AreEqual("[type]/Input_InputTestObject/GraphIdNotRequired", graphField.Route.ToString()); - Assert.AreEqual("GraphIdNotRequired", graphField.InternalName); + Assert.AreEqual("GraphIdNotRequired", graphField.DeclaredName); Assert.AreEqual(typeof(GraphId), graphField.DeclaredReturnType); Assert.AreEqual(0, graphField.AppliedDirectives.Count); @@ -209,17 +211,33 @@ public void Parse_NotRequiredNullableGraphIdPropertyCheck() .Values .Single(x => x.Name == nameof(InputTestObject.GraphIdNullable)); - var graphField = new GraphFieldMaker(server.Schema).CreateField(fieldTemplate).Field; + var graphField = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)).CreateField(fieldTemplate).Field; Assert.AreEqual("graphIdNullable", graphField.Name); - Assert.IsFalse((bool)graphField.IsRequired); + Assert.IsFalse(graphField.IsRequired); Assert.AreEqual("ID", graphField.TypeExpression.ToString()); Assert.AreEqual("[type]/Input_InputTestObject/GraphIdNullable", graphField.Route.ToString()); - Assert.AreEqual("GraphIdNullable", graphField.InternalName); + Assert.AreEqual("GraphIdNullable", graphField.DeclaredName); Assert.AreEqual(typeof(GraphId?), graphField.DeclaredReturnType); Assert.AreEqual(0, graphField.AppliedDirectives.Count); } + + [Test] + public void Parse_InternalName_PropertyCheck() + { + var server = new TestServerBuilder().Build(); + var template = GraphQLTemplateHelper.CreateInputObjectTemplate(); + + var fieldTemplate = template + .FieldTemplates + .Values + .Single(x => x.Name == nameof(InputTestObjectWithInternalName.Prop1)); + + var graphField = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)).CreateField(fieldTemplate).Field; + + Assert.AreEqual("Prop1InternalName", graphField.InternalName); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/FieldMaker_StandardFieldTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/FieldMaker_StandardFieldTests.cs similarity index 53% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/FieldMaker_StandardFieldTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/FieldMaker_StandardFieldTests.cs index 579a6441f..4fb477682 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/FieldMaker_StandardFieldTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/FieldMaker_StandardFieldTests.cs @@ -6,17 +6,23 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers { using System.Linq; - using GraphQL.AspNet.Engine.TypeMakers; + using GraphQL.AspNet.Configuration; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Security; - using GraphQL.AspNet.Tests.Engine.TypeMakers.TestData; + using GraphQL.AspNet.Tests.CommonHelpers; using GraphQL.AspNet.Tests.Framework; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData; + using Microsoft.AspNetCore.Hosting.Server; using NSubstitute; using NUnit.Framework; @@ -60,10 +66,10 @@ public void Parse_PolicyOnController_IsInheritedByField() Assert.AreEqual(1, template.SecurityPolicies.Count()); Assert.AreEqual(0, actionMethod.SecurityPolicies.Count()); - var graphField = new GraphFieldMaker(server.Schema).CreateField(actionMethod).Field; - Assert.AreEqual(1, Enumerable.Count(graphField.SecurityGroups)); + var graphField = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)).CreateField(actionMethod).Field; + Assert.AreEqual(1, graphField.SecurityGroups.Count()); - var group = Enumerable.First(graphField.SecurityGroups); + var group = graphField.SecurityGroups.First(); Assert.AreEqual(template.SecurityPolicies.First(), group.First()); } @@ -80,13 +86,13 @@ public void Parse_PolicyOnController_AndOnMethod_IsInheritedByField_InCorrectOrd Assert.AreEqual(1, template.SecurityPolicies.Count()); Assert.AreEqual(1, actionMethod.SecurityPolicies.Count()); - var graphField = new GraphFieldMaker(server.Schema).CreateField(actionMethod).Field; + var graphField = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)).CreateField(actionMethod).Field; - Assert.AreEqual(2, Enumerable.Count(graphField.SecurityGroups)); + Assert.AreEqual(2, graphField.SecurityGroups.Count()); // ensure policy order of controller -> method - var controllerTemplateGroup = Enumerable.First(graphField.SecurityGroups); - var fieldTemplateGroup = Enumerable.Skip(graphField.SecurityGroups, 1).First(); + var controllerTemplateGroup = graphField.SecurityGroups.First(); + var fieldTemplateGroup = graphField.SecurityGroups.Skip(1).First(); Assert.AreEqual(template.SecurityPolicies.First(), controllerTemplateGroup.First()); Assert.AreEqual(actionMethod.SecurityPolicies.First(), fieldTemplateGroup.First()); } @@ -107,13 +113,12 @@ public void Parse_MethodWithNullableEnum_ParsesCorrectly() Assert.AreEqual(typeof(NullableEnumController.LengthType), arg.ObjectType); Assert.AreEqual(NullableEnumController.LengthType.Yards, arg.DefaultValue); - var graphField = new GraphFieldMaker(server.Schema).CreateField(field).Field; + var graphField = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)).CreateField(field).Field; Assert.IsNotNull(graphField); - var graphArg = Enumerable.FirstOrDefault(graphField.Arguments); + var graphArg = graphField.Arguments.FirstOrDefault(); Assert.IsNotNull(graphArg); Assert.IsEmpty(graphArg.TypeExpression.Wrappers); - Assert.AreEqual(GraphArgumentModifiers.None, graphArg.ArgumentModifiers); } [Test] @@ -123,11 +128,12 @@ public void PropertyGraphField_DefaultValuesCheck() var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); + obj.ObjectType.Returns(typeof(SimplePropertyObject)); var parent = obj; var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.Name)); - var template = new AspNet.Internal.TypeTemplates.PropertyGraphFieldTemplate(parent, propInfo, TypeKind.OBJECT); + var template = new PropertyGraphFieldTemplate(parent, propInfo, TypeKind.OBJECT); template.Parse(); template.ValidateOrThrow(); @@ -140,14 +146,14 @@ public void PropertyGraphField_DefaultValuesCheck() [Test] public void PropertyGraphField_GraphName_OverridesDefaultName() { - var server = new TestServerBuilder().Build(); var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); + obj.ObjectType.Returns(typeof(SimplePropertyObject)); var parent = obj; var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.Age)); - var template = new AspNet.Internal.TypeTemplates.PropertyGraphFieldTemplate(parent, propInfo, TypeKind.OBJECT); + var template = new PropertyGraphFieldTemplate(parent, propInfo, TypeKind.OBJECT); template.Parse(); template.ValidateOrThrow(); @@ -166,11 +172,12 @@ public void PropertyGraphField_DirectivesAreAppliedToCreatedField() var server = new TestServerBuilder().Build(); var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); + obj.ObjectType.Returns(typeof(SimplePropertyObject)); var parent = obj; var propInfo = typeof(ObjectDirectiveTestItem).GetProperty(nameof(ObjectDirectiveTestItem.Prop1)); - var template = new AspNet.Internal.TypeTemplates.PropertyGraphFieldTemplate(parent, propInfo, TypeKind.OBJECT); + var template = new PropertyGraphFieldTemplate(parent, propInfo, TypeKind.OBJECT); template.Parse(); template.ValidateOrThrow(); @@ -179,7 +186,7 @@ public void PropertyGraphField_DirectivesAreAppliedToCreatedField() Assert.AreEqual(1, field.AppliedDirectives.Count); Assert.AreEqual(field, field.AppliedDirectives.Parent); - var appliedDirective = Enumerable.FirstOrDefault(field.AppliedDirectives); + var appliedDirective = field.AppliedDirectives.FirstOrDefault(); Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); CollectionAssert.AreEqual(new object[] { 13, "prop field arg" }, appliedDirective.ArgumentValues); } @@ -190,7 +197,8 @@ public void MethodGraphField_DirectivesAreAppliedToCreatedField() var server = new TestServerBuilder().Build(); var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); + obj.ObjectType.Returns(typeof(ObjectDirectiveTestItem)); var parent = obj; var methodInfo = typeof(ObjectDirectiveTestItem).GetMethod(nameof(ObjectDirectiveTestItem.Method1)); @@ -203,7 +211,7 @@ public void MethodGraphField_DirectivesAreAppliedToCreatedField() Assert.AreEqual(1, field.AppliedDirectives.Count); Assert.AreEqual(field, field.AppliedDirectives.Parent); - var appliedDirective = Enumerable.FirstOrDefault(field.AppliedDirectives); + var appliedDirective = field.AppliedDirectives.FirstOrDefault(); Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); CollectionAssert.AreEqual(new object[] { 14, "method field arg" }, appliedDirective.ArgumentValues); } @@ -214,7 +222,8 @@ public void Arguments_DirectivesAreApplied() var server = new TestServerBuilder().Build(); var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); + obj.ObjectType.Returns(typeof(ObjectDirectiveTestItem)); var parent = obj; var methodInfo = typeof(ObjectDirectiveTestItem).GetMethod(nameof(ObjectDirectiveTestItem.MethodWithArgDirectives)); @@ -231,10 +240,125 @@ public void Arguments_DirectivesAreApplied() Assert.AreEqual(1, arg.AppliedDirectives.Count); Assert.AreEqual(arg, arg.AppliedDirectives.Parent); - var appliedDirective = Enumerable.FirstOrDefault(field.Arguments["arg1"].AppliedDirectives); + var appliedDirective = field.Arguments["arg1"].AppliedDirectives.FirstOrDefault(); Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); CollectionAssert.AreEqual(new object[] { 15, "arg arg" }, appliedDirective.ArgumentValues); } + + [TestCase( + SchemaArgumentBindingRules.ParametersRequireFromServicesDeclaration, + nameof(ObjectWithAttributedFieldArguments.FieldWithExplicitServiceArg), + false)] + [TestCase( + SchemaArgumentBindingRules.ParametersRequireFromGraphQLDeclaration, + nameof(ObjectWithAttributedFieldArguments.FieldWithExplicitServiceArg), + false)] + [TestCase( + SchemaArgumentBindingRules.ParametersPreferQueryResolution, + nameof(ObjectWithAttributedFieldArguments.FieldWithExplicitServiceArg), + false)] + [TestCase( + SchemaArgumentBindingRules.ParametersRequireFromServicesDeclaration, + nameof(ObjectWithAttributedFieldArguments.FieldWithImplicitArgThatShouldBeServiceInjected), + false)] + [TestCase( + SchemaArgumentBindingRules.ParametersRequireFromGraphQLDeclaration, + nameof(ObjectWithAttributedFieldArguments.FieldWithImplicitArgThatShouldBeServiceInjected), + false)] + [TestCase( + SchemaArgumentBindingRules.ParametersPreferQueryResolution, + nameof(ObjectWithAttributedFieldArguments.FieldWithImplicitArgThatShouldBeServiceInjected), + false)] + [TestCase( + SchemaArgumentBindingRules.ParametersRequireFromServicesDeclaration, + nameof(ObjectWithAttributedFieldArguments.FieldWithImplicitArgThatShouldBeInGraph), + true)] + [TestCase( + SchemaArgumentBindingRules.ParametersRequireFromGraphQLDeclaration, + nameof(ObjectWithAttributedFieldArguments.FieldWithImplicitArgThatShouldBeInGraph), + false)] + [TestCase( + SchemaArgumentBindingRules.ParametersPreferQueryResolution, + nameof(ObjectWithAttributedFieldArguments.FieldWithImplicitArgThatShouldBeInGraph), + true)] + public void Arguments_ArgumentBindingRuleTests(SchemaArgumentBindingRules bindingRule, string fieldName, bool argumentShouldBeIncluded) + { + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.DeclarationOptions.ArgumentBindingRule = bindingRule; + }) + .Build(); + + var obj = Substitute.For(); + obj.Route.Returns(new SchemaItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); + obj.ObjectType.Returns(typeof(ObjectWithAttributedFieldArguments)); + + var parent = obj; + var methodInfo = typeof(ObjectWithAttributedFieldArguments).GetMethod(fieldName); + var template = new MethodGraphFieldTemplate(parent, methodInfo, TypeKind.OBJECT); + template.Parse(); + template.ValidateOrThrow(); + + // Fact: The field has no attributes on it but could be included in the schema. + // Fact: The schema is defined to require [FromGraphQL] for things to be included. + // Conclusion: the argument should be ignored. + var maker = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)); + var field = maker.CreateField(template).Field; + + if (argumentShouldBeIncluded) + Assert.AreEqual(1, field.Arguments.Count); + else + Assert.AreEqual(0, field.Arguments.Count); + } + + [Test] + public void InternalName_OnPropField_IsRendered() + { + var server = new TestServerBuilder().Build(); + + var obj = Substitute.For(); + obj.Route.Returns(new SchemaItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); + obj.ObjectType.Returns(typeof(ObjectWithInternalName)); + + var parent = obj; + var methodInfo = typeof(ObjectWithInternalName).GetProperty("Prop1"); + var template = new PropertyGraphFieldTemplate(parent, methodInfo, TypeKind.OBJECT); + template.Parse(); + template.ValidateOrThrow(); + + var maker = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)); + var field = maker.CreateField(template).Field; + + Assert.AreEqual("PropInternalName", field.InternalName); + Assert.AreEqual("[type]/Item0/Prop1", field.Route.Path); + } + + [Test] + public void InternalName_OnMethodField_IsRendered() + { + var server = new TestServerBuilder().Build(); + + var obj = Substitute.For(); + obj.Route.Returns(new SchemaItemPath("[type]/Item0")); + obj.InternalName.Returns("LongItem0"); + obj.InternalName.Returns("Item0"); + obj.ObjectType.Returns(typeof(ObjectWithInternalName)); + + var parent = obj; + var methodInfo = typeof(ObjectWithInternalName).GetMethod("Method1"); + var template = new MethodGraphFieldTemplate(parent, methodInfo, TypeKind.OBJECT); + template.Parse(); + template.ValidateOrThrow(); + + var maker = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)); + var field = maker.CreateField(template).Field; + + Assert.AreEqual("MethodInternalName", field.InternalName); + Assert.AreEqual("[type]/Item0/Method1", field.Route.Path); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/GraphTypeMakerTestBase.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/GraphTypeMakerTestBase.cs similarity index 72% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/GraphTypeMakerTestBase.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/GraphTypeMakerTestBase.cs index 67df5988c..9b39ee539 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/GraphTypeMakerTestBase.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/GraphTypeMakerTestBase.cs @@ -7,15 +7,15 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers { using System; using GraphQL.AspNet.Configuration; - using GraphQL.AspNet.Engine; - using GraphQL.AspNet.Engine.TypeMakers; - using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.CommonHelpers; using GraphQL.AspNet.Tests.Framework; public abstract class GraphTypeMakerTestBase @@ -34,16 +34,21 @@ protected GraphTypeCreationResult MakeGraphType( }); } - var typeMaker = new DefaultGraphTypeMakerProvider(); var testServer = builder.Build(); - var maker = typeMaker.CreateTypeMaker(testServer.Schema, kind); - return maker.CreateGraphType(type); + + var factory = testServer.CreateMakerFactory(); + + var template = factory.MakeTemplate(type, kind); + var maker = factory.CreateTypeMaker(type, kind); + + return maker.CreateGraphType(template); } protected IGraphField MakeGraphField(IGraphFieldTemplate fieldTemplate) { var testServer = new TestServerBuilder().Build(); - var maker = new GraphFieldMaker(testServer.Schema); + var maker = new GraphFieldMaker(testServer.Schema, new GraphArgumentMaker(testServer.Schema)); + return maker.CreateField(fieldTemplate).Field; } } diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/InputObjectGraphTypeMakerTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/InputObjectGraphTypeMakerTests.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/InputObjectGraphTypeMakerTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/InputObjectGraphTypeMakerTests.cs index 803c3841e..01702bbb2 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/InputObjectGraphTypeMakerTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/InputObjectGraphTypeMakerTests.cs @@ -6,18 +6,20 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers { using System.Linq; using GraphQL.AspNet.Configuration; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Engine.TypeMakers.TestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData; using NUnit.Framework; [TestFixture] @@ -40,9 +42,9 @@ public void Parse_FieldAndDependencies_SetCorrectly() Assert.IsNotNull(field); // string, OneMarkedProperty - Assert.AreEqual(2, Enumerable.Count(typeResult.DependentTypes)); - Assert.IsTrue(Enumerable.Any(typeResult.DependentTypes, x => x.Type == typeof(OneMarkedProperty) && x.ExpectedKind == TypeKind.INPUT_OBJECT)); - Assert.IsTrue(Enumerable.Any(typeResult.DependentTypes, x => x.Type == typeof(string) && x.ExpectedKind == TypeKind.SCALAR)); + Assert.AreEqual(2, typeResult.DependentTypes.Count()); + Assert.IsTrue(typeResult.DependentTypes.Any(x => x.Type == typeof(OneMarkedProperty))); + Assert.IsTrue(typeResult.DependentTypes.Any(x => x.Type == typeof(string))); } [Test] @@ -138,8 +140,8 @@ public void InputObject_CreateGraphType_WhenPropertyDelcarationIsRequired_DoesNo var inputType = result.GraphType as IInputObjectGraphType; Assert.IsNotNull(inputType); - Assert.IsTrue(Enumerable.Any(inputType.Fields, x => x.Name == nameof(TypeWithUndeclaredFields.DeclaredProperty))); - Assert.IsFalse(Enumerable.Any(inputType.Fields, x => x.Name == nameof(TypeWithUndeclaredFields.UndeclaredProperty))); + Assert.IsTrue(inputType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFields.DeclaredProperty))); + Assert.IsFalse(inputType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFields.UndeclaredProperty))); } [Test] @@ -149,8 +151,8 @@ public void CreateGraphType_WhenPropertyDelcarationIsNotRequired_DoesIncludeUnde var inputType = result.GraphType as IInputObjectGraphType; Assert.IsNotNull(inputType); - Assert.IsTrue(Enumerable.Any(inputType.Fields, x => x.Name == nameof(TypeWithUndeclaredFields.DeclaredProperty))); - Assert.IsTrue(Enumerable.Any(inputType.Fields, x => x.Name == nameof(TypeWithUndeclaredFields.UndeclaredProperty))); + Assert.IsTrue(inputType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFields.DeclaredProperty))); + Assert.IsTrue(inputType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFields.UndeclaredProperty))); } [Test] @@ -161,8 +163,8 @@ public void CreateGraphType_WhenPropertyDelcarationIsRequired_ButWithGraphTypeOv var objectGraphType = result.GraphType as IInputObjectGraphType; Assert.IsNotNull(objectGraphType); - Assert.IsTrue(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFieldsWithOverride.DeclaredProperty))); - Assert.IsFalse(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFieldsWithOverride.UndeclaredProperty))); + Assert.IsTrue(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFieldsWithOverride.DeclaredProperty))); + Assert.IsFalse(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFieldsWithOverride.UndeclaredProperty))); } [Test] @@ -173,8 +175,8 @@ public void InputObject_CreateGraphType_WhenPropertyDelcarationIsNotRequired_But var inputType = result.GraphType as IInputObjectGraphType; Assert.IsNotNull(inputType); - Assert.IsTrue(Enumerable.Any(inputType.Fields, x => x.Name == nameof(TypeWithUndeclaredFieldsWithOverrideNone.DeclaredProperty))); - Assert.IsTrue(Enumerable.Any(inputType.Fields, x => x.Name == nameof(TypeWithUndeclaredFieldsWithOverrideNone.UndeclaredProperty))); + Assert.IsTrue(inputType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFieldsWithOverrideNone.DeclaredProperty))); + Assert.IsTrue(inputType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFieldsWithOverrideNone.UndeclaredProperty))); } [Test] @@ -196,6 +198,7 @@ public void InputObject_CreateGraphType_WithSelfReferencingObject_ParsesAsExpect [Test] public void InputObject_CreateGraphType_DirectivesAreApplied() { + // config says properties DO require declaration, override on type says it does not var result = this.MakeGraphType(typeof(InputTypeWithDirective), TypeKind.INPUT_OBJECT); var inputType = result.GraphType as IInputObjectGraphType; @@ -203,12 +206,22 @@ public void InputObject_CreateGraphType_DirectivesAreApplied() Assert.AreEqual(1, inputType.AppliedDirectives.Count); Assert.AreEqual(inputType, inputType.AppliedDirectives.Parent); - var appliedDirective = Enumerable.FirstOrDefault(inputType.AppliedDirectives); + var appliedDirective = inputType.AppliedDirectives.FirstOrDefault(); Assert.IsNotNull(appliedDirective); Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); CollectionAssert.AreEqual(new object[] { 44, "input arg" }, appliedDirective.ArgumentValues); } + [Test] + public void InputObject_WithInternalName_HasInternalNameSet() + { + // config says properties DO require declaration, override on type says it does not + var result = this.MakeGraphType(typeof(InputTestObjectWithInternalName), TypeKind.INPUT_OBJECT); + var inputType = result.GraphType as IInputObjectGraphType; + + Assert.AreEqual("InputObjectInternalName", inputType.InternalName); + } + [Test] public void InputObject_CreateGraphType_WithNoFields_ThrowsError() { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/InterfaceTypeMakerTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/InterfaceTypeMakerTests.cs similarity index 79% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/InterfaceTypeMakerTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/InterfaceTypeMakerTests.cs index 779637206..7f519c0bd 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/InterfaceTypeMakerTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/InterfaceTypeMakerTests.cs @@ -6,16 +6,19 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers { using System.Linq; using GraphQL.AspNet.Configuration; - using GraphQL.AspNet.Engine; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.CommonHelpers; using GraphQL.AspNet.Tests.Engine.TypeMakers.TestData; using GraphQL.AspNet.Tests.Framework; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData; using NUnit.Framework; [TestFixture] @@ -26,17 +29,19 @@ public void CreateGraphType_PropertyCheck() { var server = new TestServerBuilder().Build(); var template = GraphQLTemplateHelper.CreateInterfaceTemplate(); - var typeMaker = new DefaultGraphTypeMakerProvider(); - var graphType = typeMaker.CreateTypeMaker(server.Schema, TypeKind.INTERFACE) - .CreateGraphType(typeof(ISimpleInterface)).GraphType as IInterfaceGraphType; + var factory = server.CreateMakerFactory(); + + var typeMaker = new InterfaceGraphTypeMaker(server.Schema.Configuration, factory.CreateFieldMaker()); + + var graphType = typeMaker.CreateGraphType(GraphQLTemplateHelper.CreateGraphTypeTemplate(typeof(ISimpleInterface), TypeKind.INTERFACE)).GraphType as IInterfaceGraphType; Assert.IsNotNull(graphType); Assert.AreEqual(template.Name, graphType.Name); Assert.AreEqual(TypeKind.INTERFACE, graphType.Kind); // Property1, Property2, __typename - Assert.AreEqual(3, Enumerable.Count(graphType.Fields)); + Assert.AreEqual(3, graphType.Fields.Count()); } [Test] @@ -49,7 +54,7 @@ public void CreateGraphType_DirectivesAreApplied() Assert.AreEqual(1, interfaceType.AppliedDirectives.Count); Assert.AreEqual(interfaceType, interfaceType.AppliedDirectives.Parent); - var appliedDirective = Enumerable.FirstOrDefault(interfaceType.AppliedDirectives); + var appliedDirective = interfaceType.AppliedDirectives.FirstOrDefault(); Assert.IsNotNull(appliedDirective); Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); CollectionAssert.AreEqual(new object[] { 58, "interface arg" }, appliedDirective.ArgumentValues); @@ -65,13 +70,13 @@ public void CreateGraphType_DeclaredInterfacesAreCaptured() // __typename and Field3 // only those declared on the interface, not those inherited - Assert.AreEqual(2, Enumerable.Count(interfaceType.Fields)); + Assert.AreEqual(2, interfaceType.Fields.Count()); // should reference the two additional interfaces // ITestInterface1 ITestInterface2 - Assert.AreEqual(2, Enumerable.Count(interfaceType.InterfaceNames)); - Assert.IsTrue(Enumerable.Any(interfaceType.InterfaceNames, x => x == "ITestInterface1")); - Assert.IsTrue(Enumerable.Any(interfaceType.InterfaceNames, x => x == "ITestInterface2")); + Assert.AreEqual(2, interfaceType.InterfaceNames.Count()); + Assert.IsTrue(interfaceType.InterfaceNames.Any(x => x == "ITestInterface1")); + Assert.IsTrue(interfaceType.InterfaceNames.Any(x => x == "ITestInterface2")); } [Test] @@ -133,6 +138,21 @@ public void CreateGraphType_WhenMethodOnBaseInterfaceIsExplicitlyDeclared_IsNotI Assert.IsTrue(objectType.Fields.Any(x => string.Equals(x.Name, Constants.ReservedNames.TYPENAME_FIELD))); } + [Test] + public void InternalName_OnInterfaceGraphType_IsRendered() + { + var result = this.MakeGraphType( + typeof(IInterfaceWithInternalName), + TypeKind.INTERFACE, + TemplateDeclarationRequirements.None); + + var objectType = result.GraphType as IInterfaceGraphType; + + // inherited and declared method field should not be counted + Assert.IsNotNull(objectType); + Assert.AreEqual("Interface_Internal_Name", objectType.InternalName); + } + [Test] public void CreateGraphType_WithNoFields_ThrowsError() { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/ObjectGraphTypeMakerTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/ObjectGraphTypeMakerTests.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/ObjectGraphTypeMakerTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/ObjectGraphTypeMakerTests.cs index 90d7e622b..82ff1f629 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/ObjectGraphTypeMakerTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/ObjectGraphTypeMakerTests.cs @@ -6,7 +6,8 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers { using System.Linq; using GraphQL.AspNet.Configuration; @@ -15,10 +16,13 @@ namespace GraphQL.AspNet.Tests.Engine.TypeMakers using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.CommonHelpers; using GraphQL.AspNet.Tests.Engine.TypeMakers.TestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData; using NUnit.Framework; [TestFixture] @@ -41,13 +45,14 @@ public void Object_CreateGraphType_WithSelfReferencingObject_ParsesAsExpected() } [Test] - public void Interface_CreateGraphType_ParsesCorrectly() + public void Object_CreateGraphType_ParsesCorrectly() { var server = new TestServerBuilder(TestOptions.UseCodeDeclaredNames).Build(); - var template = GraphQLTemplateHelper.CreateGraphTypeTemplate(); - var typeMaker = new DefaultGraphTypeMakerProvider(); + var template = GraphQLTemplateHelper.CreateGraphTypeTemplate(TypeKind.OBJECT); - var objectGraphType = typeMaker.CreateTypeMaker(server.Schema, TypeKind.OBJECT).CreateGraphType(typeof(TypeCreationItem)).GraphType as IObjectGraphType; + var objectGraphType = new GraphTypeMakerFactory(server.Schema) + .CreateTypeMaker(typeof(TypeCreationItem)) + .CreateGraphType(template).GraphType as IObjectGraphType; Assert.IsNotNull(objectGraphType); Assert.AreEqual(nameof(TypeCreationItem), objectGraphType.Name); @@ -118,8 +123,8 @@ public void CreateGraphType_WhenMethodDelcarationIsRequired_DoesNotIncludeUndecl var objectGraphType = this.MakeGraphType(typeof(TypeWithUndeclaredFields), TypeKind.OBJECT, TemplateDeclarationRequirements.Method).GraphType as IObjectGraphType; Assert.IsNotNull(objectGraphType); - Assert.IsTrue(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFields.DeclaredMethod))); - Assert.IsFalse(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFields.UndeclaredMethod))); + Assert.IsTrue(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFields.DeclaredMethod))); + Assert.IsFalse(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFields.UndeclaredMethod))); } [Test] @@ -128,8 +133,8 @@ public void CreateGraphType_WhenMethodDelcarationIsNotRequired_DoesIncludeUndecl var objectGraphType = this.MakeGraphType(typeof(TypeWithUndeclaredFields), TypeKind.OBJECT, TemplateDeclarationRequirements.None).GraphType as IObjectGraphType; Assert.IsNotNull(objectGraphType); - Assert.IsTrue(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFields.DeclaredMethod))); - Assert.IsTrue(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFields.UndeclaredMethod))); + Assert.IsTrue(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFields.DeclaredMethod))); + Assert.IsTrue(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFields.UndeclaredMethod))); } [Test] @@ -138,8 +143,8 @@ public void CreateGraphType_AsObject_WhenPropertyDelcarationIsRequired_DoesNotIn var objectGraphType = this.MakeGraphType(typeof(TypeWithUndeclaredFields), TypeKind.OBJECT, TemplateDeclarationRequirements.Property).GraphType as IObjectGraphType; Assert.IsNotNull(objectGraphType); - Assert.IsTrue(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFields.DeclaredProperty))); - Assert.IsFalse(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFields.UndeclaredProperty))); + Assert.IsTrue(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFields.DeclaredProperty))); + Assert.IsFalse(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFields.UndeclaredProperty))); } [Test] @@ -148,8 +153,8 @@ public void CreateGraphType_AsObject_WhenPropertyDelcarationIsNotRequired_DoesIn var objectGraphType = this.MakeGraphType(typeof(TypeWithUndeclaredFields), TypeKind.OBJECT, TemplateDeclarationRequirements.None).GraphType as IObjectGraphType; Assert.IsNotNull(objectGraphType); - Assert.IsTrue(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFields.DeclaredProperty))); - Assert.IsTrue(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFields.UndeclaredProperty))); + Assert.IsTrue(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFields.DeclaredProperty))); + Assert.IsTrue(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFields.UndeclaredProperty))); } [Test] @@ -158,8 +163,8 @@ public void CreateGraphType_AsObject_WhenMethodDelcarationIsRequired_ButWithGrap var objectGraphType = this.MakeGraphType(typeof(TypeWithUndeclaredFieldsWithOverride), TypeKind.OBJECT, TemplateDeclarationRequirements.None).GraphType as IObjectGraphType; Assert.IsNotNull(objectGraphType); - Assert.IsTrue(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFieldsWithOverride.DeclaredMethod))); - Assert.IsFalse(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFieldsWithOverride.UndeclaredMethod))); + Assert.IsTrue(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFieldsWithOverride.DeclaredMethod))); + Assert.IsFalse(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFieldsWithOverride.UndeclaredMethod))); } [Test] @@ -168,8 +173,8 @@ public void CreateGraphType_AsObject_WhenMethodDelcarationIsNotRequired_ButWithG var objectGraphType = this.MakeGraphType(typeof(TypeWithUndeclaredFieldsWithOverrideNone), TypeKind.OBJECT, TemplateDeclarationRequirements.Method).GraphType as IObjectGraphType; Assert.IsNotNull(objectGraphType); - Assert.IsTrue(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFieldsWithOverrideNone.DeclaredMethod))); - Assert.IsTrue(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFieldsWithOverrideNone.UndeclaredMethod))); + Assert.IsTrue(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFieldsWithOverrideNone.DeclaredMethod))); + Assert.IsTrue(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFieldsWithOverrideNone.UndeclaredMethod))); } [Test] @@ -178,8 +183,8 @@ public void CreateGraphType_AsObject_WhenPropertyDelcarationIsRequired_ButWithGr var objectGraphType = this.MakeGraphType(typeof(TypeWithUndeclaredFieldsWithOverride), TypeKind.OBJECT, TemplateDeclarationRequirements.None).GraphType as IObjectGraphType; Assert.IsNotNull(objectGraphType); - Assert.IsTrue(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFields.DeclaredProperty))); - Assert.IsFalse(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFields.UndeclaredProperty))); + Assert.IsTrue(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFields.DeclaredProperty))); + Assert.IsFalse(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFields.UndeclaredProperty))); } [Test] @@ -188,8 +193,8 @@ public void CreateGraphType_AsObject_WhenPropertyDelcarationIsNotRequired_ButWit var objectGraphType = this.MakeGraphType(typeof(TypeWithUndeclaredFieldsWithOverrideNone), TypeKind.OBJECT, TemplateDeclarationRequirements.Property).GraphType as IObjectGraphType; Assert.IsNotNull(objectGraphType); - Assert.IsTrue(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFields.DeclaredProperty))); - Assert.IsTrue(Enumerable.Any(objectGraphType.Fields, x => x.Name == nameof(TypeWithUndeclaredFields.UndeclaredProperty))); + Assert.IsTrue(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFields.DeclaredProperty))); + Assert.IsTrue(objectGraphType.Fields.Any(x => x.Name == nameof(TypeWithUndeclaredFields.UndeclaredProperty))); } [Test] @@ -216,7 +221,7 @@ public void CreateGraphType_DirectivesAreApplied() Assert.AreEqual(1, objectType.AppliedDirectives.Count); Assert.AreEqual(objectType, objectType.AppliedDirectives.Parent); - var appliedDirective = Enumerable.FirstOrDefault(objectType.AppliedDirectives); + var appliedDirective = objectType.AppliedDirectives.FirstOrDefault(); Assert.IsNotNull(appliedDirective); Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); CollectionAssert.AreEqual(new object[] { 12, "object directive" }, appliedDirective.ArgumentValues); @@ -281,6 +286,19 @@ public void CreateGraphType_AsObject_WhenMethodOnBaseObjectIsExplicitlyDeclared_ Assert.IsTrue(objectType.Fields.Any(x => string.Equals(x.Name, Constants.ReservedNames.TYPENAME_FIELD))); } + [Test] + public void InternalName_OnObjectGraphType_IsRendered() + { + var result = this.MakeGraphType( + typeof(ObjectWithInternalName), + TypeKind.OBJECT, + TemplateDeclarationRequirements.None); + + var objectType = result.GraphType as IObjectGraphType; + + Assert.AreEqual("Object_Internal_Name", objectType.InternalName); + } + [Test] public void CreateGraphType_AsObject_WhenOverloadedMethodIncludesOnce_IsCorrectlyCreated() { diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/ScalarGraphTypeMakerTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/ScalarGraphTypeMakerTests.cs new file mode 100644 index 000000000..fc7e4881f --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/ScalarGraphTypeMakerTests.cs @@ -0,0 +1,88 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers +{ + using System; + using GraphQL.AspNet.Configuration.Formatting; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Tests.Framework; + using NUnit.Framework; + + [TestFixture] + public class ScalarGraphTypeMakerTests + { + [Test] + public void RegisteredScalarIsReturned() + { + var schema = new GraphSchema(); + var maker = new ScalarGraphTypeMaker(schema.Configuration); + + var result = maker.CreateGraphType(GraphQLTemplateHelper.CreateGraphTypeTemplate(typeof(int))); + Assert.IsNotNull(result?.GraphType); + Assert.AreEqual(typeof(int), result.ConcreteType); + } + + [Test] + public void NullType_ReturnsNullResult() + { + var schema = new GraphSchema(); + var maker = new ScalarGraphTypeMaker(schema.Configuration); + + var result = maker.CreateGraphType(null); + Assert.IsNull(result); + } + + // fixed name scalars will never be renamed + [TestCase(typeof(int), GraphNameFormatStrategy.UpperCase, "Int")] + [TestCase(typeof(int), GraphNameFormatStrategy.LowerCase, "Int")] + [TestCase(typeof(int), GraphNameFormatStrategy.ProperCase, "Int")] + [TestCase(typeof(float), GraphNameFormatStrategy.UpperCase, "Float")] + [TestCase(typeof(float), GraphNameFormatStrategy.LowerCase, "Float")] + [TestCase(typeof(float), GraphNameFormatStrategy.ProperCase, "Float")] + [TestCase(typeof(string), GraphNameFormatStrategy.UpperCase, "String")] + [TestCase(typeof(string), GraphNameFormatStrategy.LowerCase, "String")] + [TestCase(typeof(string), GraphNameFormatStrategy.ProperCase, "String")] + [TestCase(typeof(bool), GraphNameFormatStrategy.UpperCase, "Boolean")] + [TestCase(typeof(bool), GraphNameFormatStrategy.LowerCase, "Boolean")] + [TestCase(typeof(bool), GraphNameFormatStrategy.ProperCase, "Boolean")] + [TestCase(typeof(GraphId), GraphNameFormatStrategy.UpperCase, "ID")] + [TestCase(typeof(GraphId), GraphNameFormatStrategy.LowerCase, "ID")] + [TestCase(typeof(GraphId), GraphNameFormatStrategy.ProperCase, "ID")] + + // non-fixed scalars will rename themselves + [TestCase(typeof(decimal), GraphNameFormatStrategy.UpperCase, "DECIMAL")] + [TestCase(typeof(decimal), GraphNameFormatStrategy.LowerCase, "decimal")] + [TestCase(typeof(decimal), GraphNameFormatStrategy.ProperCase, "Decimal")] + [TestCase(typeof(Uri), GraphNameFormatStrategy.UpperCase, "URI")] + [TestCase(typeof(Uri), GraphNameFormatStrategy.LowerCase, "uri")] + [TestCase(typeof(Uri), GraphNameFormatStrategy.ProperCase, "Uri")] + public void BuiltInScalar_ObeysNamingRulesOfConfig(Type builtInScalarType, GraphNameFormatStrategy strategy, string expectedName) + { + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.DeclarationOptions.GraphNamingFormatter + = new GraphNameFormatter(strategy); + }) + .Build(); + + var maker = new ScalarGraphTypeMaker(server.Schema.Configuration); + + var template = new ScalarGraphTypeTemplate(builtInScalarType); + template.Parse(); + template.ValidateOrThrow(); + + var result = maker.CreateGraphType(template); + Assert.AreEqual(expectedName, result.GraphType.Name); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ArgCheckExplicitInjectedItemDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ArgCheckExplicitInjectedItemDirective.cs new file mode 100644 index 000000000..437c4920b --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ArgCheckExplicitInjectedItemDirective.cs @@ -0,0 +1,26 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Directives; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Schemas.TypeSystem; + using Microsoft.AspNetCore.Mvc; + + public class ArgCheckExplicitInjectedItemDirective : GraphDirective + { + [DirectiveLocations(DirectiveLocation.SCALAR)] + public IGraphActionResult ForTypeSystem([FromServices] ITestInterface1 arg1) + { + return null; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ArgCheckExplicitInvalidSchemaItemDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ArgCheckExplicitInvalidSchemaItemDirective.cs new file mode 100644 index 000000000..e591cb1c7 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ArgCheckExplicitInvalidSchemaItemDirective.cs @@ -0,0 +1,25 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Directives; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Schemas.TypeSystem; + + public class ArgCheckExplicitInvalidSchemaItemDirective : GraphDirective + { + [DirectiveLocations(DirectiveLocation.SCALAR)] + public IGraphActionResult ForTypeSystem([FromGraphQL] ITestInterface1 arg1) + { + return null; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ArgCheckExplicitValidSchemaItemDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ArgCheckExplicitValidSchemaItemDirective.cs new file mode 100644 index 000000000..901a5beb9 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ArgCheckExplicitValidSchemaItemDirective.cs @@ -0,0 +1,26 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Directives; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class ArgCheckExplicitValidSchemaItemDirective : GraphDirective + { + [DirectiveLocations(DirectiveLocation.SCALAR)] + public IGraphActionResult ForTypeSystem([FromGraphQL] TwoPropertyObject arg1) + { + return null; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ArgCheckImplicitInjectedItemDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ArgCheckImplicitInjectedItemDirective.cs new file mode 100644 index 000000000..7a6e2c36c --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ArgCheckImplicitInjectedItemDirective.cs @@ -0,0 +1,26 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Directives; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class ArgCheckImplicitInjectedItemDirective : GraphDirective + { + [DirectiveLocations(DirectiveLocation.SCALAR)] + public IGraphActionResult ForTypeSystem(ITestInterface1 arg1) + { + return null; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ArgCheckImplicitSchemaItemDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ArgCheckImplicitSchemaItemDirective.cs new file mode 100644 index 000000000..21f95e6b7 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ArgCheckImplicitSchemaItemDirective.cs @@ -0,0 +1,26 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Directives; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class ArgCheckImplicitSchemaItemDirective : GraphDirective + { + [DirectiveLocations(DirectiveLocation.SCALAR)] + public IGraphActionResult ForTypeSystem(TwoPropertyObject secondArg) + { + return null; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ComplexInputObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ComplexInputObject.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ComplexInputObject.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ComplexInputObject.cs index a31e8eb39..220ec23ef 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ComplexInputObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ComplexInputObject.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ControllerWithInternalNames.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ControllerWithInternalNames.cs new file mode 100644 index 000000000..a6de7fadc --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ControllerWithInternalNames.cs @@ -0,0 +1,30 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class ControllerWithInternalNames : GraphController + { + [QueryRoot(InternalName = "ActionWithInternalName")] + public TwoPropertyObject ActionField() + { + return null; + } + + [TypeExtension(typeof(TwoPropertyObject), "field1", InternalName = "TypeExtensionInternalName")] + public int TypeExpressionField() + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/CustomScalarWithDirectives.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/CustomScalarWithDirectives.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/CustomScalarWithDirectives.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/CustomScalarWithDirectives.cs index 19fe71b53..ec5cc1862 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/CustomScalarWithDirectives.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/CustomScalarWithDirectives.cs @@ -7,14 +7,14 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using System; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Common; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas.TypeSystem.Scalars; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [ApplyDirective(typeof(DirectiveWithArgs), 87, "scalar arg")] public class CustomScalarWithDirectives : ScalarGraphTypeBase @@ -37,7 +37,5 @@ public override object Serialize(object item) } public override ScalarValueType ValueType => ScalarValueType.String; - - public override TypeCollection OtherKnownTypes { get; } = new TypeCollection(); } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/DirectiveWithArgs.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/DirectiveWithArgs.cs similarity index 90% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/DirectiveWithArgs.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/DirectiveWithArgs.cs index 8d8d7ab4e..6a1b022e0 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/DirectiveWithArgs.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/DirectiveWithArgs.cs @@ -6,7 +6,8 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Directives; diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/DirectiveWithInternalName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/DirectiveWithInternalName.cs new file mode 100644 index 000000000..612c50806 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/DirectiveWithInternalName.cs @@ -0,0 +1,26 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Directives; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Schemas.TypeSystem; + + [GraphType(InternalName = "DirectiveInternalName_33")] + public class DirectiveWithInternalName : GraphDirective + { + [DirectiveLocations(DirectiveLocation.AllTypeSystemLocations)] + public IGraphActionResult Execute() + { + return null; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumValueWithDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumValueWithDirective.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumValueWithDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumValueWithDirective.cs index bff6bc0a6..873b0eeed 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumValueWithDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumValueWithDirective.cs @@ -6,7 +6,8 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithDescriptionOnValues.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithDescriptionOnValues.cs similarity index 88% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithDescriptionOnValues.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithDescriptionOnValues.cs index 08b50e74c..4de779d8d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithDescriptionOnValues.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithDescriptionOnValues.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using System.ComponentModel; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithDirective.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithDirective.cs index ede4913c9..24d7511df 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithDirective.cs @@ -6,7 +6,8 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithGraphName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithGraphName.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithGraphName.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithGraphName.cs index af2c95d94..c8d9ed395 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithGraphName.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithGraphName.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithInternalNames.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithInternalNames.cs new file mode 100644 index 000000000..42822e986 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithInternalNames.cs @@ -0,0 +1,20 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData +{ + using GraphQL.AspNet.Attributes; + + [GraphType(InternalName = "EnumInternalName")] + public enum EnumWithInternalNames + { + [GraphEnumValue(InternalName = "Value1InternalName")] + Value1, + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithUndeclaredValues.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithUndeclaredValues.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithUndeclaredValues.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithUndeclaredValues.cs index d57c25ff0..e23e298bf 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithUndeclaredValues.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithUndeclaredValues.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithValueOfFalseKeyword.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithValueOfFalseKeyword.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithValueOfFalseKeyword.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithValueOfFalseKeyword.cs index 6be5452e4..c0b12e3cf 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithValueOfFalseKeyword.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithValueOfFalseKeyword.cs @@ -6,7 +6,8 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithValueOfNullKeyword.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithValueOfNullKeyword.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithValueOfNullKeyword.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithValueOfNullKeyword.cs index 78d0bb56c..a317fd1dd 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithValueOfNullKeyword.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithValueOfNullKeyword.cs @@ -6,7 +6,8 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithValueOfTrueKeyword.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithValueOfTrueKeyword.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithValueOfTrueKeyword.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithValueOfTrueKeyword.cs index 4bd4af325..48eb95310 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithValueOfTrueKeyword.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithValueOfTrueKeyword.cs @@ -6,7 +6,8 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/IInterfaceWithInternalName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/IInterfaceWithInternalName.cs new file mode 100644 index 000000000..4bf9c20fe --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/IInterfaceWithInternalName.cs @@ -0,0 +1,19 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData +{ + using GraphQL.AspNet.Attributes; + + [GraphType(InternalName = "Interface_Internal_Name")] + public interface IInterfaceWithInternalName + { + int Prop1 { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/ISimpleInterface.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ISimpleInterface.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/ISimpleInterface.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ISimpleInterface.cs index 71dcae0fa..0a39ea48e 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/ISimpleInterface.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ISimpleInterface.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.InterfaceTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ITestInterface1.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ITestInterface1.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ITestInterface1.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ITestInterface1.cs index d86a8785b..40f862a74 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ITestInterface1.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ITestInterface1.cs @@ -6,7 +6,8 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { public interface ITestInterface1 { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ITestInterface2.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ITestInterface2.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ITestInterface2.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ITestInterface2.cs index 756061274..299cc7659 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ITestInterface2.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ITestInterface2.cs @@ -6,7 +6,8 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { public interface ITestInterface2 { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ITestInterfaceForDeclaredInterfaces.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ITestInterfaceForDeclaredInterfaces.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ITestInterfaceForDeclaredInterfaces.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ITestInterfaceForDeclaredInterfaces.cs index 2f31db3c3..3368591b2 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ITestInterfaceForDeclaredInterfaces.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ITestInterfaceForDeclaredInterfaces.cs @@ -6,7 +6,8 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { public interface ITestInterfaceForDeclaredInterfaces : ITestInterface1, ITestInterface2 { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/IUnionTestDataItem.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/IUnionTestDataItem.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/IUnionTestDataItem.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/IUnionTestDataItem.cs index d12e499a7..6ac7fdaaa 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/IUnionTestDataItem.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/IUnionTestDataItem.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { public interface IUnionTestDataItem { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InputTestObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InputTestObject.cs similarity index 90% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InputTestObject.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InputTestObject.cs index 2bcbf1d39..f992800a8 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InputTestObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InputTestObject.cs @@ -7,12 +7,12 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using System.ComponentModel.DataAnnotations; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class InputTestObject { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InputTestObjectWithDefaultFieldValues.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InputTestObjectWithDefaultFieldValues.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InputTestObjectWithDefaultFieldValues.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InputTestObjectWithDefaultFieldValues.cs index 35a5809f9..ee7bc5cfe 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InputTestObjectWithDefaultFieldValues.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InputTestObjectWithDefaultFieldValues.cs @@ -7,10 +7,10 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using System.ComponentModel.DataAnnotations; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class InputTestObjectWithDefaultFieldValues { diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InputTestObjectWithInternalName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InputTestObjectWithInternalName.cs new file mode 100644 index 000000000..46fcd7190 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InputTestObjectWithInternalName.cs @@ -0,0 +1,20 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData +{ + using GraphQL.AspNet.Attributes; + + [GraphType(InternalName = "InputObjectInternalName")] + public class InputTestObjectWithInternalName + { + [GraphField(InternalName = "Prop1InternalName")] + public int Prop1 { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InputTypeWithDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InputTypeWithDirective.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InputTypeWithDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InputTypeWithDirective.cs index ff5a9cfe6..44289e234 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InputTypeWithDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InputTypeWithDirective.cs @@ -6,7 +6,8 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/InterfaceThatInheritsDeclaredMethodField.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InterfaceThatInheritsDeclaredMethodField.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/InterfaceThatInheritsDeclaredMethodField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InterfaceThatInheritsDeclaredMethodField.cs index 99d5a17b5..048fbeae7 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/InterfaceThatInheritsDeclaredMethodField.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InterfaceThatInheritsDeclaredMethodField.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.InterfaceTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { public interface InterfaceThatInheritsDeclaredMethodField : InterfaceWithDeclaredInterfaceField { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InterfaceThatInheritsUndeclaredMethodField.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InterfaceThatInheritsUndeclaredMethodField.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InterfaceThatInheritsUndeclaredMethodField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InterfaceThatInheritsUndeclaredMethodField.cs index db4791e7f..167417811 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InterfaceThatInheritsUndeclaredMethodField.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InterfaceThatInheritsUndeclaredMethodField.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { public interface InterfaceThatInheritsUndeclaredMethodField : InterfaceWithUndeclaredInterfaceField { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InterfaceWithDeclaredInterfaceField.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InterfaceWithDeclaredInterfaceField.cs similarity index 88% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InterfaceWithDeclaredInterfaceField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InterfaceWithDeclaredInterfaceField.cs index 5119d69a1..3c47c9c2d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InterfaceWithDeclaredInterfaceField.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InterfaceWithDeclaredInterfaceField.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InterfaceWithDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InterfaceWithDirective.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InterfaceWithDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InterfaceWithDirective.cs index b9e57b696..aaddd1011 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InterfaceWithDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InterfaceWithDirective.cs @@ -6,7 +6,8 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/InterfaceWithUndeclaredInterfaceField.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InterfaceWithUndeclaredInterfaceField.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/InterfaceWithUndeclaredInterfaceField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InterfaceWithUndeclaredInterfaceField.cs index c8469182d..a9425b8fa 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/InterfaceWithUndeclaredInterfaceField.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InterfaceWithUndeclaredInterfaceField.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.InterfaceTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { public interface InterfaceWithUndeclaredInterfaceField { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/MultiMethodDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/MultiMethodDirective.cs similarity index 88% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/MultiMethodDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/MultiMethodDirective.cs index abef67a8c..74224ea21 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/MultiMethodDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/MultiMethodDirective.cs @@ -6,14 +6,15 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using System.ComponentModel; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Directives; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [Description("A Multi Method Directive")] public class MultiMethodDirective : GraphDirective diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/NullableEnumController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/NullableEnumController.cs similarity index 90% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/NullableEnumController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/NullableEnumController.cs index 43cbfec2d..1df09798a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/NullableEnumController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/NullableEnumController.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ObjectDirectiveTestItem.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectDirectiveTestItem.cs similarity index 92% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ObjectDirectiveTestItem.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectDirectiveTestItem.cs index 6d251d183..f00833fc2 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ObjectDirectiveTestItem.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectDirectiveTestItem.cs @@ -6,7 +6,8 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithAttributedFieldArguments.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithAttributedFieldArguments.cs new file mode 100644 index 000000000..ad0ca1336 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithAttributedFieldArguments.cs @@ -0,0 +1,37 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Common.Interfaces; + using Microsoft.AspNetCore.Mvc; + + public class ObjectWithAttributedFieldArguments + { + [GraphField] + public int FieldWithExplicitServiceArg([FromServices] int arg1) + { + return 0; + } + + [GraphField] + public int FieldWithImplicitArgThatShouldBeServiceInjected(ISinglePropertyObject arg1) + { + return 0; + } + + [GraphField] + public int FieldWithImplicitArgThatShouldBeInGraph(TwoPropertyObject arg1) + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ObjectWithDeclaredMethodField.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithDeclaredMethodField.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ObjectWithDeclaredMethodField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithDeclaredMethodField.cs index 74be406cd..bf477954b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ObjectWithDeclaredMethodField.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithDeclaredMethodField.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ObjectWithInheritedDeclaredMethodField.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithInheritedDeclaredMethodField.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ObjectWithInheritedDeclaredMethodField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithInheritedDeclaredMethodField.cs index 6fa45f95e..52b19222d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ObjectWithInheritedDeclaredMethodField.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithInheritedDeclaredMethodField.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { public class ObjectWithInheritedDeclaredMethodField : ObjectWithDeclaredMethodField { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ObjectWithInheritedUndeclaredMethodField.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithInheritedUndeclaredMethodField.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ObjectWithInheritedUndeclaredMethodField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithInheritedUndeclaredMethodField.cs index df004d7d2..6eb8b51dc 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ObjectWithInheritedUndeclaredMethodField.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithInheritedUndeclaredMethodField.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { public class ObjectWithInheritedUndeclaredMethodField : ObjectWithUndeclaredMethodField { diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithInternalName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithInternalName.cs new file mode 100644 index 000000000..8362f4181 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithInternalName.cs @@ -0,0 +1,26 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData +{ + using GraphQL.AspNet.Attributes; + + [GraphType(InternalName = "Object_Internal_Name")] + public class ObjectWithInternalName + { + [GraphField(InternalName = "PropInternalName")] + public int Prop1 { get; set; } + + [GraphField(InternalName = "MethodInternalName")] + public int Method1() + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ObjectWithUndeclaredMethodField.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithUndeclaredMethodField.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ObjectWithUndeclaredMethodField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithUndeclaredMethodField.cs index 1bd934e10..b082f9c30 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ObjectWithUndeclaredMethodField.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ObjectWithUndeclaredMethodField.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { public class ObjectWithUndeclaredMethodField { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/OneMarkedProperty.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/OneMarkedProperty.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/OneMarkedProperty.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/OneMarkedProperty.cs index f0f52b796..d9834f680 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/OneMarkedProperty.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/OneMarkedProperty.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/PropAuthorizeAttribute.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/PropAuthorizeAttribute.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/PropAuthorizeAttribute.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/PropAuthorizeAttribute.cs index 5ae51dc05..c403f6893 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/PropAuthorizeAttribute.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/PropAuthorizeAttribute.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using System; using Microsoft.AspNetCore.Authorization; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/RepeatableDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/RepeatableDirective.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/RepeatableDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/RepeatableDirective.cs index 067bc1957..c9bd738bf 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/RepeatableDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/RepeatableDirective.cs @@ -6,13 +6,15 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { + using System.Threading; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Directives; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [Repeatable] public class RepeatableDirective : GraphDirective diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/SecuredController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/SecuredController.cs similarity index 91% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/SecuredController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/SecuredController.cs index bf8d6b103..d472ce2da 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/SecuredController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/SecuredController.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/SelfReferencingObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/SelfReferencingObject.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/SelfReferencingObject.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/SelfReferencingObject.cs index f65318b8d..84e80058e 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/SelfReferencingObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/SelfReferencingObject.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/SimplePropertyObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/SimplePropertyObject.cs similarity index 93% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/SimplePropertyObject.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/SimplePropertyObject.cs index ec6523f95..e6b1e5bbc 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/SimplePropertyObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/SimplePropertyObject.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using System.Collections.Generic; using System.ComponentModel; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/TypeCreationItem.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/TypeCreationItem.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/TypeCreationItem.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/TypeCreationItem.cs index 908f40eaa..be12a0286 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/TypeCreationItem.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/TypeCreationItem.cs @@ -7,10 +7,10 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class TypeCreationItem { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/TypeWithUndeclaredFields.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/TypeWithUndeclaredFields.cs similarity index 93% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/TypeWithUndeclaredFields.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/TypeWithUndeclaredFields.cs index 39dd6f2cf..6c728f9cf 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/TypeWithUndeclaredFields.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/TypeWithUndeclaredFields.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/TypeWithUndeclaredFieldsWithOverride.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/TypeWithUndeclaredFieldsWithOverride.cs similarity index 94% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/TypeWithUndeclaredFieldsWithOverride.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/TypeWithUndeclaredFieldsWithOverride.cs index 717f8c7fb..ebc897397 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/TypeWithUndeclaredFieldsWithOverride.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/TypeWithUndeclaredFieldsWithOverride.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Configuration; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/TypeWithUndeclaredFieldsWithOverrideNone.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/TypeWithUndeclaredFieldsWithOverrideNone.cs similarity index 94% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/TypeWithUndeclaredFieldsWithOverrideNone.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/TypeWithUndeclaredFieldsWithOverrideNone.cs index 4e1d7fc73..b37f0b90c 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/TypeWithUndeclaredFieldsWithOverrideNone.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/TypeWithUndeclaredFieldsWithOverrideNone.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Configuration; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/UnionDataA.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionDataA.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/UnionDataA.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionDataA.cs index c1d333bd1..027a25f05 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/UnionDataA.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionDataA.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { public class UnionDataA : IUnionTestDataItem { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionDataB.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionDataB.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionDataB.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionDataB.cs index 5fb9bc69d..2861a7a82 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionDataB.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionDataB.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { public class UnionDataB : IUnionTestDataItem { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionProxyWithDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionProxyWithDirective.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionProxyWithDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionProxyWithDirective.cs index 5b537beaa..65e88d031 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionProxyWithDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionProxyWithDirective.cs @@ -6,7 +6,8 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Schemas.TypeSystem; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionProxyWithInvalidDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionProxyWithInvalidDirective.cs similarity index 81% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionProxyWithInvalidDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionProxyWithInvalidDirective.cs index 511cd55a5..c4bfa0200 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionProxyWithInvalidDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionProxyWithInvalidDirective.cs @@ -6,11 +6,12 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; // not a directive [ApplyDirective(typeof(TwoPropertyObject), 121, "union directive")] diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionTestController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionTestController.cs similarity index 94% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionTestController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionTestController.cs index 682179986..08c97963d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionTestController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionTestController.cs @@ -7,14 +7,14 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using System; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphRoute("unionTest")] public class UnionTestController : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionTestProxy.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionTestProxy.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionTestProxy.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionTestProxy.cs index 221e09654..0acbcdd63 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionTestProxy.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionTestProxy.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData { using System; using System.Collections.Generic; @@ -27,6 +27,8 @@ public class UnionTestProxy : IGraphUnionProxy public bool Publish { get; set; } = true; + public string InternalName => "Internal_BobUnion"; + public Type MapType(Type runtimeObjectType) { return runtimeObjectType; diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionWithInternalName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionWithInternalName.cs new file mode 100644 index 000000000..a2318a5cf --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionWithInternalName.cs @@ -0,0 +1,22 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData +{ + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class UnionWithInternalName : GraphUnionProxy + { + public UnionWithInternalName() + : base("TestUnion", "TestUnionInternalName", typeof(TwoPropertyObject)) + { + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionWithNoInternalName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionWithNoInternalName.cs new file mode 100644 index 000000000..0bcc3a328 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionWithNoInternalName.cs @@ -0,0 +1,22 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData +{ + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class UnionWithNoInternalName : GraphUnionProxy + { + public UnionWithNoInternalName() + : base("TestUnion", typeof(TwoPropertyObject)) + { + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/UnionTypeMakerTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/UnionTypeMakerTests.cs similarity index 57% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/UnionTypeMakerTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/UnionTypeMakerTests.cs index 0f7603629..b44188dbd 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/UnionTypeMakerTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/UnionTypeMakerTests.cs @@ -6,17 +6,18 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers { using System; using System.Linq; - using GraphQL.AspNet.Engine.TypeMakers; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Engine.TypeMakers.TestData; using GraphQL.AspNet.Tests.Framework; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData; using NUnit.Framework; [TestFixture] @@ -29,7 +30,7 @@ public void ActionTemplate_CreateUnionType_PropertyCheck() schema.SetNoAlterationConfiguration(); var action = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(UnionTestController.TwoTypeUnion)); - var unionResult = new UnionGraphTypeMaker(schema).CreateUnionFromProxy(action.UnionProxy); + var unionResult = new UnionGraphTypeMaker(schema.Configuration).CreateUnionFromProxy(action.UnionProxy); var union = unionResult.GraphType as IUnionGraphType; Assert.IsNotNull(union); @@ -37,13 +38,13 @@ public void ActionTemplate_CreateUnionType_PropertyCheck() Assert.AreEqual("FragmentData", union.Name); Assert.IsNull(union.Description); - Assert.AreEqual(2, Enumerable.Count(union.PossibleGraphTypeNames)); - Assert.AreEqual(2, Enumerable.Count(union.PossibleConcreteTypes)); + Assert.AreEqual(2, union.PossibleGraphTypeNames.Count()); + Assert.AreEqual(2, union.PossibleConcreteTypes.Count()); - Assert.IsTrue((bool)union.PossibleGraphTypeNames.Contains(nameof(UnionDataA))); - Assert.IsTrue((bool)union.PossibleGraphTypeNames.Contains(nameof(UnionDataB))); - Assert.IsTrue((bool)union.PossibleConcreteTypes.Contains(typeof(UnionDataA))); - Assert.IsTrue((bool)union.PossibleConcreteTypes.Contains(typeof(UnionDataB))); + Assert.IsTrue(union.PossibleGraphTypeNames.Contains(nameof(UnionDataA))); + Assert.IsTrue(union.PossibleGraphTypeNames.Contains(nameof(UnionDataB))); + Assert.IsTrue(union.PossibleConcreteTypes.Contains(typeof(UnionDataA))); + Assert.IsTrue(union.PossibleConcreteTypes.Contains(typeof(UnionDataB))); } [Test] @@ -52,7 +53,7 @@ public void UnionProxyWithDirectives_DirectivesAreApplied() var schema = new GraphSchema(); schema.SetNoAlterationConfiguration(); - var maker = new UnionGraphTypeMaker(schema); + var maker = new UnionGraphTypeMaker(schema.Configuration); var unionResult = maker.CreateUnionFromProxy(new UnionProxyWithDirective()); var unionType = unionResult.GraphType as IUnionGraphType; @@ -60,7 +61,7 @@ public void UnionProxyWithDirectives_DirectivesAreApplied() Assert.AreEqual(1, unionType.AppliedDirectives.Count); Assert.AreEqual(unionType, unionType.AppliedDirectives.Parent); - var appliedDirective = Enumerable.FirstOrDefault(unionType.AppliedDirectives); + var appliedDirective = unionType.AppliedDirectives.FirstOrDefault(); Assert.IsNotNull(appliedDirective); Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); CollectionAssert.AreEqual(new object[] { 121, "union directive" }, appliedDirective.ArgumentValues); @@ -71,7 +72,7 @@ public void UnionProxyWithDirectives_InvalidDirectiveType_ThrowsException() { var schema = new GraphSchema(); schema.SetNoAlterationConfiguration(); - var maker = new UnionGraphTypeMaker(schema); + var maker = new UnionGraphTypeMaker(schema.Configuration); Assert.Throws(() => { @@ -84,10 +85,32 @@ public void NullProxy_YieldsNullGraphType() { var schema = new GraphSchema(); schema.SetNoAlterationConfiguration(); - var maker = new UnionGraphTypeMaker(schema); + var maker = new UnionGraphTypeMaker(schema.Configuration); var unionType = maker.CreateUnionFromProxy(null); Assert.IsNull(unionType); } + + [Test] + public void Proxy_WithCustomInternalName_IsSetToSaidName() + { + var schema = new GraphSchema(); + schema.SetNoAlterationConfiguration(); + var maker = new UnionGraphTypeMaker(schema.Configuration); + + var unionType = maker.CreateUnionFromProxy(new UnionWithInternalName()).GraphType as IUnionGraphType; + Assert.AreEqual("TestUnionInternalName", unionType.InternalName); + } + + [Test] + public void Proxy_WithNoInternalName_IsSetToProxyName() + { + var schema = new GraphSchema(); + schema.SetNoAlterationConfiguration(); + var maker = new UnionGraphTypeMaker(schema.Configuration); + + var unionType = maker.CreateUnionFromProxy(new UnionWithNoInternalName()).GraphType as IUnionGraphType; + Assert.AreEqual("UnionWithNoInternalName", unionType.InternalName); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionMethodTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionMethodTemplateTests.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionMethodTemplateTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionMethodTemplateTests.cs index c9ea0122f..58cfedc25 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionMethodTemplateTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionMethodTemplateTests.cs @@ -7,20 +7,19 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates { using System.Linq; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - using GraphQL.AspNet.Tests.Internal.Templating.ActionTestData; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; using NSubstitute; using NUnit.Framework; @@ -31,7 +30,7 @@ private ControllerActionGraphFieldTemplate CreateActionTemplate where TControllerType : GraphController { var mockController = Substitute.For(); - mockController.InternalFullName.Returns(typeof(TControllerType).Name); + mockController.InternalName.Returns(typeof(TControllerType).Name); mockController.Route.Returns(new SchemaItemPath("path0")); mockController.Name.Returns("path0"); mockController.ObjectType.Returns(typeof(TControllerType)); @@ -39,7 +38,6 @@ private ControllerActionGraphFieldTemplate CreateActionTemplate var methodInfo = typeof(TControllerType).GetMethod(actionName); var action = new ControllerActionGraphFieldTemplate(mockController, methodInfo); action.Parse(); - action.ValidateOrThrow(); return action; } @@ -56,9 +54,9 @@ public void ActionTemplate_Parse_BasicPropertySets() Assert.AreEqual(typeof(OneMethodController), action.Parent.ObjectType); Assert.AreEqual(SchemaItemCollections.Query, action.Route.RootCollection); Assert.AreEqual("[query]/path0/path1", action.Route.Path); - Assert.AreEqual($"{nameof(OneMethodController)}.{nameof(OneMethodController.MethodWithBasicAttribtributes)}", action.InternalFullName); - Assert.AreEqual(methodInfo.ReflectedType, ((IGraphFieldResolverMethod)action).Parent.ObjectType); - Assert.AreEqual("path0", ((IGraphFieldResolverMethod)action).Parent.Name); + Assert.AreEqual($"{nameof(OneMethodController)}.{nameof(OneMethodController.MethodWithBasicAttribtributes)}", action.InternalName); + Assert.AreEqual(methodInfo.ReflectedType, action.Parent.ObjectType); + Assert.AreEqual("path0", action.Parent.Name); Assert.AreEqual(methodInfo, action.Method); Assert.AreEqual(0, action.Arguments.Count); Assert.IsFalse(action.Route.IsTopLevelField); @@ -69,6 +67,7 @@ public void ActionTemplate_Parse_BasicPropertySets() public void ActionTemplate_Parse_MethodMarkedAsOperationIsAssignedARootPath() { var action = this.CreateActionTemplate(nameof(ContainerController.RootMethod)); + action.ValidateOrThrow(); Assert.AreEqual(SchemaItemCollections.Query, action.Route.RootCollection); Assert.AreEqual(0, action.Arguments.Count); @@ -80,6 +79,7 @@ public void ActionTemplate_Parse_MethodMarkedAsOperationIsAssignedARootPath() public void ActionTemplate_Parse_WithValidDeclaredUnion_ParsesCorrectly() { var action = this.CreateActionTemplate(nameof(UnionTestController.TwoTypeUnion)); + action.ValidateOrThrow(); Assert.AreEqual(SchemaItemCollections.Query, action.Route.RootCollection); Assert.IsNotNull(action.UnionProxy); @@ -96,6 +96,7 @@ public void ActionTemplate_Parse_WithValidDeclaredUnion_ParsesCorrectly() public void ActionTemplate_Parse_WithValidDeclaredUnionViaProxy_UsesProxy() { var action = this.CreateActionTemplate(nameof(UnionTestController.UnionViaProxy)); + action.ValidateOrThrow(); Assert.AreEqual(SchemaItemCollections.Query, action.Route.RootCollection); Assert.IsNotNull(action.UnionProxy); @@ -110,7 +111,8 @@ public void ActionTemplate_Parse_ThrowsException(string methodName) { Assert.Throws(() => { - this.CreateActionTemplate(methodName); + var template = this.CreateActionTemplate(methodName); + template.ValidateOrThrow(); }); } @@ -120,15 +122,17 @@ public void ActionTemplate_NegativeComplexityValue_ThrowsException() Assert.Throws(() => { var action = this.CreateActionTemplate(nameof(ComplexityValueCheckController.ReturnsAnInt)); + action.ValidateOrThrow(); }); } [Test] - public void ActionTemplate_ReturnTypeOfActionResult_ThrowsException() + public void ActionTemplate_ReturnTypeOfActionResult_WithoutDeclaredReturnType_ThrowsException() { Assert.Throws(() => { - this.CreateActionTemplate(nameof(ActionResultReturnTypeController.ActionResultMethod)); + var action = this.CreateActionTemplate(nameof(ActionResultReturnTypeController.ActionResultMethod)); + action.ValidateOrThrow(); }); } @@ -136,14 +140,16 @@ public void ActionTemplate_ReturnTypeOfActionResult_ThrowsException() public void ActionTemplate_ReturnTypeOfActionResult_WithDeclaredDataType_RendersCorrectly() { var action = this.CreateActionTemplate(nameof(ActionResultReturnTypeController.ActionResultMethodWithDeclaredReturnType)); + action.ValidateOrThrow(); Assert.AreEqual(typeof(TwoPropertyObject), action.ObjectType); } [Test] - public void ActionTemplate_ReturnTypeOfActionResult_WithDeclaredListDataType_RendersOptionsCorrectly() + public void ActionTemplate_ReturnTypeOfActionResult_WithDeclaredListDataType_RendersObjectTypeCorrectly() { var action = this.CreateActionTemplate(nameof(ActionResultReturnTypeController.ActionResultMethodWithListReturnType)); + action.ValidateOrThrow(); Assert.AreEqual(typeof(TwoPropertyObject), action.ObjectType); Assert.IsTrue(action.TypeExpression.IsListOfItems); @@ -168,17 +174,16 @@ public void ActionTemplate_ReturnTypeOfActionResult_WithDeclaredDataType_ThatHas action.ValidateOrThrow(); Assert.AreEqual(typeof(CustomNamedItem), action.ObjectType); - Assert.AreEqual("AnAwesomeName", action.TypeExpression.TypeName); + Assert.AreEqual("Type", action.TypeExpression.TypeName); } [Test] - public void ActionTemplate_ReturnTypeOfActionResult_WithDeclaredDataType_DiffersFromMethodReturn_ThrowsException() + public void ActionTemplate_WithDeclaredDataType_ThatDiffersFromMethodReturn_ThrowsException() { - Assert.Throws(() => + var ex = Assert.Throws(() => { var action = this.CreateActionTemplate( - nameof(ActionResultReturnTypeController - .ActionResultMethodWithDeclaredReturnTypeAndMethodReturnType)); + nameof(ActionResultReturnTypeController.MethodWithDeclaredReturnTypeAndMethodReturnType)); action.ValidateOrThrow(); }); } @@ -187,6 +192,7 @@ public void ActionTemplate_ReturnTypeOfActionResult_WithDeclaredDataType_Differs public void ActionTemplate_ReturnTypeOfInterface_WithPossibleTypesAttribute_RetrieveDependentTypes_ReturnsAllTypes() { var action = this.CreateActionTemplate(nameof(InterfaceReturnTypeController.RetrieveData)); + action.ValidateOrThrow(); var types = action.RetrieveRequiredTypes(); Assert.IsNotNull(types); @@ -201,6 +207,7 @@ public void ActionTemplate_ReturnTypeOfInterface_WithPossibleTypesAttribute_Retr public void ActionTemplate_ReturnTypeOfInterface_WithoutPossibleTypesAttribute_DoesntFail_ReturnsOnlyInterface() { var action = this.CreateActionTemplate(nameof(InterfaceReturnTypeController.RetrieveDataNoAttributeDeclared)); + action.ValidateOrThrow(); var types = action.RetrieveRequiredTypes(); Assert.IsNotNull(types); @@ -215,6 +222,7 @@ public void ActionTemplate_ReturnTypeOfInterface_WithPossibleTypesAttribute_ButT Assert.Throws(() => { var action = this.CreateActionTemplate(nameof(InterfaceReturnTypeController.RetrieveDataInvalidPossibleType)); + action.ValidateOrThrow(); }); } @@ -224,6 +232,7 @@ public void ActionTemplate_ReturnTypeOfInterface_WithPossibleTypesAttribute_ButT Assert.Throws(() => { var action = this.CreateActionTemplate(nameof(InterfaceReturnTypeController.RetrieveDataPossibleTypeIsInterface)); + action.ValidateOrThrow(); }); } @@ -233,6 +242,7 @@ public void ActionTemplate_ReturnTypeOfInterface_WithPossibleTypesAttribute_ButT Assert.Throws(() => { var action = this.CreateActionTemplate(nameof(InterfaceReturnTypeController.RetrieveDataPossibleTypeIsScalar)); + action.ValidateOrThrow(); }); } @@ -242,6 +252,7 @@ public void ActionTemplate_ReturnTypeOfInterface_WithPossibleTypesAttribute_ButT Assert.Throws(() => { var action = this.CreateActionTemplate(nameof(InterfaceReturnTypeController.RetrieveDataPossibleTypeIsAStruct)); + action.ValidateOrThrow(); }); } @@ -249,6 +260,7 @@ public void ActionTemplate_ReturnTypeOfInterface_WithPossibleTypesAttribute_ButT public void ActionTemplate_ArrayOnInputParameter_RendersFine() { var action = this.CreateActionTemplate(nameof(ArrayInputMethodController.AddData)); + action.ValidateOrThrow(); var types = action.RetrieveRequiredTypes(); Assert.IsNotNull(types); @@ -264,6 +276,7 @@ public void ActionTemplate_ArrayOnInputParameter_RendersFine() public void Parse_AssignedDirective_IsTemplatized() { var action = this.CreateActionTemplate(nameof(ActionMethodWithDirectiveController.Execute)); + action.ValidateOrThrow(); Assert.AreEqual(1, action.AppliedDirectives.Count()); @@ -271,5 +284,14 @@ public void Parse_AssignedDirective_IsTemplatized() Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); Assert.AreEqual(new object[] { 202, "controller action arg" }, appliedDirective.Arguments); } + + [Test] + public void Parse_InternalName_IsAssignedCorrectly() + { + var action = this.CreateActionTemplate(nameof(ActionMethodWithInternalNameController.Execute)); + action.ValidateOrThrow(); + + Assert.AreEqual("Internal_Action_Name_37", action.InternalName); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ActionMethodWithDirectiveController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ActionMethodWithDirectiveController.cs similarity index 78% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ActionMethodWithDirectiveController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ActionMethodWithDirectiveController.cs index 03606b8b7..438b3a26c 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ActionMethodWithDirectiveController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ActionMethodWithDirectiveController.cs @@ -6,11 +6,11 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; public class ActionMethodWithDirectiveController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ActionMethodWithInternalNameController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ActionMethodWithInternalNameController.cs new file mode 100644 index 000000000..0e6f7d67f --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ActionMethodWithInternalNameController.cs @@ -0,0 +1,23 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; + + public class ActionMethodWithInternalNameController : GraphController + { + [Query(InternalName = "Internal_Action_Name_37")] + public int Execute() + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ActionResultReturnTypeController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ActionResultReturnTypeController.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ActionResultReturnTypeController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ActionResultReturnTypeController.cs index 89257b75a..5d41c28ea 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ActionResultReturnTypeController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ActionResultReturnTypeController.cs @@ -7,14 +7,13 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { using System.Collections.Generic; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphRoute("path0")] public class ActionResultReturnTypeController : GraphController @@ -44,7 +43,7 @@ public IGraphActionResult ActionResultMethodWithListReturnTypeAndOptions() } [Query("path4", typeof(TwoPropertyObject))] - public TwoPropertyObjectV2 ActionResultMethodWithDeclaredReturnTypeAndMethodReturnType() + public TwoPropertyObjectV2 MethodWithDeclaredReturnTypeAndMethodReturnType() { return null; } diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ArrayInputMethodController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ArrayInputMethodController.cs similarity index 80% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ArrayInputMethodController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ArrayInputMethodController.cs index 15d3a1da6..ad630eb98 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ArrayInputMethodController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ArrayInputMethodController.cs @@ -7,11 +7,11 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ArrayInputMethodController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ComplexityValueCheckController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ComplexityValueCheckController.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ComplexityValueCheckController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ComplexityValueCheckController.cs index 774aedef7..7244db342 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ComplexityValueCheckController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ComplexityValueCheckController.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ContainerController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ContainerController.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ContainerController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ContainerController.cs index 7dffe95fb..0cecd177a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ContainerController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ContainerController.cs @@ -7,13 +7,13 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { using System.ComponentModel; using System.Threading.Tasks; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphRoute("path0")] public class ContainerController : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/CustomNamedItem.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/CustomNamedItem.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/CustomNamedItem.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/CustomNamedItem.cs index 3d55db3a7..f50372523 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/CustomNamedItem.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/CustomNamedItem.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ITestItem.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ITestItem.cs similarity index 81% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ITestItem.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ITestItem.cs index fac10e20a..4e3515542 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ITestItem.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ITestItem.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { public interface ITestItem { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/IUnionTestDataItem.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/IUnionTestDataItem.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/IUnionTestDataItem.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/IUnionTestDataItem.cs index e53bd61e2..8a35eda2b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/IUnionTestDataItem.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/IUnionTestDataItem.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { public interface IUnionTestDataItem { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/InterfaceReturnTypeController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/InterfaceReturnTypeController.cs similarity index 90% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/InterfaceReturnTypeController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/InterfaceReturnTypeController.cs index a8fcc76bf..2bc7d1280 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/InterfaceReturnTypeController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/InterfaceReturnTypeController.cs @@ -7,11 +7,11 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class InterfaceReturnTypeController : GraphController { @@ -20,7 +20,7 @@ public struct MyStruct public int Data1 { get; } } - [Query] + [Query("field")] [PossibleTypes(typeof(TestItemA), typeof(TestItemB))] public ITestItem RetrieveData() { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/OneMethodController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/OneMethodController.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/OneMethodController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/OneMethodController.cs index 39680972c..04f84f8a8 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/OneMethodController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/OneMethodController.cs @@ -7,12 +7,12 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { using System.ComponentModel; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphRoute("path0")] public class OneMethodController : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ScalarInUnionController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ScalarInUnionController.cs similarity index 88% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ScalarInUnionController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ScalarInUnionController.cs index 98ebd0fcf..005adc77d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/ScalarInUnionController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/ScalarInUnionController.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { using System; using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/TestItemA.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/TestItemA.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/TestItemA.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/TestItemA.cs index da307edee..6d72ed2e1 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/TestItemA.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/TestItemA.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { public class TestItemA : ITestItem { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/TestItemB.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/TestItemB.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/TestItemB.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/TestItemB.cs index e1b6b1701..cffd177a8 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/TestItemB.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/TestItemB.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { public class TestItemB : ITestItem { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionDataA.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/UnionDataA.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionDataA.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/UnionDataA.cs index 08c52abc8..b891b5227 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/UnionDataA.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/UnionDataA.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { public class UnionDataA : IUnionTestDataItem { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/UnionDataB.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/UnionDataB.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/UnionDataB.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/UnionDataB.cs index ce2ae0721..1fa57e14a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/UnionDataB.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/UnionDataB.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { public class UnionDataB : IUnionTestDataItem { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/UnionTestController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/UnionTestController.cs similarity index 92% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/UnionTestController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/UnionTestController.cs index 3f634e127..84fae65f2 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/UnionTestController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/UnionTestController.cs @@ -7,14 +7,13 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { using System; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphRoute("unionTest")] public class UnionTestController : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/UnionTestProxy.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/UnionTestProxy.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/UnionTestProxy.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/UnionTestProxy.cs index e6521469a..56e56df71 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/UnionTestProxy.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/UnionTestProxy.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ActionTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ActionTestData { using System; using System.Collections.Generic; @@ -17,6 +17,8 @@ public class UnionTestProxy : IGraphUnionProxy { public string Name { get; set; } = "BobUnion"; + public string InternalName { get; } = "BobUnionInternal"; + public string Description { get; set; } = "This is the Bob union"; public HashSet Types { get; } = new HashSet() diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/AppliedDirectiveTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/AppliedDirectiveTemplateTests.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/AppliedDirectiveTemplateTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/AppliedDirectiveTemplateTests.cs index 2371c1ef4..7ef74085e 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/AppliedDirectiveTemplateTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/AppliedDirectiveTemplateTests.cs @@ -6,13 +6,15 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates { using System; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal.TypeTemplates; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; + + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; using NSubstitute; using NUnit.Framework; @@ -23,7 +25,7 @@ public class AppliedDirectiveTemplateTests public void PropertyCheck() { var owner = Substitute.For(); - owner.InternalFullName.Returns("OWNER"); + owner.InternalName.Returns("OWNER"); var template = new AppliedDirectiveTemplate( owner, typeof(DirectiveWithArgs), @@ -41,7 +43,7 @@ public void PropertyCheck() public void NulLTemplateType_ThrowsException() { var owner = Substitute.For(); - owner.InternalFullName.Returns("OWNER"); + owner.InternalName.Returns("OWNER"); var template = new AppliedDirectiveTemplate( owner, null as Type, @@ -59,7 +61,7 @@ public void NulLTemplateType_ThrowsException() public void NullName_ThrowsException() { var owner = Substitute.For(); - owner.InternalFullName.Returns("OWNER"); + owner.InternalName.Returns("OWNER"); var template = new AppliedDirectiveTemplate( owner, null as string, @@ -77,7 +79,7 @@ public void NullName_ThrowsException() public void EmptyName_ThrowsException() { var owner = Substitute.For(); - owner.InternalFullName.Returns("OWNER"); + owner.InternalName.Returns("OWNER"); var template = new AppliedDirectiveTemplate( owner, string.Empty, @@ -95,7 +97,7 @@ public void EmptyName_ThrowsException() public void NonDirectiveType_ThrowsException() { var owner = Substitute.For(); - owner.InternalFullName.Returns("OWNER"); + owner.InternalName.Returns("OWNER"); var template = new AppliedDirectiveTemplate( owner, typeof(AppliedDirectiveTemplateTests), diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ArgumentTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ArgumentTemplateTests.cs similarity index 73% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ArgumentTemplateTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ArgumentTemplateTests.cs index 04b52cb47..f2bf5f17c 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ArgumentTemplateTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ArgumentTemplateTests.cs @@ -7,8 +7,9 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates { + using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -16,17 +17,19 @@ namespace GraphQL.AspNet.Tests.Internal.Templating using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Tests.CommonHelpers; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; - using GraphQL.AspNet.Tests.Internal.Templating.ParameterTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ParameterTestData; using NSubstitute; using NUnit.Framework; [TestFixture] public class ArgumentTemplateTests { - private AspNet.Internal.TypeTemplates.GraphArgumentTemplate ExtractParameterTemplate(string paramName, out ParameterInfo paramInfo) + private GraphArgumentTemplate ExtractParameterTemplate(string paramName, out ParameterInfo paramInfo) { paramInfo = typeof(ParameterTestClass) .GetMethod(nameof(ParameterTestClass.TestMethod)) @@ -34,9 +37,10 @@ private AspNet.Internal.TypeTemplates.GraphArgumentTemplate ExtractParameterTemp .FirstOrDefault(x => x.Name == paramName); var mockMethod = Substitute.For(); - mockMethod.InternalFullName + mockMethod.InternalName .Returns($"{nameof(ParameterTestClass)}.{nameof(ParameterTestClass.TestMethod)}"); mockMethod.ObjectType.Returns(typeof(ParameterTestClass)); + mockMethod.Arguments.Returns(new List()); var route = new SchemaItemPath(SchemaItemPath.Join( SchemaItemCollections.Query, @@ -44,7 +48,7 @@ private AspNet.Internal.TypeTemplates.GraphArgumentTemplate ExtractParameterTemp nameof(ParameterTestClass.TestMethod))); mockMethod.Route.Returns(route); - var argTemplate = new AspNet.Internal.TypeTemplates.GraphArgumentTemplate(mockMethod, paramInfo); + var argTemplate = new GraphArgumentTemplate(mockMethod, paramInfo); argTemplate.Parse(); argTemplate.ValidateOrThrow(); @@ -58,10 +62,11 @@ public void StringParam_ParsesCorrectly() Assert.AreEqual(paramInfo.Name, template.Name); Assert.AreEqual(paramInfo, template.Parameter); Assert.AreEqual(null, template.Description); - Assert.IsEmpty(template.TypeExpression.Wrappers); + Assert.IsEmpty((IEnumerable)template.TypeExpression.Wrappers); Assert.IsNull(template.DefaultValue); - Assert.AreEqual($"{nameof(ParameterTestClass)}.{nameof(ParameterTestClass.TestMethod)}.stringArg", template.InternalFullName); - Assert.AreEqual("stringArg", template.InternalName); + Assert.AreEqual($"{nameof(ParameterTestClass)}.{nameof(ParameterTestClass.TestMethod)}.stringArg", template.InternalName); + Assert.AreEqual("stringArg", template.ParameterName); + Assert.AreEqual("ParameterTestClass.TestMethod.stringArg", template.InternalName); } [Test] @@ -98,14 +103,14 @@ public void ValueTypeParam_SetsCorrectly() public void NullableTParam_WithNoDefaultValue_SetsFieldOptionsAsNotNull() { var template = this.ExtractParameterTemplate("nullableIntArg", out var paramInfo); - Assert.IsEmpty(template.TypeExpression.Wrappers); + Assert.IsEmpty((IEnumerable)template.TypeExpression.Wrappers); } [Test] public void ReferenceParam_ParsesCorrectly() { var template = this.ExtractParameterTemplate("objectArg", out var paramInfo); - Assert.IsEmpty(template.TypeExpression.Wrappers); + Assert.IsEmpty((IEnumerable)template.TypeExpression.Wrappers); } [Test] @@ -159,7 +164,7 @@ public void StringReference_WithNullDefaultValue_SetsCorrectly() { var template = this.ExtractParameterTemplate("defaultValueStringArg", out var paramInfo); Assert.AreEqual(typeof(string), template.Parameter.ParameterType); - Assert.IsEmpty(template.TypeExpression.Wrappers); + Assert.IsEmpty((IEnumerable)template.TypeExpression.Wrappers); Assert.IsNull(template.DefaultValue); } @@ -168,7 +173,7 @@ public void StringReference_WithASuppliedDefaultValue_SetsCorrectly() { var template = this.ExtractParameterTemplate("defaultValueStringArgWithValue", out var paramInfo); Assert.AreEqual(typeof(string), template.Parameter.ParameterType); - Assert.IsEmpty(template.TypeExpression.Wrappers); + Assert.IsEmpty((IEnumerable)template.TypeExpression.Wrappers); Assert.AreEqual("abc", template.DefaultValue); } @@ -176,7 +181,7 @@ public void StringReference_WithASuppliedDefaultValue_SetsCorrectly() public void NullableTParam_WithDefaultValue_SetsFieldOptionsAsNullable() { var template = this.ExtractParameterTemplate("defaultValueNullableIntArg", out var paramInfo); - Assert.IsEmpty(template.TypeExpression.Wrappers); + Assert.IsEmpty((IEnumerable)template.TypeExpression.Wrappers); Assert.AreEqual(5, template.DefaultValue); } @@ -185,7 +190,7 @@ public void ArrayOfObjects_ThrowsException() { var template = this.ExtractParameterTemplate("arrayOfObjects", out var paramInfo); Assert.AreEqual(typeof(Person[]), template.Parameter.ParameterType); - Assert.AreEqual("[Input_Person]", template.TypeExpression.ToString()); + Assert.AreEqual("[Type]", template.TypeExpression.ToString()); Assert.AreEqual(null, template.DefaultValue); } @@ -194,7 +199,7 @@ public void ArrayOfEnumerableOfObject_ThrowsException() { var template = this.ExtractParameterTemplate("arrayOfEnumerableOfObject", out var paramInfo); Assert.AreEqual(typeof(IEnumerable[]), template.Parameter.ParameterType); - Assert.AreEqual("[[Input_Person]]", template.TypeExpression.ToString()); + Assert.AreEqual("[[Type]]", template.TypeExpression.ToString()); Assert.AreEqual(null, template.DefaultValue); } @@ -203,7 +208,7 @@ public void EnumerableArrayOfObjects_ThrowsException() { var template = this.ExtractParameterTemplate("enumerableOfArrayOfObjects", out var paramInfo); Assert.AreEqual(typeof(IEnumerable), template.Parameter.ParameterType); - Assert.AreEqual("[[Input_Person]]", template.TypeExpression.ToString()); + Assert.AreEqual("[[Type]]", template.TypeExpression.ToString()); Assert.AreEqual(null, template.DefaultValue); } @@ -212,7 +217,7 @@ public void EnumerableArrayOfArrryOfObjects_ParsesTypeExpression() { var template = this.ExtractParameterTemplate("arrayOfEnumerableOfArrayOfObjects", out var paramInfo); Assert.AreEqual(typeof(IEnumerable[]), template.Parameter.ParameterType); - Assert.AreEqual("[[[Input_Person]]]", template.TypeExpression.ToString()); + Assert.AreEqual("[[[Type]]]", template.TypeExpression.ToString()); Assert.AreEqual(null, template.DefaultValue); } @@ -221,7 +226,7 @@ public void StupidDeepArray_ParsesCorrectTypeExpression() { var template = this.ExtractParameterTemplate("deepArray", out var paramInfo); Assert.AreEqual(typeof(Person[][][][][][][][][][][][][][][][][][][]), template.Parameter.ParameterType); - Assert.AreEqual("[[[[[[[[[[[[[[[[[[[Input_Person]]]]]]]]]]]]]]]]]]]", template.TypeExpression.ToString()); + Assert.AreEqual("[[[[[[[[[[[[[[[[[[[Type]]]]]]]]]]]]]]]]]]]", template.TypeExpression.ToString()); Assert.AreEqual(null, template.DefaultValue); } @@ -229,9 +234,9 @@ public void StupidDeepArray_ParsesCorrectTypeExpression() public void Parse_AssignedDirective_IsTemplatized() { var template = this.ExtractParameterTemplate("paramDirective", out var paramInfo); - Assert.AreEqual(1, template.AppliedDirectives.Count()); + Assert.AreEqual(1, Enumerable.Count(template.AppliedDirectives)); - var appliedDirective = template.AppliedDirectives.First(); + var appliedDirective = Enumerable.First(template.AppliedDirectives); Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); Assert.AreEqual(new object[] { 77, "param arg" }, appliedDirective.Arguments); } @@ -242,7 +247,7 @@ public void CompatiableDeclaredTypeExpression_IsAllowed() // actual type expression "Int" // declared as Int! var template = this.ExtractParameterTemplate("compatiableTypeExpressionSingle", out var paramInfo); - Assert.AreEqual("Int!", template.TypeExpression.ToString()); + Assert.AreEqual("Type!", template.TypeExpression.ToString()); } [Test] @@ -251,7 +256,7 @@ public void CompatiableDeclaredTypeExpressionOnList_IsAllowed() // actual type expression [Int] // declared as [Int!]! var template = this.ExtractParameterTemplate("compatiableTypeExpressionList", out var paramInfo); - Assert.AreEqual("[Int!]!", template.TypeExpression.ToString()); + Assert.AreEqual("[Type!]!", template.TypeExpression.ToString()); } [Test] @@ -289,5 +294,53 @@ public void IncompatiableTypeExpressionNullToNotNull_ThrowsException() var template = this.ExtractParameterTemplate("incompatiableTypeExpressionNullToNotNull", out var paramInfo); }); } + + [Test] + public void FromGraphQLDeclaration_SetsParamModifierAppropriately() + { + var template = this.ExtractParameterTemplate("justFromGraphQLDeclaration", out var paramInfo); + Assert.AreEqual(GraphArgumentModifiers.ExplicitSchemaItem, template.ArgumentModifier); + } + + [Test] + public void FromServiceDeclaration_SetsParamModifierAppropriately() + { + var template = this.ExtractParameterTemplate("justFromServicesDeclaration", out var paramInfo); + Assert.AreEqual(GraphArgumentModifiers.ExplicitInjected, template.ArgumentModifier); + } + + [Test] + public void ArgumentAttributedasFromGraphQLAndFromServices_ThrowsException() + { + Assert.Throws(() => + { + var template = this.ExtractParameterTemplate("doubleDeclaredObject", out var paramInfo); + }); + } + + [Test] + public void ArgumentAttributedasGraphSkip_ThrowsException() + { + Assert.Throws(() => + { + var template = this.ExtractParameterTemplate("graphSkipArgument", out var paramInfo); + }); + } + + [Test] + public void ArgumentTypeAttributedasGraphSkip_ThrowsException() + { + Assert.Throws(() => + { + var template = this.ExtractParameterTemplate("typeHasGraphSkip", out var paramInfo); + }); + } + + [Test] + public void InternalName_IsSetCorrectly() + { + var template = this.ExtractParameterTemplate("internalNameObject", out var paramInfo); + Assert.AreEqual("customInternalName_38", template.InternalName); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTemplateTests.cs new file mode 100644 index 000000000..423aebe8f --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTemplateTests.cs @@ -0,0 +1,233 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates +{ + using System.Collections.Generic; + using System.Linq; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; + using NUnit.Framework; + + [TestFixture] + public class ControllerTemplateTests + { + [Test] + public void Parse_GraphRootController_UsesEmptyRoutePath() + { + var template = new GraphControllerTemplate(typeof(DeclaredGraphRootController)); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual(SchemaItemPath.Empty, template.Route); + } + + [Test] + public void Parse_MismatchedRouteFragmentConfiguration_ThrowsException() + { + var template = new GraphControllerTemplate(typeof(InvalidRouteController)); + template.Parse(); + + Assert.Throws(() => + { + template.ValidateOrThrow(); + }); + } + + [Test] + public void Parse_OverloadedMethodsOnDifferentRoots_ParsesCorrectly() + { + var template = new GraphControllerTemplate(typeof(TwoMethodsDifferentRootsController)); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual(3, Enumerable.Count(template.FieldTemplates)); + Assert.AreEqual(2, Enumerable.Count(template.Actions)); + Assert.AreEqual(1, Enumerable.Count(template.Extensions)); + Assert.IsTrue(template.FieldTemplates.Any(x => x.Route.Path == $"[mutation]/TwoMethodsDifferentRoots/{nameof(TwoMethodsDifferentRootsController.ActionMethodNoAttributes)}")); + Assert.IsTrue(template.FieldTemplates.Any(x => x.Route.Path == $"[query]/TwoMethodsDifferentRoots/{nameof(TwoMethodsDifferentRootsController.ActionMethodNoAttributes)}")); + Assert.IsTrue(template.FieldTemplates.Any(x => x.Route.Path == $"[type]/TwoPropertyObject/Property3")); + } + + [Test] + public void Parse_ReturnArrayOnAction_ParsesCorrectly() + { + var expectedTypeExpression = new GraphTypeExpression( + Constants.Other.DEFAULT_TYPE_EXPRESSION_TYPE_NAME, + MetaGraphTypes.IsList); + + var template = new GraphControllerTemplate(typeof(ArrayReturnController)); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual(1, Enumerable.Count(template.Actions)); + + var action = Enumerable.ElementAt(template.Actions, 0); + Assert.AreEqual(typeof(string[]), action.DeclaredReturnType); + Assert.AreEqual(expectedTypeExpression, action.TypeExpression); + } + + [Test] + public void Parse_ArrayOnInputParameter_ParsesCorrectly() + { + var expectedTypeExpression = new GraphTypeExpression( + Constants.Other.DEFAULT_TYPE_EXPRESSION_TYPE_NAME, + MetaGraphTypes.IsList); + + var template = new GraphControllerTemplate(typeof(ArrayInputParamController)); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual(1, Enumerable.Count(template.Actions)); + + var action = Enumerable.ElementAt(template.Actions, 0); + Assert.AreEqual(typeof(string), action.DeclaredReturnType); + Assert.AreEqual(1, action.Arguments.Count); + Assert.AreEqual(expectedTypeExpression, action.Arguments[0].TypeExpression); + } + + [Test] + public void Parse_AssignedDirective_IsTemplatized() + { + var template = new GraphControllerTemplate(typeof(ControllerWithDirective)); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual(1, Enumerable.Count(template.AppliedDirectives)); + + var appliedDirective = Enumerable.First(template.AppliedDirectives); + Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); + Assert.AreEqual(new object[] { 101, "controller arg" }, appliedDirective.Arguments); + } + + [Test] + public void Parse_StaticMethodsWithProperGraphFieldDeclarations_AreSkipped() + { + var template = new GraphControllerTemplate(typeof(ControllerWithStaticMethod)); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual(1, Enumerable.Count(template.Actions)); + + var action = Enumerable.First(template.Actions); + Assert.AreEqual(nameof(ControllerWithStaticMethod.InstanceMethod), action.Name); + } + + [Test] + public void Parse_UnionAttributeDefinedUnion_CreatesUnitonCorrectly() + { + var template = new GraphControllerTemplate(typeof(ControllerWithUnionAttributes)); + template.Parse(); + template.ValidateOrThrow(); + + var action = template.Actions.Single(x => x.InternalName.EndsWith(nameof(ControllerWithUnionAttributes.UnionDeclaredViaUnionAttribute))); + + Assert.IsNotNull(action.UnionProxy); + Assert.AreEqual("myUnion", action.UnionProxy.Name); + Assert.AreEqual(2, action.UnionProxy.Types.Count); + Assert.IsNotNull(action.UnionProxy.Types.Single(x => x == typeof(TwoPropertyObject))); + Assert.IsNotNull(action.UnionProxy.Types.Single(x => x == typeof(TwoPropertyObjectV2))); + } + + [Test] + public void Parse_UnionViaProxy_OnQuery() + { + var template = new GraphControllerTemplate(typeof(ControllerWithUnionAttributes)); + template.Parse(); + template.ValidateOrThrow(); + + var action = template.Actions.Single(x => x.InternalName.EndsWith(nameof(ControllerWithUnionAttributes.UnionViaProxyOnQuery))); + + Assert.IsNotNull(action.UnionProxy); + Assert.AreEqual("TestUnion", action.UnionProxy.Name); + Assert.AreEqual(1, action.UnionProxy.Types.Count); + Assert.IsNotNull(action.UnionProxy.Types.Single(x => x == typeof(TwoPropertyObject))); + } + + [Test] + public void Parse_UnionViaProxy_OnUnionAttribute() + { + var template = new GraphControllerTemplate(typeof(ControllerWithUnionAttributes)); + template.Parse(); + template.ValidateOrThrow(); + + var action = template.Actions.Single(x => x.InternalName.EndsWith(nameof(ControllerWithUnionAttributes.UnionViaProxyOnUnionAttribute))); + + Assert.IsNotNull(action.UnionProxy); + Assert.AreEqual("TestUnion", action.UnionProxy.Name); + Assert.AreEqual(1, action.UnionProxy.Types.Count); + Assert.IsNotNull(action.UnionProxy.Types.Single(x => x == typeof(TwoPropertyObject))); + } + + [Test] + public void Parse_UnionViaProxy_LotsOfUselessNulls_OnUnionAttribute() + { + var template = new GraphControllerTemplate(typeof(ControllerWithUnionAttributes)); + template.Parse(); + template.ValidateOrThrow(); + + var action = template.Actions.Single(x => x.InternalName.EndsWith(nameof(ControllerWithUnionAttributes.LotsOfNullsWithProxy))); + + Assert.IsNotNull(action.UnionProxy); + Assert.AreEqual("TestUnion", action.UnionProxy.Name); + Assert.AreEqual(1, action.UnionProxy.Types.Count); + Assert.IsNotNull(action.UnionProxy.Types.Single(x => x == typeof(TwoPropertyObject))); + } + + [Test] + public void Parse_UnionAttributeDefinedUnion_WithLotsOfNulls() + { + var template = new GraphControllerTemplate(typeof(ControllerWithUnionAttributes)); + template.Parse(); + template.ValidateOrThrow(); + + var action = template.Actions.Single(x => x.InternalName.EndsWith(nameof(ControllerWithUnionAttributes.LotsOfNullsWithDeclaration))); + + // only two actual types, all nulls are ignored + Assert.IsNotNull(action.UnionProxy); + Assert.AreEqual("myUnion2", action.UnionProxy.Name); + Assert.AreEqual(2, action.UnionProxy.Types.Count); + Assert.IsNotNull(action.UnionProxy.Types.Single(x => x == typeof(TwoPropertyObject))); + Assert.IsNotNull(action.UnionProxy.Types.Single(x => x == typeof(TwoPropertyObjectV2))); + } + + [Test] + public void Parse_DoubleDeclaredUnion_ThrowsException() + { + var template = new GraphControllerTemplate(typeof(ControllerWithDoubleDeclaredUnion)); + template.Parse(); + + var ex = Assert.Throws(() => + { + template.ValidateOrThrow(); + }); + } + + [Test] + public void Parse_InheritedAction_IsIncludedInTheTemplate() + { + var template = new GraphControllerTemplate(typeof(ControllerWithInheritedAction)); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual(2, template.Actions.Count()); + + Assert.IsNotNull(template.Actions.Single(x => x.Name.EndsWith(nameof(BaseControllerWithAction.BaseControllerAction)))); + Assert.IsNotNull(template.Actions.Single(x => x.Name.EndsWith(nameof(ControllerWithInheritedAction.ChildControllerAction)))); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/ArrayInputParamController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ArrayInputParamController.cs similarity index 80% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/ArrayInputParamController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ArrayInputParamController.cs index aace60508..8138b96f1 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/ArrayInputParamController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ArrayInputParamController.cs @@ -7,11 +7,11 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ArrayInputParamController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/ArrayReturnController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ArrayReturnController.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/ArrayReturnController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ArrayReturnController.cs index eeeb8746b..e277b848c 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/ArrayReturnController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ArrayReturnController.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/BaseControllerWithAction.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/BaseControllerWithAction.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/BaseControllerWithAction.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/BaseControllerWithAction.cs index 12f02a484..93dd5bcbb 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/BaseControllerWithAction.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/BaseControllerWithAction.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/ControllerWithActionAsTypeExtensionForCustomNamedObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithActionAsTypeExtensionForCustomNamedObject.cs similarity index 100% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/ControllerWithActionAsTypeExtensionForCustomNamedObject.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithActionAsTypeExtensionForCustomNamedObject.cs diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/ControllerWithDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithDirective.cs similarity index 74% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/ControllerWithDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithDirective.cs index 9f4e0d537..e01a41c30 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/ControllerWithDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithDirective.cs @@ -7,11 +7,11 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; [ApplyDirective(typeof(DirectiveWithArgs), 101, "controller arg")] public class ControllerWithDirective : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithDoubleDeclaredUnion.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithDoubleDeclaredUnion.cs new file mode 100644 index 000000000..5a94be16a --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithDoubleDeclaredUnion.cs @@ -0,0 +1,26 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class ControllerWithDoubleDeclaredUnion : GraphController + { + [Query("field", "myUnion", typeof(TwoPropertyObject))] + [Union("myUnion", typeof(TwoPropertyObject), typeof(TwoPropertyObjectV2))] + public IGraphActionResult UnionWithMixedTypeDeclaration() + { + return this.Ok(null); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/ControllerWithInheritedAction.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithInheritedAction.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/ControllerWithInheritedAction.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithInheritedAction.cs index 46ab3f636..a784cb405 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/ControllerWithInheritedAction.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithInheritedAction.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithStaticMethod.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithStaticMethod.cs new file mode 100644 index 000000000..579d77ba6 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithStaticMethod.cs @@ -0,0 +1,29 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Controllers; + + public class ControllerWithStaticMethod : GraphController + { + [Query] + public static int StaticMethod(int a, int b) + { + return a - b; + } + + [Query] + public int InstanceMethod(int a, int b) + { + return a + b; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithUnionAttributes.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithUnionAttributes.cs new file mode 100644 index 000000000..1ce3372d9 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/ControllerWithUnionAttributes.cs @@ -0,0 +1,63 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeMakers.TestData; + + public class ControllerWithUnionAttributes : GraphController + { + [Query] + [Union("myUnion", typeof(TwoPropertyObject), typeof(TwoPropertyObjectV2))] + public IGraphActionResult UnionDeclaredViaUnionAttribute() + { + return this.Ok(null); + } + + [Query("field", "myUnion")] + [PossibleTypes(typeof(TwoPropertyObject), typeof(TwoPropertyObjectV2))] + public IGraphActionResult UnionWithMixedTypeDeclaration() + { + return this.Ok(null); + } + + [Query("field1", typeof(UnionWithInternalName))] + public IGraphActionResult UnionViaProxyOnQuery() + { + return this.Ok(null); + } + + [Query] + [Union(typeof(UnionWithInternalName))] + public IGraphActionResult UnionViaProxyOnUnionAttribute() + { + return this.Ok(null); + } + + [Query(returnType: null)] + [Union(typeof(UnionWithInternalName))] + [PossibleTypes(null, null, null, null)] + public IGraphActionResult LotsOfNullsWithProxy() + { + return this.Ok(null); + } + + [Query(returnType: typeof(TwoPropertyObject))] + [Union("myUnion2", typeof(TwoPropertyObjectV2), null, null)] + [PossibleTypes(null, null, null, null)] + public IGraphActionResult LotsOfNullsWithDeclaration() + { + return this.Ok(null); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/DeclaredGraphRootController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/DeclaredGraphRootController.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/DeclaredGraphRootController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/DeclaredGraphRootController.cs index bcadaaeb9..214df388f 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/DeclaredGraphRootController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/DeclaredGraphRootController.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/InvalidRouteController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/InvalidRouteController.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/InvalidRouteController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/InvalidRouteController.cs index 1b1b58910..5c797d28f 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/InvalidRouteController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/InvalidRouteController.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/NoInheritanceFromBaseController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/NoInheritanceFromBaseController.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/NoInheritanceFromBaseController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/NoInheritanceFromBaseController.cs index cea622ddd..4cc8037bc 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/NoInheritanceFromBaseController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/NoInheritanceFromBaseController.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData { using GraphQL.AspNet.Controllers; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/NoRouteSpecifierController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/NoRouteSpecifierController.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/NoRouteSpecifierController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/NoRouteSpecifierController.cs index e79cdba70..b883c9ef7 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/NoRouteSpecifierController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/NoRouteSpecifierController.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData { using GraphQL.AspNet.Controllers; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/OneMethodController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/OneMethodController.cs similarity index 80% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/OneMethodController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/OneMethodController.cs index 6262863ab..f1b78d8e3 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/OneMethodController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/OneMethodController.cs @@ -7,11 +7,11 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class OneMethodController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/SimpleControllerNoMethods.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/SimpleControllerNoMethods.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/SimpleControllerNoMethods.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/SimpleControllerNoMethods.cs index dbf8010de..69f3c955d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/SimpleControllerNoMethods.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/SimpleControllerNoMethods.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData { using System.ComponentModel; using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/TwoMethodClashController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/TwoMethodClashController.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/TwoMethodClashController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/TwoMethodClashController.cs index ef996a8ad..f0d5c27e1 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/TwoMethodClashController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/TwoMethodClashController.cs @@ -7,11 +7,11 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; /// /// contains an overloaded method without providing specific names for each in the diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/TwoMethodNoClashController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/TwoMethodNoClashController.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/TwoMethodNoClashController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/TwoMethodNoClashController.cs index def7fc1bc..202ccf850 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/TwoMethodNoClashController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/TwoMethodNoClashController.cs @@ -7,11 +7,11 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; /// /// contains an overloaded method but DOES provide specific names to create a diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/TwoMethodsDifferentRootsController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/TwoMethodsDifferentRootsController.cs similarity index 89% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/TwoMethodsDifferentRootsController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/TwoMethodsDifferentRootsController.cs index 7f3c14cce..a0a9e09aa 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ControllerTestData/TwoMethodsDifferentRootsController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTestData/TwoMethodsDifferentRootsController.cs @@ -7,12 +7,12 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; /// /// contains an overloaded method without providing specific names for each in the diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveMethodTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveMethodTemplateTests.cs similarity index 79% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveMethodTemplateTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveMethodTemplateTests.cs index 767409aab..e1d4c63c3 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveMethodTemplateTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveMethodTemplateTests.cs @@ -7,16 +7,16 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates { using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; using NSubstitute; using NUnit.Framework; @@ -30,7 +30,7 @@ public void SimpleDescriptor_AllDefaults_GeneralPropertyCheck() .GetMethod(nameof(SimpleExecutableDirective.Execute)); var mock = Substitute.For(); - mock.InternalFullName.Returns("Simple"); + mock.InternalName.Returns("Simple"); var route = new SchemaItemPath(SchemaItemCollections.Directives, "Simple"); mock.Route.Returns(route); var template = new GraphDirectiveMethodTemplate(mock, method); @@ -42,15 +42,17 @@ public void SimpleDescriptor_AllDefaults_GeneralPropertyCheck() Assert.AreEqual("IGraphActionResult (int arg1, string arg2)", template.MethodSignature); Assert.AreEqual(nameof(SimpleExecutableDirective.Execute), template.Name); - Assert.AreEqual($"Simple.{nameof(SimpleExecutableDirective.Execute)}", template.InternalFullName); + Assert.AreEqual($"Simple.{nameof(SimpleExecutableDirective.Execute)}", template.InternalName); Assert.IsTrue(template.IsAsyncField); Assert.AreEqual(typeof(IGraphActionResult), template.ObjectType); Assert.AreEqual(DirectiveLocation.FIELD, template.Locations); + Assert.AreEqual(2, template.Arguments.Count); Assert.AreEqual("arg1", template.Arguments[0].Name); Assert.AreEqual(typeof(int), template.Arguments[0].ObjectType); Assert.AreEqual("arg2", template.Arguments[1].Name); Assert.AreEqual(typeof(string), template.Arguments[1].ObjectType); + Assert.IsTrue(template.IsExplicitDeclaration); Assert.AreEqual(GraphFieldSource.Method, template.FieldSource); @@ -65,7 +67,7 @@ public void MethodWithSkipAttached_ThrowsException() .GetMethod(nameof(TestDirectiveMethodTemplateContainer.SkippedMethod)); var mock = Substitute.For(); - mock.InternalFullName.Returns("Simple"); + mock.InternalName.Returns("Simple"); var route = new SchemaItemPath(SchemaItemCollections.Directives, "Simple"); mock.Route.Returns(route); var template = new GraphDirectiveMethodTemplate(mock, method); @@ -84,7 +86,7 @@ public void InvalidReturnType_ThrowsException() .GetMethod(nameof(TestDirectiveMethodTemplateContainer.IncorrrectReturnType)); var mock = Substitute.For(); - mock.InternalFullName.Returns("Simple"); + mock.InternalName.Returns("Simple"); var route = new SchemaItemPath(SchemaItemCollections.Directives, "Simple"); mock.Route.Returns(route); @@ -103,26 +105,7 @@ public void AsyncMethodWithNoReturnType_ThrowsException() .GetMethod(nameof(TestDirectiveMethodTemplateContainer2.InvalidTaskReference)); var mock = Substitute.For(); - mock.InternalFullName.Returns("Simple"); - var route = new SchemaItemPath(SchemaItemCollections.Directives, "Simple"); - mock.Route.Returns(route); - - Assert.Throws(() => - { - var template = new GraphDirectiveMethodTemplate(mock, method); - template.Parse(); - template.ValidateOrThrow(); - }); - } - - [Test] - public void InterfaceAsInputParameter_ThrowsException() - { - var method = typeof(TestDirectiveMethodTemplateContainer2) - .GetMethod(nameof(TestDirectiveMethodTemplateContainer2.InterfaceAsParameter)); - - var mock = Substitute.For(); - mock.InternalFullName.Returns("Simple"); + mock.InternalName.Returns("Simple"); var route = new SchemaItemPath(SchemaItemCollections.Directives, "Simple"); mock.Route.Returns(route); diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTemplateTests.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTemplateTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTemplateTests.cs index b31ad82e8..9978094fb 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTemplateTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTemplateTests.cs @@ -7,14 +7,15 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates { using System.Linq; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; + using GraphQL.AspNet.Security; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; using NUnit.Framework; [TestFixture] @@ -29,14 +30,13 @@ public void Simpletemplate_AllDefaults_GeneralPropertyCheck() Assert.AreEqual("SimpleExecutable", template.Name); Assert.AreEqual(nameof(SimpleExecutableDirective), template.InternalName); - Assert.AreEqual(typeof(SimpleExecutableDirective).FriendlyName(true), template.InternalFullName); Assert.AreEqual("Simple Description", template.Description); Assert.AreEqual(1, template.Methods.Count); Assert.IsTrue(template.Locations.HasFlag(DirectiveLocation.FIELD)); Assert.AreEqual(typeof(SimpleExecutableDirective), template.ObjectType); Assert.AreEqual("[directive]/SimpleExecutable", template.Route.Path); Assert.AreEqual(DirectiveLocation.FIELD, template.Locations); - Assert.IsNotNull(template.Methods.FindMethod(DirectiveLocation.FIELD)); + Assert.IsNotNull(template.Methods.FindMetaData(DirectiveLocation.FIELD)); Assert.IsFalse(template.IsRepeatable); } @@ -125,5 +125,15 @@ public void SecurityPolicices_AreParsedCorrectly() Assert.IsFalse(template.SecurityPolicies.ElementAt(1).IsNamedPolicy); CollectionAssert.AreEquivalent(new string[] { "CustomRole1", "CustomRole2" }, template.SecurityPolicies.ElementAt(1).AllowedRoles); } + + [Test] + public void InternalName_WhenSuppliedOnGraphType_IsExtractedCorrectly() + { + var template = new GraphDirectiveTemplate(typeof(DirectiveWithInternalName)); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual("MyInternalNameDirective", template.InternalName); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/DirectiveWithArgs.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithArgs.cs similarity index 90% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/DirectiveWithArgs.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithArgs.cs index 637407cf3..74c0c86dd 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/DirectiveWithArgs.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithArgs.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Directives; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/DirectiveWithDeclaredDirectiveOnMethod.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithDeclaredDirectiveOnMethod.cs similarity index 91% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/DirectiveWithDeclaredDirectiveOnMethod.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithDeclaredDirectiveOnMethod.cs index a49b12505..edeb576c5 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/DirectiveWithDeclaredDirectiveOnMethod.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithDeclaredDirectiveOnMethod.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData { using System.Threading.Tasks; using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/DirectiveWithDeclaredDirectives.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithDeclaredDirectives.cs similarity index 91% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/DirectiveWithDeclaredDirectives.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithDeclaredDirectives.cs index 3985051f6..0a88fec87 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/DirectiveWithDeclaredDirectives.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithDeclaredDirectives.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData { using System.Threading.Tasks; using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithInternalName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithInternalName.cs new file mode 100644 index 000000000..d4ff38c92 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithInternalName.cs @@ -0,0 +1,26 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Directives; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + [GraphType(InternalName = "MyInternalNameDirective")] + public class DirectiveWithInternalName : GraphDirective + { + [DirectiveLocations(DirectiveLocation.FIELD)] + public IGraphActionResult Execute(TwoPropertyObject obj) + { + return null; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/DirectiveWithRequirements.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithRequirements.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/DirectiveWithRequirements.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithRequirements.cs index 8534fd6c9..bb9d5e6af 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/DirectiveWithRequirements.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithRequirements.cs @@ -6,13 +6,13 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Directives; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class DirectiveWithRequirements : GraphDirective { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/DirectiveWithSecurityRequirements.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithSecurityRequirements.cs similarity index 90% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/DirectiveWithSecurityRequirements.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithSecurityRequirements.cs index b54f57f43..7eb58aecb 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/DirectiveWithSecurityRequirements.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/DirectiveWithSecurityRequirements.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Directives; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/MismatchedSignaturesDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/MismatchedSignaturesDirective.cs similarity index 91% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/MismatchedSignaturesDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/MismatchedSignaturesDirective.cs index e20933be3..278a5c055 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/MismatchedSignaturesDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/MismatchedSignaturesDirective.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData { using System.Threading.Tasks; using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/NoLocationAttributeDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/NoLocationAttributeDirective.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/NoLocationAttributeDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/NoLocationAttributeDirective.cs index 48745a824..73ad69a95 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/NoLocationAttributeDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/NoLocationAttributeDirective.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData { using System.Threading.Tasks; using GraphQL.AspNet.Directives; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/NoLocationsDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/NoLocationsDirective.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/NoLocationsDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/NoLocationsDirective.cs index be2328c3f..99fb127de 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/NoLocationsDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/NoLocationsDirective.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData { using System.Threading.Tasks; using GraphQL.AspNet.Directives; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/OverlappingLocationsDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/OverlappingLocationsDirective.cs similarity index 90% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/OverlappingLocationsDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/OverlappingLocationsDirective.cs index 3a5375a9f..6e62171ce 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/OverlappingLocationsDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/OverlappingLocationsDirective.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData { using System.Threading.Tasks; using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/RepeatableDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/RepeatableDirective.cs similarity index 89% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/RepeatableDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/RepeatableDirective.cs index b94909242..90889058a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/RepeatableDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/RepeatableDirective.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData { using System.Threading.Tasks; using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/SimpleExecutableDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/SimpleExecutableDirective.cs similarity index 91% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/SimpleExecutableDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/SimpleExecutableDirective.cs index 9f7215dc2..44b7a6244 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/SimpleExecutableDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/SimpleExecutableDirective.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData { using System.ComponentModel; using System.Threading.Tasks; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/TestDirectiveMethodTemplateContainer.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/TestDirectiveMethodTemplateContainer.cs similarity index 91% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/TestDirectiveMethodTemplateContainer.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/TestDirectiveMethodTemplateContainer.cs index adb340d55..59967b188 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/TestDirectiveMethodTemplateContainer.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/TestDirectiveMethodTemplateContainer.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData { using System.Threading.Tasks; using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/TestDirectiveMethodTemplateContainer2.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/TestDirectiveMethodTemplateContainer2.cs similarity index 90% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/TestDirectiveMethodTemplateContainer2.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/TestDirectiveMethodTemplateContainer2.cs index efc365738..26b25906e 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/DirectiveTestData/TestDirectiveMethodTemplateContainer2.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/DirectiveTestData/TestDirectiveMethodTemplateContainer2.cs @@ -7,14 +7,14 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData { using System.Threading.Tasks; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Directives; using GraphQL.AspNet.Interfaces.Controllers; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.Interfaces; + using GraphQL.AspNet.Tests.Common.Interfaces; public class TestDirectiveMethodTemplateContainer2 : GraphDirective { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumGraphTypeTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumGraphTypeTemplateTests.cs similarity index 75% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumGraphTypeTemplateTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumGraphTypeTemplateTests.cs index 63f1a02c2..62cc3cf84 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumGraphTypeTemplateTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumGraphTypeTemplateTests.cs @@ -7,13 +7,15 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates { using System; using System.Linq; using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; - using GraphQL.AspNet.Tests.Internal.Templating.EnumTestData; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData; using NUnit.Framework; [TestFixture] @@ -22,7 +24,7 @@ public class EnumGraphTypeTemplateTests [Test] public void Parse_SimpleEnum_AllDefault_ParsesCorrectly() { - var template = new AspNet.Internal.TypeTemplates.EnumGraphTypeTemplate(typeof(SimpleEnum)); + var template = new EnumGraphTypeTemplate(typeof(SimpleEnum)); template.Parse(); template.ValidateOrThrow(); @@ -37,7 +39,7 @@ public void Parse_SimpleEnum_AllDefault_ParsesCorrectly() [Test] public void Parse_EnumWithDescription_ParsesCorrectly() { - var template = new AspNet.Internal.TypeTemplates.EnumGraphTypeTemplate(typeof(EnumWithDescription)); + var template = new EnumGraphTypeTemplate(typeof(EnumWithDescription)); template.Parse(); template.ValidateOrThrow(); @@ -47,7 +49,7 @@ public void Parse_EnumWithDescription_ParsesCorrectly() [Test] public void Parse_EnumWithGraphName_ParsesCorrectly() { - var template = new AspNet.Internal.TypeTemplates.EnumGraphTypeTemplate(typeof(EnumWithGraphName)); + var template = new EnumGraphTypeTemplate(typeof(EnumWithGraphName)); template.Parse(); template.ValidateOrThrow(); @@ -57,7 +59,7 @@ public void Parse_EnumWithGraphName_ParsesCorrectly() [Test] public void Parse_EnumWithDescriptionOnValues_ParsesCorrectly() { - var template = new AspNet.Internal.TypeTemplates.EnumGraphTypeTemplate(typeof(EnumWithDescriptionOnValues)); + var template = new EnumGraphTypeTemplate(typeof(EnumWithDescriptionOnValues)); template.Parse(); template.ValidateOrThrow(); @@ -70,7 +72,7 @@ public void Parse_EnumWithDescriptionOnValues_ParsesCorrectly() [Test] public void Parse_EnumWithInvalidValueName_ThrowsException() { - var template = new AspNet.Internal.TypeTemplates.EnumGraphTypeTemplate(typeof(EnumWithInvalidValueName)); + var template = new EnumGraphTypeTemplate(typeof(EnumWithInvalidValueName)); template.Parse(); Assert.Throws(() => @@ -82,7 +84,7 @@ public void Parse_EnumWithInvalidValueName_ThrowsException() [Test] public void Parse_EnumWithValueWithGraphName_ParsesCorrectly() { - var template = new AspNet.Internal.TypeTemplates.EnumGraphTypeTemplate(typeof(EnumWithValueWithGraphName)); + var template = new EnumGraphTypeTemplate(typeof(EnumWithValueWithGraphName)); template.Parse(); template.ValidateOrThrow(); @@ -94,7 +96,7 @@ public void Parse_EnumWithValueWithGraphName_ParsesCorrectly() [Test] public void Parse_EnumWithValueWithGraphName_ButGraphNameIsInvalid_ThrowsException() { - var template = new AspNet.Internal.TypeTemplates.EnumGraphTypeTemplate(typeof(EnumWithValueWithGraphNameButGraphNameIsInvalid)); + var template = new EnumGraphTypeTemplate(typeof(EnumWithValueWithGraphNameButGraphNameIsInvalid)); template.Parse(); Assert.Throws(() => { @@ -105,7 +107,7 @@ public void Parse_EnumWithValueWithGraphName_ButGraphNameIsInvalid_ThrowsExcepti [Test] public void Parse_EnumWithNonIntBase_ParsesCorrectly() { - var template = new AspNet.Internal.TypeTemplates.EnumGraphTypeTemplate(typeof(EnumFromUInt)); + var template = new EnumGraphTypeTemplate(typeof(EnumFromUInt)); template.Parse(); template.ValidateOrThrow(); @@ -118,7 +120,7 @@ public void Parse_EnumWithNonIntBase_ParsesCorrectly() [Test] public void Parse_EnumWithDuplciateValues_ThrowsException() { - var template = new AspNet.Internal.TypeTemplates.EnumGraphTypeTemplate(typeof(EnumWithDuplicateValues)); + var template = new EnumGraphTypeTemplate(typeof(EnumWithDuplicateValues)); template.Parse(); Assert.Throws(() => @@ -130,7 +132,7 @@ public void Parse_EnumWithDuplciateValues_ThrowsException() [Test] public void Parse_EnumWithDuplciateValuesFromComposite_ThrowsException() { - var template = new AspNet.Internal.TypeTemplates.EnumGraphTypeTemplate(typeof(EnumWithDuplicateValuesFromComposite)); + var template = new EnumGraphTypeTemplate(typeof(EnumWithDuplicateValuesFromComposite)); template.Parse(); Assert.Throws(() => @@ -145,7 +147,7 @@ public void Parse_EnumWithDuplciateValuesFromComposite_ThrowsException() [TestCase(typeof(EnumFromLong))] public void Parse_EnsureEnumsOfSignedValues(Type type) { - var template = new AspNet.Internal.TypeTemplates.EnumGraphTypeTemplate(type); + var template = new EnumGraphTypeTemplate(type); template.Parse(); template.ValidateOrThrow(); @@ -164,7 +166,7 @@ public void Parse_EnsureEnumsOfSignedValues(Type type) [TestCase(typeof(EnumFromULong))] public void Parse_EnsureEnumsOfUnSignedValues(Type type) { - var template = new AspNet.Internal.TypeTemplates.EnumGraphTypeTemplate(type); + var template = new EnumGraphTypeTemplate(type); template.Parse(); template.ValidateOrThrow(); @@ -179,7 +181,7 @@ public void Parse_SignedEnum_CompleteKeySpace_ParsesCorrectly() { // the enum defines EVERY value for its key space // ensure it parses - var template = new AspNet.Internal.TypeTemplates.EnumGraphTypeTemplate(typeof(EnumCompleteSByte)); + var template = new EnumGraphTypeTemplate(typeof(EnumCompleteSByte)); template.Parse(); template.ValidateOrThrow(); @@ -192,7 +194,7 @@ public void Parse_UnsignedEnum_CompleteKeySpace_ParsesCorrectly() { // the enum defines EVERY value for its key space // ensure it parses - var template = new AspNet.Internal.TypeTemplates.EnumGraphTypeTemplate(typeof(EnumCompleteByte)); + var template = new EnumGraphTypeTemplate(typeof(EnumCompleteByte)); template.Parse(); template.ValidateOrThrow(); @@ -203,7 +205,7 @@ public void Parse_UnsignedEnum_CompleteKeySpace_ParsesCorrectly() [Test] public void Parse_AssignedDirective_IsTemplatized() { - var template = new AspNet.Internal.TypeTemplates.EnumGraphTypeTemplate(typeof(EnumWithDirective)); + var template = new EnumGraphTypeTemplate(typeof(EnumWithDirective)); template.Parse(); template.ValidateOrThrow(); @@ -217,7 +219,7 @@ public void Parse_AssignedDirective_IsTemplatized() [Test] public void Parse_EnumOption_AssignedDirective_IsTemplatized() { - var template = new AspNet.Internal.TypeTemplates.EnumGraphTypeTemplate(typeof(EnumWithDirectiveOnOption)); + var template = new EnumGraphTypeTemplate(typeof(EnumWithDirectiveOnOption)); template.Parse(); template.ValidateOrThrow(); @@ -233,5 +235,26 @@ public void Parse_EnumOption_AssignedDirective_IsTemplatized() Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); Assert.AreEqual(new object[] { 89, "enum option arg" }, appliedDirective.Arguments); } + + [Test] + public void Parse_InternalName_WhenSupplied_IsRendered() + { + var template = new EnumGraphTypeTemplate(typeof(EnumWithInternalName)); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual("InternalName_Enum_21", template.InternalName); + } + + [Test] + public void Parse_InternalName_OnValue_WhenSupplied_IsRendered() + { + var template = new EnumGraphTypeTemplate(typeof(EnumWithInternalNameOnOption)); + template.Parse(); + template.ValidateOrThrow(); + + var option = template.Values.Single(); + Assert.AreEqual("Value1_InternalName", option.InternalName); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/DeprecatedValueEnum.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/DeprecatedValueEnum.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/DeprecatedValueEnum.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/DeprecatedValueEnum.cs index 9288328bf..d6b379a27 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/DeprecatedValueEnum.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/DeprecatedValueEnum.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumCompleteByte.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumCompleteByte.cs similarity index 99% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumCompleteByte.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumCompleteByte.cs index 9fb9da252..33919604b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumCompleteByte.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumCompleteByte.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { public enum EnumCompleteByte : byte { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumCompleteSByte.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumCompleteSByte.cs similarity index 99% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumCompleteSByte.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumCompleteSByte.cs index f6a8f9519..49cbf932a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumCompleteSByte.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumCompleteSByte.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { public enum EnumCompleteSByte : sbyte { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromByte.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromByte.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromByte.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromByte.cs index b6f74fa81..7b51439d6 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromByte.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromByte.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { public enum EnumFromByte { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromInt.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromInt.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromInt.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromInt.cs index 3c0a76fc9..dac474535 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromInt.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromInt.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { public enum EnumFromInt : int { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromLong.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromLong.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromLong.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromLong.cs index e5163c112..cf9d4bad5 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromLong.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromLong.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { public enum EnumFromLong : long { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromSByte.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromSByte.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromSByte.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromSByte.cs index efa1dbf11..914fc91d6 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromSByte.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromSByte.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { public enum EnumFromSByte : sbyte { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromShort.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromShort.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromShort.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromShort.cs index 0ca1c8324..bfcafc41a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromShort.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromShort.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { public enum EnumFromShort : short { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromUInt.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromUInt.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromUInt.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromUInt.cs index c0a733994..c3811f104 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromUInt.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromUInt.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { public enum EnumFromUInt : uint { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromULong.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromULong.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromULong.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromULong.cs index 508c7179f..984d8116e 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromULong.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromULong.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { public enum EnumFromULong : ulong { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromUShort.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromUShort.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromUShort.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromUShort.cs index 118dddab0..2d7b6355f 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumFromUShort.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumFromUShort.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { public enum EnumFromUShort : ushort { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDescription.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDescription.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDescription.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDescription.cs index b1cd9e269..0b188c22a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDescription.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDescription.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { using System.ComponentModel; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDescriptionOnValues.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDescriptionOnValues.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDescriptionOnValues.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDescriptionOnValues.cs index c3edd3e1f..d1862d987 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDescriptionOnValues.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDescriptionOnValues.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { using System.ComponentModel; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDirective.cs similarity index 73% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDirective.cs index dd59f0d70..9b7d51d3d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDirective.cs @@ -6,10 +6,10 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; [ApplyDirective(typeof(DirectiveWithArgs), 5, "bob")] public enum EnumWithDirective diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDirectiveOnOption.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDirectiveOnOption.cs similarity index 74% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDirectiveOnOption.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDirectiveOnOption.cs index cf3c873ef..5237d7a45 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDirectiveOnOption.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDirectiveOnOption.cs @@ -6,10 +6,10 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; public enum EnumWithDirectiveOnOption { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDuplicateValues.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDuplicateValues.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDuplicateValues.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDuplicateValues.cs index 9d72e2119..af34edaab 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDuplicateValues.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDuplicateValues.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { public enum EnumWithDuplicateValues { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDuplicateValuesFromComposite.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDuplicateValuesFromComposite.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDuplicateValuesFromComposite.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDuplicateValuesFromComposite.cs index 9fa7ee654..3d8668dbd 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithDuplicateValuesFromComposite.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDuplicateValuesFromComposite.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { public enum EnumWithDuplicateValuesFromComposite { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithGraphName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithGraphName.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithGraphName.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithGraphName.cs index 96b1eaf7f..584fd7f87 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/EnumWithGraphName.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithGraphName.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithInternalName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithInternalName.cs new file mode 100644 index 000000000..07ac7a2cd --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithInternalName.cs @@ -0,0 +1,20 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData +{ + using GraphQL.AspNet.Attributes; + + [GraphType(InternalName = "InternalName_Enum_21")] + public enum EnumWithInternalName + { + Value1, + Value2, + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithInternalNameOnOption.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithInternalNameOnOption.cs new file mode 100644 index 000000000..1f24feb72 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithInternalNameOnOption.cs @@ -0,0 +1,19 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData +{ + using GraphQL.AspNet.Attributes; + + public enum EnumWithInternalNameOnOption + { + [GraphEnumValue(InternalName = "Value1_InternalName")] + Value1, + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithInvalidValueName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithInvalidValueName.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithInvalidValueName.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithInvalidValueName.cs index 4015116d7..0a74f17b6 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithInvalidValueName.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithInvalidValueName.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { public enum EnumWithInvalidValueName { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithValueWithGraphName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithValueWithGraphName.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithValueWithGraphName.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithValueWithGraphName.cs index bd1a4290b..871a5b741 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithValueWithGraphName.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithValueWithGraphName.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithValueWithGraphNameButGraphNameIsInvalid.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithValueWithGraphNameButGraphNameIsInvalid.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithValueWithGraphNameButGraphNameIsInvalid.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithValueWithGraphNameButGraphNameIsInvalid.cs index 4d4d7bf80..1231a70eb 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/EnumWithValueWithGraphNameButGraphNameIsInvalid.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithValueWithGraphNameButGraphNameIsInvalid.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/SimpleEnum.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/SimpleEnum.cs similarity index 81% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/SimpleEnum.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/SimpleEnum.cs index 7202d3d83..7166578d2 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/SimpleEnum.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/SimpleEnum.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.EnumTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.EnumTestData { public enum SimpleEnum { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ExtensionMethodTestData/CustomNamedObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ExtensionMethodTestData/CustomNamedObject.cs similarity index 100% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ExtensionMethodTestData/CustomNamedObject.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ExtensionMethodTestData/CustomNamedObject.cs diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ExtensionMethodTestData/ExtensionMethodController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ExtensionMethodTestData/ExtensionMethodController.cs similarity index 90% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ExtensionMethodTestData/ExtensionMethodController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ExtensionMethodTestData/ExtensionMethodController.cs index 00fa1ac87..1cdaa437b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ExtensionMethodTestData/ExtensionMethodController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ExtensionMethodTestData/ExtensionMethodController.cs @@ -7,15 +7,16 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ExtensionMethodTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ExtensionMethodTestData { using System.Collections.Generic; using System.ComponentModel; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - using GraphQL.AspNet.Tests.Framework.Interfaces; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Common.Interfaces; + using GraphQL.AspNet.Tests.Internal.Templating.ExtensionMethodTestData; public class ExtensionMethodController : GraphController { @@ -91,6 +92,12 @@ public IDictionary> CustomValidReturnType(IEnumerab return null; } + [BatchTypeExtension(typeof(TwoPropertyObject), "Property3", InternalName = "BatchInternalName")] + public IDictionary> CustomeInternalName(IEnumerable sourceData, int arg1) + { + return null; + } + [BatchTypeExtension(typeof(TwoPropertyObject), "Property3", typeof(List))] public IDictionary> MismatchedPropertyDeclarations(IEnumerable sourceData, int arg1) { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/AttributedClass.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/AttributedClass.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/AttributedClass.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/AttributedClass.cs index 57b0965b0..584ba44bf 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/AttributedClass.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/AttributedClass.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.GraphTypeNameTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.GraphTypeNameTestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/AttributedClassInvalidName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/AttributedClassInvalidName.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/AttributedClassInvalidName.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/AttributedClassInvalidName.cs index 0665fa473..b4ff8b207 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/AttributedClassInvalidName.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/AttributedClassInvalidName.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.GraphTypeNameTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.GraphTypeNameTestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/EnumNameTest.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/EnumNameTest.cs similarity index 80% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/EnumNameTest.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/EnumNameTest.cs index 4f71da2f7..b93cab4cf 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/EnumNameTest.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/EnumNameTest.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.GraphTypeNameTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.GraphTypeNameTestData { public enum EnumNameTest { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/EnumNameTestWithTypeName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/EnumNameTestWithTypeName.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/EnumNameTestWithTypeName.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/EnumNameTestWithTypeName.cs index 26831eb85..3043c3aa3 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/EnumNameTestWithTypeName.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/EnumNameTestWithTypeName.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.GraphTypeNameTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.GraphTypeNameTestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/GenericClass.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/GenericClass.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/GenericClass.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/GenericClass.cs index e9e35fbbc..816bd907c 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/GenericClass.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/GenericClass.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.GraphTypeNameTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.GraphTypeNameTestData { public class GenericClass { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/GenericClassWithAttribute.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/GenericClassWithAttribute.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/GenericClassWithAttribute.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/GenericClassWithAttribute.cs index 3307c7208..8fe3b7b4f 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/GenericClassWithAttribute.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/GenericClassWithAttribute.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.GraphTypeNameTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.GraphTypeNameTestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/NoAttributeClass.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/NoAttributeClass.cs similarity index 79% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/NoAttributeClass.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/NoAttributeClass.cs index 722714955..00899c064 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/NoAttributeClass.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/NoAttributeClass.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.GraphTypeNameTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.GraphTypeNameTestData { public class NoAttributeClass { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/NoAttributeClassForNewName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/NoAttributeClassForNewName.cs similarity index 79% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/NoAttributeClassForNewName.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/NoAttributeClassForNewName.cs index 022b45601..17b3b4aef 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNameTestData/NoAttributeClassForNewName.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNameTestData/NoAttributeClassForNewName.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.GraphTypeNameTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.GraphTypeNameTestData { public class NoAttributeClassForNewName { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNamesTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNamesTests.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNamesTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNamesTests.cs index d5dbfdb2c..2e89697fd 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/GraphTypeNamesTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/GraphTypeNamesTests.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; @@ -15,9 +15,9 @@ namespace GraphQL.AspNet.Tests.Internal.Templating using GraphQL.AspNet.Common.Generics; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal; + using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Internal.Templating.GraphTypeNameTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.GraphTypeNameTestData; using NUnit.Framework; [TestFixture] @@ -62,22 +62,16 @@ public void Item_WithAttribute_ReturnsAttributeDefinedName() } [Test] - public void Item_ScalarParsesToScalarName_Always() + public void ScalarNames_AreUnique() { - foreach (var scalarType in GraphQLProviders.ScalarProvider.ScalarInstanceTypes) + var nameSet = new HashSet(); + foreach (var scalarType in GlobalTypes.ScalarInstanceTypes) { var scalar = InstanceFactory.CreateInstance(scalarType) as IScalarGraphType; - var nameSet = new HashSet(); - var concreteType = GraphQLProviders.ScalarProvider.RetrieveConcreteType(scalar.Name); - foreach (var typeKind in Enum.GetValues(typeof(TypeKind)).Cast()) - { - var name = GraphTypeNames.ParseName(concreteType, TypeKind.OBJECT); - if (!nameSet.Contains(name)) - nameSet.Add(name); - - Assert.AreEqual(1, nameSet.Count); - } + nameSet.Add(scalar.Name.ToLowerInvariant()); } + + Assert.AreEqual(GlobalTypes.ScalarInstanceTypes.Count(), nameSet.Count); } [Test] @@ -86,7 +80,7 @@ public void Item_EnumParsesToEnumName_RegardlessOfTypeKind() var nameSet = new HashSet(); foreach (var typeKind in Enum.GetValues(typeof(TypeKind)).Cast()) { - var name = GraphTypeNames.ParseName(typeof(EnumNameTest), TypeKind.OBJECT); + var name = GraphTypeNames.ParseName(typeof(EnumNameTest), typeKind); if (!nameSet.Contains(name)) nameSet.Add(name); @@ -100,7 +94,7 @@ public void Item_EnumWithTypeNameAttribute_ParseesToEnumName_RegardlessOfTypeKin var nameSet = new HashSet(); foreach (var typeKind in Enum.GetValues(typeof(TypeKind)).Cast()) { - var name = GraphTypeNames.ParseName(typeof(EnumNameTestWithTypeName), TypeKind.OBJECT); + var name = GraphTypeNames.ParseName(typeof(EnumNameTestWithTypeName), typeKind); if (!nameSet.Contains(name)) nameSet.Add(name); diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InputGraphFieldTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InputGraphFieldTemplateTests.cs similarity index 89% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/InputGraphFieldTemplateTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InputGraphFieldTemplateTests.cs index 8ae1ad5b3..0d825c2d7 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InputGraphFieldTemplateTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InputGraphFieldTemplateTests.cs @@ -7,15 +7,15 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates { using System.Linq; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Internal.Templating.PropertyTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.PropertyTestData; using NSubstitute; using NUnit.Framework; @@ -26,7 +26,7 @@ public void Parse_GeneralPropertyCheck() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); obj.Kind.Returns(TypeKind.INPUT_OBJECT); var parent = obj; @@ -40,10 +40,10 @@ public void Parse_GeneralPropertyCheck() Assert.AreEqual("name desc", template.Description); Assert.AreEqual(typeof(string), template.ObjectType); Assert.IsFalse(template.IsRequired); - Assert.AreEqual("String", template.TypeExpression.ToString()); + Assert.AreEqual("Type", template.TypeExpression.ToString()); Assert.AreEqual(1, template.AppliedDirectives.Count()); Assert.AreEqual("nameDirective", template.AppliedDirectives.Single().DirectiveName); - Assert.AreEqual("Name", template.InternalName); + Assert.AreEqual("Item0.Name", template.InternalName); } [Test] @@ -51,7 +51,7 @@ public void Parse_IsRequired_IsNotSet() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); obj.Kind.Returns(TypeKind.INPUT_OBJECT); var parent = obj; @@ -70,7 +70,7 @@ public void Parse_IsRequired_IsSet() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); obj.Kind.Returns(TypeKind.INPUT_OBJECT); var parent = obj; @@ -88,7 +88,7 @@ public void Parse_NotRequired_NullableType_ButWithExplicitNonNullTypeExpression_ { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); obj.Kind.Returns(TypeKind.INPUT_OBJECT); var parent = obj; @@ -98,7 +98,7 @@ public void Parse_NotRequired_NullableType_ButWithExplicitNonNullTypeExpression_ template.ValidateOrThrow(); // field does declare [Required] there for cannot have a default value - Assert.AreEqual("Input_ShoeData!", template.TypeExpression.ToString()); + Assert.AreEqual("Type!", template.TypeExpression.ToString()); } [Test] @@ -106,7 +106,7 @@ public void Parse_InterfaceAsPropertyType_ThrowsException() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); obj.Kind.Returns(TypeKind.INPUT_OBJECT); var parent = obj; @@ -125,7 +125,7 @@ public void Parse_TaskAsPropertyType_ThrowsException() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); obj.Kind.Returns(TypeKind.INPUT_OBJECT); var parent = obj; @@ -144,7 +144,7 @@ public void Parse_UnionAsPropertyType_ThrowsException() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); obj.Kind.Returns(TypeKind.INPUT_OBJECT); var parent = obj; @@ -163,7 +163,7 @@ public void Parse_ActionResultAsPropertyType_ThrowsException() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); obj.Kind.Returns(TypeKind.INPUT_OBJECT); var parent = obj; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InputObjectTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InputObjectTemplateTests.cs similarity index 95% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/InputObjectTemplateTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InputObjectTemplateTests.cs index c9f681290..5fdbf31f5 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InputObjectTemplateTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InputObjectTemplateTests.cs @@ -7,17 +7,19 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; using System.Linq; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; using GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests; using NUnit.Framework; [TestFixture] @@ -59,7 +61,6 @@ public void Parse_Object_GeneralPropertySettings_SetCorrectly() Assert.AreEqual(0, template.FieldTemplates.Count()); Assert.AreEqual(TypeKind.INPUT_OBJECT, template.Kind); Assert.AreEqual(nameof(SimpleObjectNoMethods), template.InternalName); - Assert.AreEqual("GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests.SimpleObjectNoMethods", template.InternalFullName); Assert.AreEqual(0, template.SecurityPolicies.Count); } @@ -375,6 +376,16 @@ public void Parse_DuplicatePropertyPaths_ThrowsException() }); } + [Test] + public void Parse_InternalName_WhenSupplied_IsRendered() + { + var template = new InputObjectGraphTypeTemplate(typeof(InputObjectWithInternalName)); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual("InputObjectWithName_33", template.InternalName); + } + [Test] public void Parse_OnRecord_YieldsTemplate() { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceGraphTypeTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceGraphTypeTemplateTests.cs similarity index 67% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceGraphTypeTemplateTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceGraphTypeTemplateTests.cs index a70b615d1..c600b99a8 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceGraphTypeTemplateTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceGraphTypeTemplateTests.cs @@ -7,13 +7,17 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates { + using System; + using System.Collections.Generic; using System.Linq; using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Internal.TypeTemplates; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Tests.Internal.Templating.InterfaceTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.InterfaceTestData; using NUnit.Framework; [TestFixture] @@ -30,7 +34,7 @@ public void Parse_FromInterface_GeneralPropertySettings_SetCorrectly() Assert.AreEqual("[type]/ISimpleInterface", template.Route.Path); Assert.AreEqual(null, template.Description); Assert.AreEqual(typeof(ISimpleInterface), template.ObjectType); - Assert.AreEqual(2, template.FieldTemplates.Count()); + Assert.AreEqual(2, Enumerable.Count(template.FieldTemplates)); } [Test] @@ -49,9 +53,9 @@ public void Parse_AssignedDirective_IsTemplatized() template.Parse(); template.ValidateOrThrow(); - Assert.AreEqual(1, template.AppliedDirectives.Count()); + Assert.AreEqual(1, Enumerable.Count(template.AppliedDirectives)); - var appliedDirective = template.AppliedDirectives.First(); + var appliedDirective = Enumerable.First(template.AppliedDirectives); Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); Assert.AreEqual(new object[] { 8, "big face" }, appliedDirective.Arguments); } @@ -66,13 +70,13 @@ public void Parse_ImplementedInterfaces_AreCaptured() Assert.IsNotNull(template); Assert.AreEqual("[type]/ITestableInterfaceImplementation", template.Route.Path); Assert.AreEqual(typeof(ITestableInterfaceImplementation), template.ObjectType); - Assert.AreEqual(5, template.DeclaredInterfaces.Count()); + Assert.AreEqual(5, Enumerable.Count(template.DeclaredInterfaces)); - Assert.IsTrue(template.DeclaredInterfaces.Contains(typeof(IInterface1))); - Assert.IsTrue(template.DeclaredInterfaces.Contains(typeof(IInterface2))); - Assert.IsTrue(template.DeclaredInterfaces.Contains(typeof(IInterface3))); - Assert.IsTrue(template.DeclaredInterfaces.Contains(typeof(INestedInterface1))); - Assert.IsTrue(template.DeclaredInterfaces.Contains(typeof(INestedInterface2))); + Assert.IsTrue(Enumerable.Contains(template.DeclaredInterfaces, typeof(IInterface1))); + Assert.IsTrue(Enumerable.Contains(template.DeclaredInterfaces, typeof(IInterface2))); + Assert.IsTrue(Enumerable.Contains(template.DeclaredInterfaces, typeof(IInterface3))); + Assert.IsTrue(Enumerable.Contains(template.DeclaredInterfaces, typeof(INestedInterface1))); + Assert.IsTrue(Enumerable.Contains(template.DeclaredInterfaces, typeof(INestedInterface2))); } [Test] @@ -87,8 +91,8 @@ public void Parse_InheritedUndeclaredMethodField_IsNotIncluded() Assert.AreEqual(2, template.FieldTemplates.Count); Assert.IsTrue(template.FieldTemplates.Any(x => string.Equals(x.Name, nameof(InterfaceThatInheritsUndeclaredMethodField.PropFieldOnInterface), System.StringComparison.OrdinalIgnoreCase))); Assert.IsTrue(template.FieldTemplates.Any(x => string.Equals(x.Name, nameof(InterfaceThatInheritsUndeclaredMethodField.MethodFieldOnInterface), System.StringComparison.OrdinalIgnoreCase))); - Assert.AreEqual(1, template.DeclaredInterfaces.Count()); - Assert.IsTrue(template.DeclaredInterfaces.Contains(typeof(InterfaceWithUndeclaredInterfaceField))); + Assert.AreEqual(1, Enumerable.Count(template.DeclaredInterfaces)); + Assert.IsTrue(Enumerable.Contains(template.DeclaredInterfaces, typeof(InterfaceWithUndeclaredInterfaceField))); } [Test] @@ -102,8 +106,18 @@ public void Parse_InheritedDeclaredMethodField_IsNotIncluded() Assert.IsTrue(template.FieldTemplates.Any(x => string.Equals(x.Name, nameof(InterfaceThatInheritsDeclaredMethodField.PropFieldOnInterface), System.StringComparison.OrdinalIgnoreCase))); Assert.IsTrue(template.FieldTemplates.Any(x => string.Equals(x.Name, nameof(InterfaceThatInheritsDeclaredMethodField.MethodFieldOnInterface), System.StringComparison.OrdinalIgnoreCase))); - Assert.AreEqual(1, template.DeclaredInterfaces.Count()); - Assert.IsTrue(template.DeclaredInterfaces.Contains(typeof(InterfaceWithDeclaredInterfaceField))); + Assert.AreEqual(1, Enumerable.Count(template.DeclaredInterfaces)); + Assert.IsTrue(Enumerable.Contains(template.DeclaredInterfaces, typeof(InterfaceWithDeclaredInterfaceField))); + } + + [Test] + public void Parse_InternalName_WhenSuppliedOnGraphType_IsExtractedCorrectly() + { + var template = new InterfaceGraphTypeTemplate(typeof(IInterfaceWIthInternalName)); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual("MyInterface_32", template.InternalName); } [Test] diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/IInterface1.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterface1.cs similarity index 80% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/IInterface1.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterface1.cs index 29f1a6758..d11757b52 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/IInterface1.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterface1.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.InterfaceTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.InterfaceTestData { public interface IInterface1 : INestedInterface1 { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/IInterface2.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterface2.cs similarity index 81% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/IInterface2.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterface2.cs index 94851c712..06b4f84c1 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/IInterface2.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterface2.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.InterfaceTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.InterfaceTestData { public interface IInterface2 : INestedInterface1, IInterface3 { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/IInterface3.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterface3.cs similarity index 79% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/IInterface3.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterface3.cs index e926c8a56..077816ce3 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/IInterface3.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterface3.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.InterfaceTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.InterfaceTestData { public interface IInterface3 { diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterfaceWIthInternalName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterfaceWIthInternalName.cs new file mode 100644 index 000000000..07a9d0661 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterfaceWIthInternalName.cs @@ -0,0 +1,23 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.InterfaceTestData +{ + using GraphQL.AspNet.Attributes; + + [GraphType(InternalName = "MyInterface_32")] + public interface IInterfaceWIthInternalName + { + [GraphField] + string Property1 { get; } + + [GraphField] + string Property2 { get; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/IInterfaceWithDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterfaceWithDirective.cs similarity index 73% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/IInterfaceWithDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterfaceWithDirective.cs index 5087c1ca7..bad0d6911 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/IInterfaceWithDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterfaceWithDirective.cs @@ -6,10 +6,10 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.InterfaceTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.InterfaceTestData { using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; [ApplyDirective(typeof(DirectiveWithArgs), 8, "big face")] public interface IInterfaceWithDirective diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/IInterfaceWithOverloads.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterfaceWithOverloads.cs similarity index 100% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/IInterfaceWithOverloads.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/IInterfaceWithOverloads.cs diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/INestedInterface1.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/INestedInterface1.cs similarity index 80% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/INestedInterface1.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/INestedInterface1.cs index ddfcbf202..68e2ca73d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/INestedInterface1.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/INestedInterface1.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.InterfaceTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.InterfaceTestData { public interface INestedInterface1 : INestedInterface2 { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/INestedInterface2.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/INestedInterface2.cs similarity index 80% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/INestedInterface2.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/INestedInterface2.cs index 8dc18e916..f8cc2d4cc 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/INestedInterface2.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/INestedInterface2.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.InterfaceTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.InterfaceTestData { public interface INestedInterface2 { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ISimpleInterface.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/ISimpleInterface.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ISimpleInterface.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/ISimpleInterface.cs index c3edf5b92..f466adc95 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/ISimpleInterface.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/ISimpleInterface.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.InterfaceTestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/ITestableInterfaceImplementation.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/ITestableInterfaceImplementation.cs similarity index 81% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/ITestableInterfaceImplementation.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/ITestableInterfaceImplementation.cs index d8ba9d336..1a1ba2153 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/ITestableInterfaceImplementation.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/ITestableInterfaceImplementation.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.InterfaceTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.InterfaceTestData { public interface ITestableInterfaceImplementation : IInterface1, IInterface2 { diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InterfaceThatInheritsDeclaredMethodField.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/InterfaceThatInheritsDeclaredMethodField.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InterfaceThatInheritsDeclaredMethodField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/InterfaceThatInheritsDeclaredMethodField.cs index 68f237751..411f974b9 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InterfaceThatInheritsDeclaredMethodField.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/InterfaceThatInheritsDeclaredMethodField.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.InterfaceTestData { public interface InterfaceThatInheritsDeclaredMethodField : InterfaceWithDeclaredInterfaceField { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/InterfaceThatInheritsUndeclaredMethodField.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/InterfaceThatInheritsUndeclaredMethodField.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/InterfaceThatInheritsUndeclaredMethodField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/InterfaceThatInheritsUndeclaredMethodField.cs index 1827b13d0..3d23bc006 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/InterfaceThatInheritsUndeclaredMethodField.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/InterfaceThatInheritsUndeclaredMethodField.cs @@ -7,10 +7,8 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.InterfaceTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.InterfaceTestData { - using GraphQL.AspNet.Attributes; - public interface InterfaceThatInheritsUndeclaredMethodField : InterfaceWithUndeclaredInterfaceField { string PropFieldOnInterface { get; set; } diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/InterfaceWithDeclaredInterfaceField.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/InterfaceWithDeclaredInterfaceField.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/InterfaceWithDeclaredInterfaceField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/InterfaceWithDeclaredInterfaceField.cs index 301e24f8c..7185e2ecb 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/InterfaceWithDeclaredInterfaceField.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/InterfaceWithDeclaredInterfaceField.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.InterfaceTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.InterfaceTestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InterfaceWithUndeclaredInterfaceField.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/InterfaceWithUndeclaredInterfaceField.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InterfaceWithUndeclaredInterfaceField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/InterfaceWithUndeclaredInterfaceField.cs index 9a044f78d..9dc6db8c5 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/InterfaceWithUndeclaredInterfaceField.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/InterfaceWithUndeclaredInterfaceField.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Engine.TypeMakers.TestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.InterfaceTestData { public interface InterfaceWithUndeclaredInterfaceField { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/MethodGraphFieldTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/MethodGraphFieldTemplateTests.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/MethodGraphFieldTemplateTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/MethodGraphFieldTemplateTests.cs index 4f6dce568..9318b0f0b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/MethodGraphFieldTemplateTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/MethodGraphFieldTemplateTests.cs @@ -7,33 +7,34 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates { using System.Linq; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; - using GraphQL.AspNet.Tests.Internal.Templating.MethodTestData; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.MethodTestData; using NSubstitute; using NUnit.Framework; [TestFixture] public class MethodGraphFieldTemplateTests { - private AspNet.Internal.TypeTemplates.MethodGraphFieldTemplate CreateMethodTemplate(string methodName) + private MethodGraphFieldTemplate CreateMethodTemplate(string methodName) { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var parent = obj; var methodInfo = typeof(TObject).GetMethod(methodName); - var template = new AspNet.Internal.TypeTemplates.MethodGraphFieldTemplate(parent, methodInfo, TypeKind.OBJECT); + var template = new MethodGraphFieldTemplate(parent, methodInfo, TypeKind.OBJECT); template.Parse(); template.ValidateOrThrow(); return template; @@ -44,16 +45,16 @@ public void DefaultValuesCheck() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var parent = obj; var methodInfo = typeof(MethodClass).GetMethod(nameof(MethodClass.SimpleMethodNoAttributes)); - var template = new AspNet.Internal.TypeTemplates.MethodGraphFieldTemplate(parent, methodInfo, TypeKind.OBJECT); + var template = new MethodGraphFieldTemplate(parent, methodInfo, TypeKind.OBJECT); template.Parse(); template.ValidateOrThrow(); Assert.AreEqual(nameof(MethodClass.SimpleMethodNoAttributes), template.Name); - Assert.AreEqual($"Item0.{nameof(MethodClass.SimpleMethodNoAttributes)}", template.InternalFullName); + Assert.AreEqual($"Item0.{nameof(MethodClass.SimpleMethodNoAttributes)}", template.InternalName); Assert.AreEqual($"[type]/Item0/{nameof(MethodClass.SimpleMethodNoAttributes)}", template.Route.Path); Assert.AreEqual(parent.Route.Path, template.Route.Parent.Path); Assert.AreEqual(methodInfo, template.Method); @@ -102,7 +103,7 @@ public void AlternateNameParameterParsing_SetsParamsCorrectly() Assert.AreEqual(1, template.Arguments.Count); var param = template.Arguments[0]; Assert.AreEqual("arg55", param.Name); - Assert.AreEqual("Item0.ParamWithAlternateName.arg1", param.InternalFullName); + Assert.AreEqual("Item0.ParamWithAlternateName.arg1", param.InternalName); } [Test] @@ -121,15 +122,6 @@ public void InvalidParameterName_ThrowsException() }); } - [Test] - public void InterfaceAsInputParameter_ThrowsException() - { - Assert.Throws(() => - { - this.CreateMethodTemplate(nameof(MethodClass.InterfaceAsInputParam)); - }); - } - [Test] public void AsyncMethodWithNoReturnType_ThrowsException() { @@ -165,10 +157,10 @@ public void ArrayFromMethod_YieldsTemplate() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var expectedTypeExpression = new GraphTypeExpression( - typeof(TwoPropertyObject).FriendlyName(), + Constants.Other.DEFAULT_TYPE_EXPRESSION_TYPE_NAME, MetaGraphTypes.IsList); var parent = obj; @@ -183,7 +175,7 @@ public void Parse_AssignedDirective_IsTemplatized() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var expectedTypeExpression = new GraphTypeExpression( typeof(TwoPropertyObject).FriendlyName(), @@ -204,7 +196,7 @@ public void DefaultNonNullableParameter_NotMarkedRequired() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var parent = obj; var template = this.CreateMethodTemplate(nameof(MethodClass.DefaultNonNullableParameter)); @@ -226,5 +218,18 @@ public void InvalidTypeExpression_ThrowsException() this.CreateMethodTemplate(nameof(MethodClass.InvalidTypeExpression)); }); } + + [Test] + public void InternalName_IsSetCorrectly() + { + var obj = Substitute.For(); + obj.Route.Returns(new SchemaItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); + + var parent = obj; + var template = this.CreateMethodTemplate(nameof(MethodClass.MethodWithInternalName)); + + Assert.AreEqual("Method_InternalName_21", template.InternalName); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/MethodTestData/ArrayMethodObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/MethodTestData/ArrayMethodObject.cs similarity index 78% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/MethodTestData/ArrayMethodObject.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/MethodTestData/ArrayMethodObject.cs index 5323fb855..a29dd0fd3 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/MethodTestData/ArrayMethodObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/MethodTestData/ArrayMethodObject.cs @@ -7,10 +7,10 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.MethodTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.MethodTestData { using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ArrayMethodObject { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/MethodTestData/MethodClass.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/MethodTestData/MethodClass.cs similarity index 91% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/MethodTestData/MethodClass.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/MethodTestData/MethodClass.cs index f7e9f635e..a2d57dabe 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/MethodTestData/MethodClass.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/MethodTestData/MethodClass.cs @@ -7,16 +7,14 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.MethodTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.MethodTestData { using System.Collections.Generic; using System.ComponentModel; using System.Threading.Tasks; using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - using GraphQL.AspNet.Tests.Framework.Interfaces; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Common.Interfaces; public class MethodClass { @@ -163,5 +161,11 @@ public TwoPropertyObject InvalidTypeExpression() { return null; } + + [GraphField(InternalName = "Method_InternalName_21")] + public Task MethodWithInternalName() + { + return Task.FromResult(new TwoPropertyObject()); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/MethodTestData/MethodClassWithDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/MethodTestData/MethodClassWithDirective.cs similarity index 75% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/MethodTestData/MethodClassWithDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/MethodTestData/MethodClassWithDirective.cs index f559532be..c8e279133 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/MethodTestData/MethodClassWithDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/MethodTestData/MethodClassWithDirective.cs @@ -7,10 +7,10 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.MethodTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.MethodTestData { using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; public class MethodClassWithDirective { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectGraphTypeTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectGraphTypeTemplateTests.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectGraphTypeTemplateTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectGraphTypeTemplateTests.cs index f0576555e..9c361959f 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectGraphTypeTemplateTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectGraphTypeTemplateTests.cs @@ -7,22 +7,22 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates { using System; using System.Collections.Generic; using System.Linq; using System.Reflection; - using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests; using NUnit.Framework; [TestFixture] @@ -272,7 +272,7 @@ public void Parse_ArrayProperty_ExtractsListOfCoreType() Assert.AreEqual(1, template.FieldTemplates.Count); var expectedTypeExpression = new GraphTypeExpression( - typeof(TwoPropertyObject).FriendlyName(), + Constants.Other.DEFAULT_TYPE_EXPRESSION_TYPE_NAME, MetaGraphTypes.IsList); Assert.AreEqual(1, template.FieldTemplates.Count()); @@ -319,36 +319,6 @@ public void Parse_InheritedProperties_AreValidFields() Assert.AreEqual("Property2", fieldTemplate2.Name); } - [Test] - public void Parse_WhenStructAKnownScalar_ThrowsException() - { - using var point = new GraphQLGlobalRestorePoint(); - - GraphQLProviders.ScalarProvider.RegisterCustomScalar(typeof(SimpleScalarStructGraphType)); - - Assert.Throws(() => - { - var template = new ObjectGraphTypeTemplate(typeof(SimpleScalarStructGraphType)); - template.Parse(); - template.ValidateOrThrow(); - }); - } - - [Test] - public void Parse_WhenObjectIsAKnownScalar_ThrowsException() - { - using var point = new GraphQLGlobalRestorePoint(); - - GraphQLProviders.ScalarProvider.RegisterCustomScalar(typeof(SimpleScalarObjectGraphType)); - - Assert.Throws(() => - { - var template = new ObjectGraphTypeTemplate(typeof(SimpleScalarObjectGraphType)); - template.Parse(); - template.ValidateOrThrow(); - }); - } - [Test] public void Parse_AssignedDirective_IsTemplatized() { @@ -382,8 +352,8 @@ public void Parse_ExplicitInheritedMethodBasedField_IsSeenAsAGraphField() template.ValidateOrThrow(); Assert.AreEqual(2, template.FieldTemplates.Count); - Assert.IsTrue(template.FieldTemplates.Any(x => x.InternalName == nameof(ObjectThatInheritsExplicitMethodField.FieldOnObject))); - Assert.IsTrue(template.FieldTemplates.Any(x => x.InternalName == nameof(ObjectWithExplicitMethodField.FieldOnBaseObject))); + Assert.IsTrue(template.FieldTemplates.Any(x => x.InternalName == $"{nameof(ObjectThatInheritsExplicitMethodField)}.{nameof(ObjectThatInheritsExplicitMethodField.FieldOnObject)}")); + Assert.IsTrue(template.FieldTemplates.Any(x => x.InternalName == $"{nameof(ObjectThatInheritsExplicitMethodField)}.{nameof(ObjectWithExplicitMethodField.FieldOnBaseObject)}")); } [Test] @@ -394,8 +364,31 @@ public void Parse_NonExplicitMethodBasedField_IsSeenAsTemplatefield() template.ValidateOrThrow(); Assert.AreEqual(2, template.FieldTemplates.Count); - Assert.IsTrue(template.FieldTemplates.Any(x => x.InternalName == nameof(ObjectThatInheritsNonExplicitMethodField.FieldOnObject))); - Assert.IsTrue(template.FieldTemplates.Any(x => x.InternalName == nameof(ObjectWithNonExplicitMethodField.FieldOnBaseObject))); + Assert.IsTrue(template.FieldTemplates.Any(x => x.DeclaredName == nameof(ObjectThatInheritsNonExplicitMethodField.FieldOnObject))); + Assert.IsTrue(template.FieldTemplates.Any(x => x.DeclaredName == nameof(ObjectWithNonExplicitMethodField.FieldOnBaseObject))); + } + + [Test] + public void Parse_StaticMethodsWithProperGraphFieldDeclarations_AreSkipped() + { + var template = new ObjectGraphTypeTemplate(typeof(ObjectWithStatics)); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual(2, template.FieldTemplates.Count()); + + Assert.IsNotNull(template.FieldTemplates.SingleOrDefault(x => x.Route.Name == nameof(ObjectWithStatics.InstanceProperty))); + Assert.IsNotNull(template.FieldTemplates.SingleOrDefault(x => x.Route.Name == nameof(ObjectWithStatics.InstanceMethod))); + } + + [Test] + public void Parse_InternalName_WhenSuppliedOnGraphType_IsExtractedCorrectly() + { + var template = new ObjectGraphTypeTemplate(typeof(ObjectWithInternalName)); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual("MyObjectWithInternalName_33", template.InternalName); } [Test] @@ -431,34 +424,5 @@ public void Parse_InternalInheritedMembers_AreNotTemplated() Assert.AreEqual("Method3", fieldTemplate0.Name); Assert.AreEqual("Field1", fieldTemplate1.Name); } - - [Test] - public void Parse_Struct_WithGraphTypeNameOverride_ParsesCorrectly() - { - var template = new ObjectGraphTypeTemplate(typeof(SimpleScalarStructWithTypeOverride)); - template.Parse(); - template.ValidateOrThrow(); - - Assert.IsNotNull(template); - Assert.AreEqual("SomeTypeName", template.Name); - } - - [Test] - public void Parse_Record_ParsesCorrectly() - { - var template = new ObjectGraphTypeTemplate(typeof(ObjectRecord)); - template.Parse(); - template.ValidateOrThrow(); - - Assert.AreEqual(2, template.FieldTemplates.Count); - var field1 = template.FieldTemplates.SingleOrDefault(x => x.Name == "Property1"); - var field2 = template.FieldTemplates.SingleOrDefault(x => x.Name == "Property2"); - - Assert.IsNotNull(field1); - Assert.IsNotNull(field2); - - Assert.AreEqual(typeof(int), field1.ObjectType); - Assert.AreEqual(typeof(string), field2.ObjectType); - } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/CustomStruct.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/CustomStruct.cs similarity index 81% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/CustomStruct.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/CustomStruct.cs index 5da76a453..b20c9b475 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/CustomStruct.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/CustomStruct.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { public struct CustomStruct { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/DescriptionObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/DescriptionObject.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/DescriptionObject.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/DescriptionObject.cs index 95569f0ec..69ca9bf73 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/DescriptionObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/DescriptionObject.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using System.ComponentModel; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/DescriptionStruct.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/DescriptionStruct.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/DescriptionStruct.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/DescriptionStruct.cs index 7e71d7d8d..b9cdb8594 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/DescriptionStruct.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/DescriptionStruct.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using System.ComponentModel; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ForceSkippedStruct.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ForceSkippedStruct.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ForceSkippedStruct.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ForceSkippedStruct.cs index db2ea3110..77405da66 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ForceSkippedStruct.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ForceSkippedStruct.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ForcedSkippedObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ForcedSkippedObject.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ForcedSkippedObject.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ForcedSkippedObject.cs index d9dccdbf9..3f1727755 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ForcedSkippedObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ForcedSkippedObject.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/IInputObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/IInputObject.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/IInputObject.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/IInputObject.cs index ea3af61f8..357f74ce9 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/IInputObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/IInputObject.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { public interface IInputObject { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputNameObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputNameObject.cs similarity index 83% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputNameObject.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputNameObject.cs index 30556bd5a..48fb9e623 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputNameObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputNameObject.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectIWithTaskProperty.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectIWithTaskProperty.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectIWithTaskProperty.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectIWithTaskProperty.cs index 93a82b595..b00f217d7 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectIWithTaskProperty.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectIWithTaskProperty.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using System.Threading.Tasks; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectWithDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithDirective.cs similarity index 74% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectWithDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithDirective.cs index c80715041..7777de2f6 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectWithDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithDirective.cs @@ -6,10 +6,10 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; [ApplyDirective(typeof(DirectiveWithArgs), 33, "input object arg")] public class InputObjectWithDirective diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectWithDuplicateProperty.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithDuplicateProperty.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectWithDuplicateProperty.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithDuplicateProperty.cs index 49a9c8a21..b81a2c77f 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectWithDuplicateProperty.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithDuplicateProperty.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectWithGraphActionProperty.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithGraphActionProperty.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectWithGraphActionProperty.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithGraphActionProperty.cs index 7a752ff94..5225c1b7a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectWithGraphActionProperty.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithGraphActionProperty.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Interfaces.Controllers; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectWithInterfaceProperty.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithInterfaceProperty.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectWithInterfaceProperty.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithInterfaceProperty.cs index c3859038d..3d4d173c1 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectWithInterfaceProperty.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithInterfaceProperty.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { public class InputObjectWithInterfaceProperty { diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithInternalName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithInternalName.cs new file mode 100644 index 000000000..95dc3bc19 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithInternalName.cs @@ -0,0 +1,20 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests +{ + using System.Threading.Tasks; + using GraphQL.AspNet.Attributes; + + [GraphType(InternalName = "InputObjectWithName_33")] + public class InputObjectWithInternalName + { + public int Id { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectWithUnionProxyProperty.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithUnionProxyProperty.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectWithUnionProxyProperty.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithUnionProxyProperty.cs index 0f503b8a5..cbec99a5b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputObjectWithUnionProxyProperty.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputObjectWithUnionProxyProperty.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Interfaces.Schema; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputRecord.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputRecord.cs similarity index 100% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/InputRecord.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/InputRecord.cs diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectRecord.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectRecord.cs similarity index 100% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectRecord.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectRecord.cs diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectThatInheritsExplicitMethodField.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectThatInheritsExplicitMethodField.cs similarity index 86% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectThatInheritsExplicitMethodField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectThatInheritsExplicitMethodField.cs index ceb09a837..da5efc7ac 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectThatInheritsExplicitMethodField.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectThatInheritsExplicitMethodField.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectThatInheritsNonExplicitMethodField.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectThatInheritsNonExplicitMethodField.cs similarity index 81% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectThatInheritsNonExplicitMethodField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectThatInheritsNonExplicitMethodField.cs index fc19eedd0..61e4fc6f6 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectThatInheritsNonExplicitMethodField.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectThatInheritsNonExplicitMethodField.cs @@ -7,10 +7,8 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { - using GraphQL.AspNet.Attributes; - public class ObjectThatInheritsNonExplicitMethodField : ObjectWithNonExplicitMethodField { public int FieldOnObject(int param2) diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithDeconstructor.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithDeconstructor.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithDeconstructor.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithDeconstructor.cs index 4d8f8b750..b62f11e91 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithDeconstructor.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithDeconstructor.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { public class ObjectWithDeconstructor { @@ -15,7 +15,7 @@ public class ObjectWithDeconstructor public void Deconstruct(out string prop1) { - prop1 = Property1; + prop1 = this.Property1; } public override string ToString() diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithDirective.cs similarity index 74% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithDirective.cs index 1eb178bcd..ce5234fa1 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithDirective.cs @@ -6,10 +6,10 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; [ApplyDirective(typeof(DirectiveWithArgs), 1, "object arg")] public class ObjectWithDirective diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithExplicitMethodField.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithExplicitMethodField.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithExplicitMethodField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithExplicitMethodField.cs index b9c8be378..b94b67ed1 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithExplicitMethodField.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithExplicitMethodField.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithInternalFields.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithInternalFields.cs similarity index 100% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithInternalFields.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithInternalFields.cs diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithInternalInheritedFields.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithInternalInheritedFields.cs similarity index 100% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithInternalInheritedFields.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithInternalInheritedFields.cs diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithInternalName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithInternalName.cs new file mode 100644 index 000000000..576a074b6 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithInternalName.cs @@ -0,0 +1,19 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests +{ + using GraphQL.AspNet.Attributes; + + [GraphType(InternalName = "MyObjectWithInternalName_33")] + public class ObjectWithInternalName + { + public int Property1 { get; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithInvalidNonDeclaredMethods.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithInvalidNonDeclaredMethods.cs similarity index 92% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithInvalidNonDeclaredMethods.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithInvalidNonDeclaredMethods.cs index 52cb3b8a6..311feab64 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithInvalidNonDeclaredMethods.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithInvalidNonDeclaredMethods.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using System.Collections.Generic; using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithMutationOperation.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithMutationOperation.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithMutationOperation.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithMutationOperation.cs index 7b8d4809b..4c7935c06 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithMutationOperation.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithMutationOperation.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithNoSetters.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithNoSetters.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithNoSetters.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithNoSetters.cs index bf0078d58..30f2df450 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithNoSetters.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithNoSetters.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { public class ObjectWithNoSetters { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithNonExplicitMethodField.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithNonExplicitMethodField.cs similarity index 80% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithNonExplicitMethodField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithNonExplicitMethodField.cs index fddc319bb..95ccbe753 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/ObjectWithNonExplicitMethodField.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithNonExplicitMethodField.cs @@ -7,10 +7,8 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { - using GraphQL.AspNet.Attributes; - public class ObjectWithNonExplicitMethodField { public int FieldOnBaseObject(int param1) diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithStatics.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithStatics.cs new file mode 100644 index 000000000..b2c931c1b --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithStatics.cs @@ -0,0 +1,34 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests +{ + using GraphQL.AspNet.Attributes; + + public class ObjectWithStatics + { + [GraphField] + public static int StaticMethod(int a, int b) + { + return a - b; + } + + [GraphField] + public static int StaticProperty { get; set; } + + [GraphField] + public int InstanceMethod(int a, int b) + { + return a + b; + } + + [GraphField] + public int InstanceProperty { get; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/OneMarkedMethod.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/OneMarkedMethod.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/OneMarkedMethod.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/OneMarkedMethod.cs index ec4063330..15bc38a0b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/OneMarkedMethod.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/OneMarkedMethod.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/OneMarkedProperty.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/OneMarkedProperty.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/OneMarkedProperty.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/OneMarkedProperty.cs index cd01d7bbf..76cfa144b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/OneMarkedProperty.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/OneMarkedProperty.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/RequiredConstructor.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/RequiredConstructor.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/RequiredConstructor.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/RequiredConstructor.cs index 0f3ebdb25..d97652a02 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/RequiredConstructor.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/RequiredConstructor.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { public class RequiredConstructor { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleObjectNoMethods.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleObjectNoMethods.cs similarity index 80% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleObjectNoMethods.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleObjectNoMethods.cs index 2eb9c1b14..d0970025d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleObjectNoMethods.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleObjectNoMethods.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { public class SimpleObjectNoMethods { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleObjectScalar.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleObjectScalar.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleObjectScalar.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleObjectScalar.cs index 39ff86b7c..9b6d0138d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleObjectScalar.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleObjectScalar.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { public class SimpleObjectScalar { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleScalarObjectGraphType.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleScalarObjectGraphType.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleScalarObjectGraphType.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleScalarObjectGraphType.cs index 867c2aa79..46ed73141 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleScalarObjectGraphType.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleScalarObjectGraphType.cs @@ -7,10 +7,9 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using System; - using GraphQL.AspNet.Common; using GraphQL.AspNet.Schemas.TypeSystem.Scalars; public class SimpleScalarObjectGraphType : ScalarGraphTypeBase @@ -23,8 +22,6 @@ public SimpleScalarObjectGraphType() public override ScalarValueType ValueType => ScalarValueType.String; - public override TypeCollection OtherKnownTypes { get; } = new TypeCollection(); - public override object Resolve(ReadOnlySpan data) { return null; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleScalarStruct.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleScalarStruct.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleScalarStruct.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleScalarStruct.cs index 075715026..3785f57d7 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleScalarStruct.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleScalarStruct.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { public struct SimpleScalarStruct { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleScalarStructGraphType.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleScalarStructGraphType.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleScalarStructGraphType.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleScalarStructGraphType.cs index 67735a6d9..8a6fb2002 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleScalarStructGraphType.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleScalarStructGraphType.cs @@ -7,10 +7,9 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using System; - using GraphQL.AspNet.Common; using GraphQL.AspNet.Schemas.TypeSystem.Scalars; public class SimpleScalarStructGraphType : ScalarGraphTypeBase @@ -23,8 +22,6 @@ public SimpleScalarStructGraphType() public override ScalarValueType ValueType => ScalarValueType.String; - public override TypeCollection OtherKnownTypes { get; } = new TypeCollection(); - public override object Resolve(ReadOnlySpan data) { return null; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleScalarStructWithTypeOverride.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleScalarStructWithTypeOverride.cs similarity index 100% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleScalarStructWithTypeOverride.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleScalarStructWithTypeOverride.cs diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleStructNoMethods.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleStructNoMethods.cs similarity index 80% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleStructNoMethods.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleStructNoMethods.cs index 2d7d35dc9..dcbc2194b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/SimpleStructNoMethods.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/SimpleStructNoMethods.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { public struct SimpleStructNoMethods { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructOneMarkedMethod.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructOneMarkedMethod.cs similarity index 87% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructOneMarkedMethod.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructOneMarkedMethod.cs index 2710c54ae..30859e123 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructOneMarkedMethod.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructOneMarkedMethod.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructOneMarkedProperty.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructOneMarkedProperty.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructOneMarkedProperty.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructOneMarkedProperty.cs index 65bb1b788..0dfddb953 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructOneMarkedProperty.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructOneMarkedProperty.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructTwoMethodsWithSameName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructTwoMethodsWithSameName.cs similarity index 88% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructTwoMethodsWithSameName.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructTwoMethodsWithSameName.cs index db5196728..7843edda8 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructTwoMethodsWithSameName.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructTwoMethodsWithSameName.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructTwoMethodsWithSameNameWithAttributeDiff.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructTwoMethodsWithSameNameWithAttributeDiff.cs similarity index 88% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructTwoMethodsWithSameNameWithAttributeDiff.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructTwoMethodsWithSameNameWithAttributeDiff.cs index f23c44d84..f92fccd40 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructTwoMethodsWithSameNameWithAttributeDiff.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructTwoMethodsWithSameNameWithAttributeDiff.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructWithInvalidNonDeclaredMethods.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructWithInvalidNonDeclaredMethods.cs similarity index 92% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructWithInvalidNonDeclaredMethods.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructWithInvalidNonDeclaredMethods.cs index 8402317c1..c85ae04ee 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructWithInvalidNonDeclaredMethods.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructWithInvalidNonDeclaredMethods.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using System.Collections.Generic; using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructWithNoSetters.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructWithNoSetters.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructWithNoSetters.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructWithNoSetters.cs index 61c7acf1f..08db2d059 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/StructWithNoSetters.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/StructWithNoSetters.cs @@ -6,7 +6,7 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { public struct StructWithNoSetters { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/TwoMethodsWithSameName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/TwoMethodsWithSameName.cs similarity index 88% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/TwoMethodsWithSameName.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/TwoMethodsWithSameName.cs index 15265fa73..d7d15029b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/TwoMethodsWithSameName.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/TwoMethodsWithSameName.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/TwoMethodsWithSameNameWithAttributeDiff.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/TwoMethodsWithSameNameWithAttributeDiff.cs similarity index 88% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/TwoMethodsWithSameNameWithAttributeDiff.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/TwoMethodsWithSameNameWithAttributeDiff.cs index cce879993..a9291f58c 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/TwoMethodsWithSameNameWithAttributeDiff.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/TwoMethodsWithSameNameWithAttributeDiff.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/TypeWithArrayProperty.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/TypeWithArrayProperty.cs similarity index 74% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/TypeWithArrayProperty.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/TypeWithArrayProperty.cs index 23d537511..dcf7221df 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/TypeWithArrayProperty.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/TypeWithArrayProperty.cs @@ -7,9 +7,9 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ObjectTypeTests +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests { - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class TypeWithArrayProperty { diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ParameterTestData/ObjectWithGraphSkip.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ParameterTestData/ObjectWithGraphSkip.cs new file mode 100644 index 000000000..38411ec83 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ParameterTestData/ObjectWithGraphSkip.cs @@ -0,0 +1,19 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ParameterTestData +{ + using GraphQL.AspNet.Attributes; + + [GraphSkip] + public class ObjectWithGraphSkip + { + public int Property1 { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ParameterTestData/ParameterTestClass.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ParameterTestData/ParameterTestClass.cs similarity index 81% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/ParameterTestData/ParameterTestClass.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ParameterTestData/ParameterTestClass.cs index 7f57f08db..b723363bc 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ParameterTestData/ParameterTestClass.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ParameterTestData/ParameterTestClass.cs @@ -9,14 +9,15 @@ // ReSharper disable InconsistentNaming // ReSharper disable UnusedParameter.Global -namespace GraphQL.AspNet.Tests.Internal.Templating.ParameterTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ParameterTestData { using System.Collections.Generic; using System.ComponentModel; using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.CommonHelpers; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; + using Microsoft.AspNetCore.Mvc; public class ParameterTestClass { @@ -42,11 +43,17 @@ public int TestMethod( IEnumerable[] arrayOfEnumerableOfArrayOfObjects, Person[][][][][][][][][][][][][][][][][][][] deepArray, [FromGraphQL(TypeExpression = "[Type!")] int invalidTypeExpression, + [GraphSkip] Person graphSkipArgument, + ObjectWithGraphSkip typeHasGraphSkip, [FromGraphQL(TypeExpression = "Type!")] int? compatiableTypeExpressionSingle, // add more specificity [FromGraphQL(TypeExpression = "[Type!]!")] int?[] compatiableTypeExpressionList, // add more specificity [FromGraphQL(TypeExpression = "[Type]")] int incompatiableTypeExpressionListToSingle, [FromGraphQL(TypeExpression = "Type!")] int[] incompatiableTypeExpressionSingleToList, [FromGraphQL(TypeExpression = "Type")] int incompatiableTypeExpressionNullToNotNull, // nullable expression, actual type is not nullable + [FromGraphQL] TwoPropertyObject justFromGraphQLDeclaration, + [FromServices] TwoPropertyObject justFromServicesDeclaration, + [FromGraphQL] [FromServices] TwoPropertyObject doubleDeclaredObject, + [FromGraphQL(InternalName = "customInternalName_38")] int internalNameObject, Person defaultValueObjectArg = null, string defaultValueStringArg = null, string defaultValueStringArgWithValue = "abc", diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyGraphFieldTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyGraphFieldTemplateTests.cs similarity index 78% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyGraphFieldTemplateTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyGraphFieldTemplateTests.cs index f6e9f4a38..184ecb15c 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyGraphFieldTemplateTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyGraphFieldTemplateTests.cs @@ -7,21 +7,20 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates { using System.Collections.Generic; using System.Linq; - using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Internal; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal.TypeTemplates; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; - using GraphQL.AspNet.Tests.Internal.Templating.PropertyTestData; + using GraphQL.AspNet.Security; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.PropertyTestData; using NSubstitute; using NUnit.Framework; @@ -33,7 +32,7 @@ public void Parse_DescriptionAttribute_SetsValue() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var parent = obj; var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.Address1)); @@ -49,7 +48,7 @@ public void Parse_DepreciationAttribute_SetsValues() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var parent = obj; var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.Address2)); @@ -63,7 +62,7 @@ public void Parse_PropertyAsAnObject_SetsReturnTypeCorrectly() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var parent = obj; var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.Hair)); @@ -79,7 +78,7 @@ public void Parse_PropertyAsAList_SetsReturnType_AsCoreItemNotTheList() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var parent = obj; @@ -97,7 +96,7 @@ public void Parse_SecurityPolices_AreAdded() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var parent = obj; var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.LastName)); @@ -105,7 +104,7 @@ public void Parse_SecurityPolices_AreAdded() template.Parse(); template.ValidateOrThrow(); - Assert.AreEqual(1, template.SecurityPolicies.Count()); + Assert.AreEqual(1, Enumerable.Count(template.SecurityPolicies)); } [Test] @@ -113,7 +112,7 @@ public void Parse_InvalidName_ThrowsException() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var parent = obj; var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.City)); @@ -131,7 +130,7 @@ public void Parse_SkipDefined_ThrowsException() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var parent = obj; var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.State)); @@ -150,7 +149,7 @@ public void Parse_BasicObject_PropertyWithNoGetter_ThrowsException() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var parent = obj; var propInfo = typeof(NoGetterOnProperty).GetProperty(nameof(NoGetterOnProperty.Prop1)); @@ -158,10 +157,12 @@ public void Parse_BasicObject_PropertyWithNoGetter_ThrowsException() var template = new PropertyGraphFieldTemplate(parent, propInfo, TypeKind.OBJECT); template.Parse(); - Assert.Throws(() => + var ex = Assert.Throws(() => { template.ValidateOrThrow(); }); + + Assert.IsTrue(ex.Message.Contains("does not define a public getter")); } [Test] @@ -169,10 +170,10 @@ public void Parse_BasicObject_PropertyReturnsArray_YieldsTemplate() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var expectedTypeExpression = new GraphTypeExpression( - typeof(TwoPropertyObject).FriendlyName(), + Constants.Other.DEFAULT_TYPE_EXPRESSION_TYPE_NAME, MetaGraphTypes.IsList); var parent = obj; @@ -191,10 +192,10 @@ public void Parse_BasicObject_PropertyReturnsArrayOfKeyValuePair_YieldsTemplate( { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var expectedTypeExpression = new GraphTypeExpression( - typeof(KeyValuePair).FriendlyGraphTypeName(), + Constants.Other.DEFAULT_TYPE_EXPRESSION_TYPE_NAME, // expression is expected to be unnamed at the template level MetaGraphTypes.IsList, MetaGraphTypes.IsNotNull); // structs can't be null @@ -214,7 +215,7 @@ public void Parse_AssignedDirective_IsTemplatized() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var expectedTypeExpression = new GraphTypeExpression( typeof(KeyValuePair).FriendlyGraphTypeName(), @@ -227,9 +228,9 @@ public void Parse_AssignedDirective_IsTemplatized() var template = new PropertyGraphFieldTemplate(parent, propInfo, TypeKind.OBJECT); template.Parse(); template.ValidateOrThrow(); - Assert.AreEqual(1, template.AppliedDirectives.Count()); + Assert.AreEqual(1, Enumerable.Count(template.AppliedDirectives)); - var appliedDirective = template.AppliedDirectives.First(); + var appliedDirective = Enumerable.First(template.AppliedDirectives); Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); Assert.AreEqual(new object[] { 55, "property arg" }, appliedDirective.Arguments); } @@ -239,7 +240,7 @@ public void InvalidTypeExpression_ThrowsException() { var obj = Substitute.For(); obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.InternalName.Returns("Item0"); var parent = obj; var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.InvalidTypeExpression)); @@ -252,5 +253,26 @@ public void InvalidTypeExpression_ThrowsException() template.ValidateOrThrow(); }); } + + [Test] + public void Parse_InternalName_IsSetCorrectly() + { + var obj = Substitute.For(); + obj.Route.Returns(new SchemaItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); + + var expectedTypeExpression = new GraphTypeExpression( + typeof(KeyValuePair).FriendlyGraphTypeName(), + MetaGraphTypes.IsList, + MetaGraphTypes.IsNotNull); // structs can't be null + + var parent = obj; + var propInfo = typeof(PropertyWithInternalName).GetProperty(nameof(PropertyWithInternalName.Prop1)); + + var template = new PropertyGraphFieldTemplate(parent, propInfo, TypeKind.OBJECT); + template.Parse(); + template.ValidateOrThrow(); + Assert.AreEqual("prop_Field_223", template.InternalName); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/ArrayKeyValuePairObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/ArrayKeyValuePairObject.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/ArrayKeyValuePairObject.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/ArrayKeyValuePairObject.cs index 3494b60dd..497af0412 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/ArrayKeyValuePairObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/ArrayKeyValuePairObject.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.PropertyTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.PropertyTestData { using System.Collections.Generic; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/ArrayPropertyObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/ArrayPropertyObject.cs similarity index 74% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/ArrayPropertyObject.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/ArrayPropertyObject.cs index 2549b7275..5d0be82c5 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/ArrayPropertyObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/ArrayPropertyObject.cs @@ -7,9 +7,9 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.PropertyTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.PropertyTestData { - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ArrayPropertyObject { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/IPropInterface.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/IPropInterface.cs similarity index 81% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/IPropInterface.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/IPropInterface.cs index 3ba2a0097..4d3bc69ee 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/IPropInterface.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/IPropInterface.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.PropertyTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.PropertyTestData { public interface IPropInterface { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/NoGetterOnProperty.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/NoGetterOnProperty.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/NoGetterOnProperty.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/NoGetterOnProperty.cs index 24b3933b1..730161c7c 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/NoGetterOnProperty.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/NoGetterOnProperty.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.PropertyTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.PropertyTestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/PropAuthorizeAttribute.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/PropAuthorizeAttribute.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/PropAuthorizeAttribute.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/PropAuthorizeAttribute.cs index 7dd6bc891..7bc20c4d8 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/PropAuthorizeAttribute.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/PropAuthorizeAttribute.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.PropertyTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.PropertyTestData { using System; using Microsoft.AspNetCore.Authorization; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/PropertyClassWithDirective.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/PropertyClassWithDirective.cs similarity index 74% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/PropertyClassWithDirective.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/PropertyClassWithDirective.cs index 76fa7ca7c..40ab0902f 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/PropertyClassWithDirective.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/PropertyClassWithDirective.cs @@ -6,10 +6,10 @@ // -- // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.PropertyTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.PropertyTestData { using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Internal.Templating.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; public class PropertyClassWithDirective { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/PropertyProxy.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/PropertyProxy.cs similarity index 82% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/PropertyProxy.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/PropertyProxy.cs index 7abbf00b1..cedb375f4 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/PropertyProxy.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/PropertyProxy.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.PropertyTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.PropertyTestData { using GraphQL.AspNet.Schemas.TypeSystem; diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/PropertyWithInternalName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/PropertyWithInternalName.cs new file mode 100644 index 000000000..09dba2896 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/PropertyWithInternalName.cs @@ -0,0 +1,19 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.PropertyTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; + + public class PropertyWithInternalName + { + [GraphField(InternalName = "prop_Field_223")] + public int Prop1 { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/SimplePropertyObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/SimplePropertyObject.cs similarity index 90% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/SimplePropertyObject.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/SimplePropertyObject.cs index 294089e10..a3ca8bf3a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/PropertyTestData/SimplePropertyObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/PropertyTestData/SimplePropertyObject.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.PropertyTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.PropertyTestData { using System.Collections.Generic; using System.ComponentModel; @@ -15,7 +15,6 @@ namespace GraphQL.AspNet.Tests.Internal.Templating.PropertyTestData using System.Threading.Tasks; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers.ActionResults; - using GraphQL.AspNet.Schemas.TypeSystem; public class SimplePropertyObject { @@ -71,6 +70,6 @@ public class ShoeData public PropertyProxy UnionProxyProperty { get; set; } - public ObjectReturnedGraphActionResult ActionResultProperty { get; set; } + public OperationCompleteGraphActionResult ActionResultProperty { get; set; } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/RuntimeControllerActionTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/RuntimeControllerActionTemplateTests.cs new file mode 100644 index 000000000..1f2642204 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/RuntimeControllerActionTemplateTests.cs @@ -0,0 +1,64 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates +{ + using System.Linq; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Common.Interfaces; + using Microsoft.Extensions.DependencyInjection; + using NUnit.Framework; + + [TestFixture] + public class RuntimeControllerActionTemplateTests + { + [Test] + public void MapQuery_PossibleTypesCheck() + { + var options = new SchemaOptions(new ServiceCollection()); + var field = options.MapQuery("fieldName", (int a) => null as ISinglePropertyObject) + .AddPossibleTypes(typeof(TwoPropertyObjectV3), typeof(TwoPropertyObject)); + + var template = new RuntimeGraphControllerTemplate(field); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual(1, template.Actions.Count()); + + var fieldTemplate = template.Actions.First(); + Assert.AreEqual("fieldName", fieldTemplate.Name); + Assert.AreEqual(0, fieldTemplate.AppliedDirectives.Count()); + + var requiredTypes = fieldTemplate.RetrieveRequiredTypes().ToList(); + Assert.AreEqual(4, requiredTypes.Count); + Assert.IsNotNull(requiredTypes.SingleOrDefault(x => x.Type == typeof(int))); + Assert.IsNotNull(requiredTypes.SingleOrDefault(x => x.Type == typeof(ISinglePropertyObject))); + Assert.IsNotNull(requiredTypes.SingleOrDefault(x => x.Type == typeof(TwoPropertyObjectV3))); + Assert.IsNotNull(requiredTypes.SingleOrDefault(x => x.Type == typeof(TwoPropertyObject))); + } + + [Test] + public void MapQuery_InternalNameCheck() + { + var options = new SchemaOptions(new ServiceCollection()); + var field = options.MapQuery("fieldName", (int a) => 0) + .WithInternalName("internalFieldName"); + + var template = new RuntimeGraphControllerTemplate(field); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual(1, template.Actions.Count()); + Assert.AreEqual("internalFieldName", template.Actions.First().InternalName); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/RuntimeSchemaItemAttributeProviderTestData/PersonAttribute.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/RuntimeSchemaItemAttributeProviderTestData/PersonAttribute.cs new file mode 100644 index 000000000..a35440a56 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/RuntimeSchemaItemAttributeProviderTestData/PersonAttribute.cs @@ -0,0 +1,17 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.RuntimeSchemaItemAttributeProviderTestData +{ + using System; + + public class PersonAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/RuntimeSchemaItemAttributeProviderTestData/TeacherAttribute.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/RuntimeSchemaItemAttributeProviderTestData/TeacherAttribute.cs new file mode 100644 index 000000000..86a3a1e3f --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/RuntimeSchemaItemAttributeProviderTestData/TeacherAttribute.cs @@ -0,0 +1,15 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.RuntimeSchemaItemAttributeProviderTestData +{ + public class TeacherAttribute : PersonAttribute + { + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/RuntimeSchemaItemAttributeProviderTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/RuntimeSchemaItemAttributeProviderTests.cs new file mode 100644 index 000000000..e9d734ed8 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/RuntimeSchemaItemAttributeProviderTests.cs @@ -0,0 +1,85 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates +{ + using System; + using System.Collections.Generic; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Interfaces.Schema.RuntimeDefinitions; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Tests.Middleware.FieldSecurityMiddlewareTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.RuntimeSchemaItemAttributeProviderTestData; + using NSubstitute; + using NUnit.Framework; + + [TestFixture] + public class RuntimeSchemaItemAttributeProviderTests + { + public delegate int NoParamDelegate(); + + [Teacher] + public int MethodWithTeacherAttribute() + { + return 0; + } + + [Person] + public int MethodWithPersonAttribute() + { + return 0; + } + + public int MethodWithNoAttributes() + { + return 0; + } + + [TestCase(typeof(TeacherAttribute), true)] + [TestCase(typeof(PersonAttribute), true)] + [TestCase(typeof(AuthorAttribute), false)] + public void HasAttribute_DirectlyDefinedDoesProduceValue(Type typeToSearchFor, bool shouldBeFound) + { + // sanity check to ensure the world is working right and MethodInfo + // as an attribute provider behaves as expected + var mock = Substitute.For(); + + var method = typeof(RuntimeSchemaItemAttributeProviderTests) + .GetMethod(nameof(MethodWithTeacherAttribute)); + + var wasFound = method.HasAttribute(typeToSearchFor); + Assert.AreEqual(shouldBeFound, wasFound); + } + + [TestCase(typeof(TeacherAttribute), true)] + [TestCase(typeof(PersonAttribute), true)] + [TestCase(typeof(AuthorAttribute), false)] + public void HasAttribute_AllProvidedExternally(Type typeToSearchFor, bool shouldBeFound) + { + var mock = Substitute.For(); + + var method = typeof(RuntimeSchemaItemAttributeProviderTests) + .GetMethod(nameof(MethodWithNoAttributes)); + + var del = method.CreateDelegate(this); + mock.Resolver.Returns(del); + + // append the teacher attribute to a list in the provider + // not as part of the method + var attribs = new List(); + attribs.Add(new TeacherAttribute()); + mock.Attributes.Returns(attribs); + + var provider = new RuntimeSchemaItemAttributeProvider(mock); + + var wasFound = provider.HasAttribute(typeToSearchFor); + Assert.AreEqual(shouldBeFound, wasFound); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ScalarGraphTypeTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ScalarGraphTypeTemplateTests.cs new file mode 100644 index 000000000..eaae9faf2 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ScalarGraphTypeTemplateTests.cs @@ -0,0 +1,67 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates +{ + using System.Linq; + using GraphQL.AspNet.Directives.Global; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ScalarTestData; + using NuGet.Frameworks; + using NUnit.Framework; + + [TestFixture] + public class ScalarGraphTypeTemplateTests + { + [Test] + public void ValidScalar_PropertyCheck() + { + var template = new ScalarGraphTypeTemplate(typeof(MyTestScalar)); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual(TypeKind.SCALAR, template.Kind); + Assert.AreEqual("myScalar", template.Name); + Assert.AreEqual("[type]/myScalar", template.Route.Path); + Assert.AreEqual("myScalar Desc", template.Description); + Assert.IsTrue(template.Publish); + Assert.AreEqual("My.Test.Scalar", template.InternalName); + Assert.AreEqual(typeof(MyTestScalar), template.ScalarType); + Assert.AreEqual(typeof(MyTestObject), template.ObjectType); + Assert.IsFalse(template.DeclarationRequirements.HasValue); + + Assert.AreEqual(2, template.AppliedDirectives.Count()); + Assert.AreEqual("myDirective", template.AppliedDirectives.First().DirectiveName); + Assert.IsNull(template.AppliedDirectives.First().DirectiveType); + Assert.AreEqual(1, template.AppliedDirectives.First().Arguments.Count()); + Assert.AreEqual("arg1", template.AppliedDirectives.First().Arguments.First().ToString()); + + Assert.IsNull(template.AppliedDirectives.Skip(1).First().DirectiveName); + Assert.AreEqual(typeof(SkipDirective), template.AppliedDirectives.Skip(1).First().DirectiveType); + Assert.AreEqual(1, template.AppliedDirectives.Skip(1).First().Arguments.Count()); + Assert.AreEqual("argA", template.AppliedDirectives.Skip(1).First().Arguments.First().ToString()); + } + + [Test] + public void InValidScalar_ThrowsExceptionOnValidate() + { + var template = new ScalarGraphTypeTemplate(typeof(TwoPropertyObject)); + template.Parse(); + + Assert.Throws(() => + { + template.ValidateOrThrow(); + }); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ScalarTestData/MyTestObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ScalarTestData/MyTestObject.cs new file mode 100644 index 000000000..e974dab94 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ScalarTestData/MyTestObject.cs @@ -0,0 +1,15 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ScalarTestData +{ + public class MyTestObject + { + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ScalarTestData/MyTestScalar.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ScalarTestData/MyTestScalar.cs new file mode 100644 index 000000000..0cf6046e3 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ScalarTestData/MyTestScalar.cs @@ -0,0 +1,89 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ScalarTestData +{ + using System; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Directives.Global; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using NSubstitute; + + [ApplyDirective("directive1")] + public class MyTestScalar : IScalarGraphType + { + public MyTestScalar() + { + this.Kind = TypeKind.SCALAR; + this.SpecifiedByUrl = "http://mytestsclar.org"; + this.ValueType = ScalarValueType.Boolean; + this.Name = "myScalar"; + this.Description = "myScalar Desc"; + this.Route = new SchemaItemPath(SchemaItemCollections.Scalars, this.Name); + this.InternalName = "My.Test.Scalar"; + this.ObjectType = typeof(MyTestObject); + this.SourceResolver = Substitute.For(); + this.Publish = true; + + var dir = new AppliedDirectiveCollection(this); + dir.Add(new AppliedDirective("myDirective", "arg1")); + dir.Add(new AppliedDirective(typeof(SkipDirective), "argA")); + this.AppliedDirectives = dir; + } + + public ScalarValueType ValueType { get; } + + public ILeafValueResolver SourceResolver { get; set; } + + public string SpecifiedByUrl { get; set; } + + public TypeKind Kind { get; } + + public bool Publish { get; set; } + + public bool IsVirtual { get; } + + public Type ObjectType { get; } + + public string InternalName { get; } + + public SchemaItemPath Route { get; } + + public IAppliedDirectiveCollection AppliedDirectives { get; } + + public string Name { get; } + + public string Description { get; set; } + + public IScalarGraphType Clone(string newName) + { + throw new NotImplementedException(); + } + + public object Serialize(object item) + { + throw new NotImplementedException(); + } + + public string SerializeToQueryLanguage(object item) + { + throw new NotImplementedException(); + } + + public bool ValidateObject(object item) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/TypeExtensionFieldTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/TypeExtensionFieldTemplateTests.cs similarity index 80% rename from src/unit-tests/graphql-aspnet-tests/Internal/Templating/TypeExtensionFieldTemplateTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/TypeExtensionFieldTemplateTests.cs index 078877919..d056de4f4 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/TypeExtensionFieldTemplateTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/TypeExtensionFieldTemplateTests.cs @@ -7,35 +7,36 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates { using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - using GraphQL.AspNet.Tests.Framework.Interfaces; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Common.Interfaces; using GraphQL.AspNet.Tests.Internal.Templating.ExtensionMethodTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ExtensionMethodTestData; using NSubstitute; using NUnit.Framework; [TestFixture] public class TypeExtensionFieldTemplateTests { - private AspNet.Internal.TypeTemplates.GraphTypeExtensionFieldTemplate CreateExtensionTemplate(string actionName) + private GraphTypeExtensionFieldTemplate CreateExtensionTemplate(string actionName) where TControllerType : GraphController { var mockController = Substitute.For(); - mockController.InternalFullName.Returns(typeof(TControllerType).Name); + mockController.InternalName.Returns(typeof(TControllerType).Name); mockController.Route.Returns(new SchemaItemPath("path0")); mockController.Name.Returns("path0"); mockController.ObjectType.Returns(typeof(TControllerType)); var methodInfo = typeof(TControllerType).GetMethod(actionName); - var template = new AspNet.Internal.TypeTemplates.GraphTypeExtensionFieldTemplate(mockController, methodInfo); + var template = new GraphTypeExtensionFieldTemplate(mockController, methodInfo); template.Parse(); template.ValidateOrThrow(); @@ -53,9 +54,9 @@ public void ClassTypeExtension_PropertyCheck() Assert.AreEqual(typeof(ExtensionMethodController), template.Parent.ObjectType); Assert.AreEqual(typeof(TwoPropertyObject), template.SourceObjectType); Assert.AreEqual($"[type]/{nameof(TwoPropertyObject)}/Property3", template.Route.Path); - Assert.AreEqual($"{nameof(ExtensionMethodController)}.{nameof(ExtensionMethodController.ClassTypeExtension)}", template.InternalFullName); - Assert.AreEqual(methodInfo.ReflectedType, ((IGraphFieldResolverMethod)template).Parent.ObjectType); - Assert.AreEqual("path0", ((IGraphFieldResolverMethod)template).Parent.Name); + Assert.AreEqual($"{nameof(ExtensionMethodController)}.{nameof(ExtensionMethodController.ClassTypeExtension)}", template.InternalName); + Assert.AreEqual(methodInfo.ReflectedType, template.Parent.ObjectType); + Assert.AreEqual("path0", template.Parent.Name); Assert.AreEqual(methodInfo, template.Method); Assert.AreEqual(typeof(TwoPropertyObjectV2), template.ObjectType); Assert.AreEqual(2, template.Arguments.Count); @@ -65,8 +66,7 @@ public void ClassTypeExtension_PropertyCheck() Assert.AreEqual(FieldResolutionMode.PerSourceItem, template.Mode); // first arg should be declared for the source data - Assert.IsTrue(template.Arguments[0].ArgumentModifiers.HasFlag(GraphArgumentModifiers.ParentFieldResult)); - Assert.IsTrue(template.Arguments[0].ArgumentModifiers.HasFlag(GraphArgumentModifiers.Internal)); + Assert.IsTrue(template.Arguments[0].ArgumentModifier.HasFlag(GraphArgumentModifiers.ParentFieldResult)); } [Test] @@ -76,9 +76,9 @@ public void ClassBatchExtension_PropertyCheck() var template = this.CreateExtensionTemplate(nameof(ExtensionMethodController.ClassBatchExtension)); Assert.AreEqual("ClassBatchExtensionDescription", template.Description); - Assert.AreEqual(methodInfo.ReflectedType, ((IGraphFieldResolverMethod)template).Parent.ObjectType); + Assert.AreEqual(methodInfo.ReflectedType, template.Parent.ObjectType); Assert.AreEqual(typeof(TwoPropertyObject), template.SourceObjectType); - Assert.AreEqual("path0", ((IGraphFieldResolverMethod)template).Parent.Name); + Assert.AreEqual("path0", template.Parent.Name); Assert.AreEqual(methodInfo, template.Method); Assert.AreEqual(typeof(int), template.ObjectType); CollectionAssert.AreEqual(new[] { MetaGraphTypes.IsList, MetaGraphTypes.IsNotNull }, template.TypeExpression.Wrappers); @@ -86,8 +86,7 @@ public void ClassBatchExtension_PropertyCheck() Assert.AreEqual(FieldResolutionMode.Batch, template.Mode); // first arg should be declared for the source data - Assert.IsTrue(template.Arguments[0].ArgumentModifiers.HasFlag(GraphArgumentModifiers.ParentFieldResult)); - Assert.IsTrue(template.Arguments[0].ArgumentModifiers.HasFlag(GraphArgumentModifiers.Internal)); + Assert.IsTrue(template.Arguments[0].ArgumentModifier.HasFlag(GraphArgumentModifiers.ParentFieldResult)); } [Test] @@ -101,9 +100,9 @@ public void StructTypeExtension_PropertyCheck() Assert.AreEqual(typeof(ExtensionMethodController), template.Parent.ObjectType); Assert.AreEqual(typeof(TwoPropertyStruct), template.SourceObjectType); Assert.AreEqual($"[type]/{nameof(TwoPropertyStruct)}/Property3", template.Route.Path); - Assert.AreEqual($"{nameof(ExtensionMethodController)}.{nameof(ExtensionMethodController.StructTypeExtension)}", template.InternalFullName); - Assert.AreEqual(methodInfo.ReflectedType, ((IGraphFieldResolverMethod)template).Parent.ObjectType); - Assert.AreEqual("path0", ((IGraphFieldResolverMethod)template).Parent.Name); + Assert.AreEqual($"{nameof(ExtensionMethodController)}.{nameof(ExtensionMethodController.StructTypeExtension)}", template.InternalName); + Assert.AreEqual(methodInfo.ReflectedType, template.Parent.ObjectType); + Assert.AreEqual("path0", template.Parent.Name); Assert.AreEqual(methodInfo, template.Method); Assert.AreEqual(typeof(TwoPropertyObjectV2), template.ObjectType); Assert.AreEqual(2, template.Arguments.Count); @@ -113,8 +112,7 @@ public void StructTypeExtension_PropertyCheck() Assert.AreEqual(FieldResolutionMode.PerSourceItem, template.Mode); // first arg should be declared for the source data - Assert.IsTrue(template.Arguments[0].ArgumentModifiers.HasFlag(GraphArgumentModifiers.ParentFieldResult)); - Assert.IsTrue(template.Arguments[0].ArgumentModifiers.HasFlag(GraphArgumentModifiers.Internal)); + Assert.IsTrue(template.Arguments[0].ArgumentModifier.HasFlag(GraphArgumentModifiers.ParentFieldResult)); } [Test] @@ -124,9 +122,9 @@ public void StructBatchExtension_PropertyCheck() var template = this.CreateExtensionTemplate(nameof(ExtensionMethodController.StructBatchTestExtension)); Assert.AreEqual("StructBatchExtensionDescription", template.Description); - Assert.AreEqual(methodInfo.ReflectedType, ((IGraphFieldResolverMethod)template).Parent.ObjectType); + Assert.AreEqual(methodInfo.ReflectedType, template.Parent.ObjectType); Assert.AreEqual(typeof(TwoPropertyStruct), template.SourceObjectType); - Assert.AreEqual("path0", ((IGraphFieldResolverMethod)template).Parent.Name); + Assert.AreEqual("path0", template.Parent.Name); Assert.AreEqual(methodInfo, template.Method); Assert.AreEqual(typeof(int), template.ObjectType); CollectionAssert.AreEqual(new[] { MetaGraphTypes.IsList, MetaGraphTypes.IsNotNull }, template.TypeExpression.Wrappers); @@ -134,8 +132,7 @@ public void StructBatchExtension_PropertyCheck() Assert.AreEqual(FieldResolutionMode.Batch, template.Mode); // first arg should be declared for the source data - Assert.IsTrue(template.Arguments[0].ArgumentModifiers.HasFlag(GraphArgumentModifiers.ParentFieldResult)); - Assert.IsTrue(template.Arguments[0].ArgumentModifiers.HasFlag(GraphArgumentModifiers.Internal)); + Assert.IsTrue(template.Arguments[0].ArgumentModifier.HasFlag(GraphArgumentModifiers.ParentFieldResult)); } [Test] @@ -149,9 +146,9 @@ public void InterfaceTypeExtension_PropertyCheck() Assert.AreEqual(typeof(ExtensionMethodController), template.Parent.ObjectType); Assert.AreEqual(typeof(ISinglePropertyObject), template.SourceObjectType); Assert.AreEqual($"[type]/TwoPropertyInterface/Property3", template.Route.Path); - Assert.AreEqual($"{nameof(ExtensionMethodController)}.{nameof(ExtensionMethodController.InterfaceTypeExtension)}", template.InternalFullName); - Assert.AreEqual(methodInfo.ReflectedType, ((IGraphFieldResolverMethod)template).Parent.ObjectType); - Assert.AreEqual("path0", ((IGraphFieldResolverMethod)template).Parent.Name); + Assert.AreEqual($"{nameof(ExtensionMethodController)}.{nameof(ExtensionMethodController.InterfaceTypeExtension)}", template.InternalName); + Assert.AreEqual(methodInfo.ReflectedType, template.Parent.ObjectType); + Assert.AreEqual("path0", template.Parent.Name); Assert.AreEqual(methodInfo, template.Method); Assert.AreEqual(typeof(TwoPropertyObjectV2), template.ObjectType); Assert.AreEqual(2, template.Arguments.Count); @@ -161,8 +158,7 @@ public void InterfaceTypeExtension_PropertyCheck() Assert.AreEqual(FieldResolutionMode.PerSourceItem, template.Mode); // first arg should be declared for the source data - Assert.IsTrue(template.Arguments[0].ArgumentModifiers.HasFlag(GraphArgumentModifiers.ParentFieldResult)); - Assert.IsTrue(template.Arguments[0].ArgumentModifiers.HasFlag(GraphArgumentModifiers.Internal)); + Assert.IsTrue(template.Arguments[0].ArgumentModifier.HasFlag(GraphArgumentModifiers.ParentFieldResult)); } [Test] @@ -172,9 +168,9 @@ public void InterfaceBatchExtension_PropertyCheck() var template = this.CreateExtensionTemplate(nameof(ExtensionMethodController.InterfaceBatchTestExtension)); Assert.AreEqual("InterfaceBatchExtensionDescription", template.Description); - Assert.AreEqual(methodInfo.ReflectedType, ((IGraphFieldResolverMethod)template).Parent.ObjectType); + Assert.AreEqual(methodInfo.ReflectedType, template.Parent.ObjectType); Assert.AreEqual(typeof(ISinglePropertyObject), template.SourceObjectType); - Assert.AreEqual("path0", ((IGraphFieldResolverMethod)template).Parent.Name); + Assert.AreEqual("path0", template.Parent.Name); Assert.AreEqual(methodInfo, template.Method); Assert.AreEqual(typeof(int), template.ObjectType); CollectionAssert.AreEqual(new[] { MetaGraphTypes.IsList, MetaGraphTypes.IsNotNull }, template.TypeExpression.Wrappers); @@ -182,8 +178,7 @@ public void InterfaceBatchExtension_PropertyCheck() Assert.AreEqual(FieldResolutionMode.Batch, template.Mode); // first arg should be declared for the source data - Assert.IsTrue(template.Arguments[0].ArgumentModifiers.HasFlag(GraphArgumentModifiers.ParentFieldResult)); - Assert.IsTrue(template.Arguments[0].ArgumentModifiers.HasFlag(GraphArgumentModifiers.Internal)); + Assert.IsTrue(template.Arguments[0].ArgumentModifier.HasFlag(GraphArgumentModifiers.ParentFieldResult)); } [Test] @@ -192,7 +187,7 @@ public void ValidBatchExtension_WithCustomReturnType_AndNoDeclaredTypeOnAttribut var methodInfo = typeof(ExtensionMethodController).GetMethod(nameof(ExtensionMethodController.CustomValidReturnType)); var template = this.CreateExtensionTemplate(nameof(ExtensionMethodController.CustomValidReturnType)); - Assert.AreEqual(methodInfo.ReflectedType, ((IGraphFieldResolverMethod)template).Parent.ObjectType); + Assert.AreEqual(methodInfo.ReflectedType, template.Parent.ObjectType); Assert.AreEqual(typeof(TwoPropertyObject), template.SourceObjectType); Assert.AreEqual(methodInfo, template.Method); CollectionAssert.AreEqual(new[] { MetaGraphTypes.IsList, MetaGraphTypes.IsNotNull }, template.TypeExpression.Wrappers); @@ -207,10 +202,10 @@ public void ValidBatchExtension_WithCustomNamedReturnType_PropertyCheck() var methodInfo = typeof(ExtensionMethodController).GetMethod(nameof(ExtensionMethodController.Batch_CustomNamedObjectReturnedTestExtension)); var template = this.CreateExtensionTemplate(nameof(ExtensionMethodController.Batch_CustomNamedObjectReturnedTestExtension)); - Assert.AreEqual(methodInfo.ReflectedType, ((IGraphFieldResolverMethod)template).Parent.ObjectType); + Assert.AreEqual(methodInfo.ReflectedType, template.CreateResolver().MetaData.ParentObjectType); Assert.AreEqual(typeof(TwoPropertyObject), template.SourceObjectType); Assert.AreEqual(methodInfo, template.Method); - Assert.AreEqual("Custom_Named_Object", template.TypeExpression.ToString()); + Assert.AreEqual("Type", template.TypeExpression.ToString()); Assert.AreEqual("[type]/TwoPropertyObject/fieldThree", template.Route.ToString()); Assert.AreEqual(typeof(CustomNamedObject), template.ObjectType); Assert.AreEqual(1, template.Arguments.Count); @@ -223,10 +218,10 @@ public void ValidBatchExtension_WithCustomNamedReturnType_OnSameCustomNamedParen var methodInfo = typeof(ExtensionMethodController).GetMethod(nameof(ExtensionMethodController.Batch_ChildIsSameCustomNamedObjectTestExtension)); var template = this.CreateExtensionTemplate(nameof(ExtensionMethodController.Batch_ChildIsSameCustomNamedObjectTestExtension)); - Assert.AreEqual(methodInfo.ReflectedType, ((IGraphFieldResolverMethod)template).Parent.ObjectType); + Assert.AreEqual(methodInfo.ReflectedType, template.CreateResolver().MetaData.ParentObjectType); Assert.AreEqual(typeof(CustomNamedObject), template.SourceObjectType); Assert.AreEqual(methodInfo, template.Method); - Assert.AreEqual("Custom_Named_Object", template.TypeExpression.ToString()); + Assert.AreEqual("Type", template.TypeExpression.ToString()); Assert.AreEqual("[type]/Custom_Named_Object/fieldThree", template.Route.ToString()); Assert.AreEqual(typeof(CustomNamedObject), template.ObjectType); Assert.AreEqual(1, template.Arguments.Count); @@ -286,5 +281,14 @@ public void BatchExtension_ExtendingAnEnum_ThrowsException() this.CreateExtensionTemplate(nameof(ExtensionMethodController.EnumBatchExtensionFails)); }); } + + [Test] + public void ValidBatchExtension_WithCustomInternalName_PropertyCheck() + { + var methodInfo = typeof(ExtensionMethodController).GetMethod(nameof(ExtensionMethodController.CustomeInternalName)); + var template = this.CreateExtensionTemplate(nameof(ExtensionMethodController.CustomeInternalName)); + + Assert.AreEqual("BatchInternalName", template.InternalName); + } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/UnionGraphTypeTemplateTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/UnionGraphTypeTemplateTests.cs new file mode 100644 index 000000000..423be86a8 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/UnionGraphTypeTemplateTests.cs @@ -0,0 +1,60 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates +{ + using System; + using System.Security.Cryptography; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.UnionTestData; + using NUnit.Framework; + + [TestFixture] + public class UnionGraphTypeTemplateTests + { + [Test] + public void NotAProxy_ThrowsException() + { + var instance = new UnionGraphTypeTemplate(typeof(TwoPropertyObject)); + instance.Parse(); + + Assert.Throws(() => + { + instance.ValidateOrThrow(); + }); + } + + [Test] + public void ValidateProxy_ParsesCorrectly() + { + var instance = new UnionGraphTypeTemplate(typeof(UnionWithInternalName)); + + instance.Parse(); + instance.ValidateOrThrow(); + + Assert.AreEqual("My Union Internal Name", instance.InternalName); + } + + [Test] + public void ValidateProxy_NoInternalName_FallsBackToProxyName() + { + var instance = new UnionGraphTypeTemplate(typeof(UnionWithNoInternalName)); + + instance.Parse(); + instance.ValidateOrThrow(); + + Assert.AreEqual(nameof(UnionWithNoInternalName), instance.InternalName); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/UnionTestData/UnionWithInternalName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/UnionTestData/UnionWithInternalName.cs new file mode 100644 index 000000000..13ce1f0ee --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/UnionTestData/UnionWithInternalName.cs @@ -0,0 +1,26 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.UnionTestData +{ + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class UnionWithInternalName : GraphUnionProxy + { + public UnionWithInternalName() + { + this.Name = "ValidUnion"; + this.Description = "My Union Desc"; + this.InternalName = "My Union Internal Name"; + this.Types.Add(typeof(TwoPropertyObject)); + this.Types.Add(typeof(TwoPropertyObjectV2)); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/UnionTestData/UnionWithNoInternalName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/UnionTestData/UnionWithNoInternalName.cs new file mode 100644 index 000000000..bcc8e17fa --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/UnionTestData/UnionWithNoInternalName.cs @@ -0,0 +1,26 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.UnionTestData +{ + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class UnionWithNoInternalName : GraphUnionProxy + { + public UnionWithNoInternalName() + { + this.Name = "ValidUnion"; + this.Description = "My Union Desc"; + this.InternalName = null; + this.Types.Add(typeof(TwoPropertyObject)); + this.Types.Add(typeof(TwoPropertyObjectV2)); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/UnionTestData/ValidTestUnion.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/UnionTestData/ValidTestUnion.cs new file mode 100644 index 000000000..007760433 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/UnionTestData/ValidTestUnion.cs @@ -0,0 +1,25 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.UnionTestData +{ + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class ValidTestUnion : GraphUnionProxy + { + public ValidTestUnion() + { + this.Name = "ValidUnion"; + this.Description = "My Union Desc"; + this.Types.Add(typeof(TwoPropertyObject)); + this.Types.Add(typeof(TwoPropertyObjectV2)); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypeTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypeTests.cs new file mode 100644 index 000000000..9d1b600ec --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypeTests.cs @@ -0,0 +1,258 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas +{ + using System; + using System.Linq; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData; + using Microsoft.AspNetCore.Mvc.ApplicationParts; + using NUnit.Framework; + + public class GlobalTypeTests + { + [Test] + public void AllInternalScalarsExistInGlobalCollection() + { + // sanity check to ensure wireups are correct + var types = typeof(IntScalarType).Assembly + .GetTypes() + .Where(x => Validation.IsCastable(x) + && !x.IsAbstract + && x.FullName.StartsWith("GraphQL.AspNet.Schemas.TypeSystem.Scalars")); + + foreach (var type in types) + { + Assert.IsTrue(GlobalTypes.IsBuiltInScalar(type), $"{type.Name} is not declared as built in but should be"); + } + } + + [TestCase("Int", true)] + [TestCase("int", true)] // test for case-insensitiveness + [TestCase("INT", true)] + [TestCase("Float", true)] + [TestCase("Boolean", true)] + [TestCase("String", true)] + [TestCase("Id", true)] + [TestCase("Long", true)] + [TestCase("UInt", true)] + [TestCase("ULong", true)] + [TestCase("Double", true)] + [TestCase("Decimal", true)] + [TestCase("DateTimeOffset", true)] + [TestCase("DateTime", true)] + [TestCase("DateOnly", true)] + [TestCase("TimeOnly", true)] + [TestCase("Byte", true)] + [TestCase("SignedByte", true)] + [TestCase("Short", true)] + [TestCase("UShort", true)] + [TestCase("Guid", true)] + [TestCase("Uri", true)] + + [TestCase("NotAScalar", false)] + [TestCase(null, false)] + public static void BuiltInScalarNames(string name, bool isBuiltIn) + { + Assert.AreEqual(isBuiltIn, GlobalTypes.IsBuiltInScalar(name)); + } + + [TestCase("Int", false)] + [TestCase("Float", false)] + [TestCase("Boolean", false)] + [TestCase("String", false)] + [TestCase("Id", false)] + [TestCase("Long", true)] + [TestCase("UInt", true)] + [TestCase("ULong", true)] + [TestCase("Double", true)] + [TestCase("Decimal", true)] + [TestCase("DateTimeOffset", true)] + [TestCase("DateTime", true)] + [TestCase("DateOnly", true)] + [TestCase("TimeOnly", true)] + [TestCase("Byte", true)] + [TestCase("SignedByte", true)] + [TestCase("Short", true)] + [TestCase("UShort", true)] + [TestCase("Guid", true)] + [TestCase("Uri", true)] + [TestCase(null, true)] + public static void CanBeRenamed(string name, bool canBeRenamed) + { + Assert.AreEqual(canBeRenamed, GlobalTypes.CanBeRenamed(name)); + } + + [TestCase(typeof(int), typeof(IntScalarType))] + [TestCase(typeof(TwoPropertyObject), null)] + [TestCase(null, null)] + public static void FindBuiltInScalarType(Type typeToTest, Type expectedOutput) + { + var result = GlobalTypes.FindBuiltInScalarType(typeToTest); + Assert.AreEqual(expectedOutput, result); + } + + [TestCase(typeof(IntScalarType), false)] + [TestCase(null, true)] // no type provided + [TestCase(typeof(NoParameterlessConstructorScalar), true)] + [TestCase(typeof(TwoPropertyObject), true)] // does not implement iScalarGraphType + [TestCase(typeof(InvalidGraphTypeNameScalar), true)] + [TestCase(typeof(NoNameOnScalar), true)] + [TestCase(typeof(NoObjectTypeScalar), true)] + [TestCase(typeof(NotScalarKindScalar), true)] + [TestCase(typeof(ObjectTypeIsNullableScalar), true)] + [TestCase(typeof(NoSourceResolverScalar), true)] + [TestCase(typeof(NoValueTypeScalar), true)] + [TestCase(typeof(NullAppliedDirectivesScalar), true)] + [TestCase(typeof(InvalidParentAppliedDirectivesScalar), true)] + public static void ValidateScalarorThrow(Type typeToTest, bool shouldThrow) + { + try + { + GlobalTypes.ValidateScalarTypeOrThrow(typeToTest); + } + catch (GraphTypeDeclarationException) + { + if (shouldThrow) + return; + + Assert.Fail("Threw when it shouldnt"); + } + catch (Exception) + { + Assert.Fail("Threw invalid exception type"); + } + + if (!shouldThrow) + return; + + Assert.Fail("Didn't throw when it should"); + } + + [TestCase(typeof(IntScalarType), true)] + [TestCase(null, false)] // no type provided + [TestCase(typeof(NoParameterlessConstructorScalar), false)] + [TestCase(typeof(TwoPropertyObject), false)] // does not implement iScalarGraphType + [TestCase(typeof(InvalidGraphTypeNameScalar), false)] + [TestCase(typeof(NoNameOnScalar), false)] + [TestCase(typeof(NoObjectTypeScalar), false)] + [TestCase(typeof(NotScalarKindScalar), false)] + [TestCase(typeof(ObjectTypeIsNullableScalar), false)] + [TestCase(typeof(NoSourceResolverScalar), false)] + [TestCase(typeof(NoValueTypeScalar), false)] + [TestCase(typeof(NullAppliedDirectivesScalar), false)] + [TestCase(typeof(InvalidParentAppliedDirectivesScalar), false)] + public static void IsValidScalar(Type typeToTest, bool isValid) + { + var result = GlobalTypes.IsValidScalarType(typeToTest); + Assert.AreEqual(isValid, result); + } + + [Test] + public static void ValdidateScalarType_ByScalarInstance_ValidScalar() + { + // shoudl not throw + var instance = new IntScalarType(); + GlobalTypes.ValidateScalarTypeOrThrow(instance); + } + + [Test] + public static void ValdidateScalarType_ByScalarInstance_InvalidScalar() + { + // shoudl not throw + var instance = new NoNameOnScalar(); + Assert.Throws(() => + { + GlobalTypes.ValidateScalarTypeOrThrow(instance); + }); + } + + [TestCase(null, true)] + [TestCase(typeof(TwoPropertyObject), true)] // not a union proxy + [TestCase(typeof(NoParameterelessConstructorProxy), true)] + [TestCase(typeof(NoNameUnionProxy), true)] + [TestCase(typeof(InvalidNameUnionProxy), true)] + [TestCase(typeof(NoUnionMembersProxy), true)] + [TestCase(typeof(NullTypesUnionProxy), true)] + [TestCase(typeof(ValidUnionProxy), false)] + public void ValidateUnionProxyOrThrow(Type typeToTest, bool shouldThrow) + { + try + { + GlobalTypes.ValidateUnionProxyOrThrow(typeToTest); + } + catch (GraphTypeDeclarationException) + { + if (shouldThrow) + return; + + Assert.Fail("Threw when it shouldnt"); + } + catch (Exception) + { + Assert.Fail("Threw invalid exception type"); + } + + if (!shouldThrow) + return; + + Assert.Fail("Didn't throw when it should"); + } + + [TestCase(null, false)] + [TestCase(typeof(TwoPropertyObject), false)] // not a union proxy + [TestCase(typeof(NoParameterelessConstructorProxy), false)] + [TestCase(typeof(NoNameUnionProxy), false)] + [TestCase(typeof(InvalidNameUnionProxy), false)] + [TestCase(typeof(NoUnionMembersProxy), false)] + [TestCase(typeof(NullTypesUnionProxy), false)] + [TestCase(typeof(ValidUnionProxy), true)] + public void IsValidUnionProxyType(Type typeToTest, bool isValid) + { + var result = GlobalTypes.IsValidUnionProxyType(typeToTest); + Assert.AreEqual(isValid, result); + } + + [Test] + public void ValidateUnionProxyOrThrow_ByInstance_ValidProxy() + { + // should not throw + GlobalTypes.ValidateUnionProxyOrThrow(new ValidUnionProxy()); + } + + [Test] + public void ValidateUnionProxyOrThrow_ByInstance_InvalidProxy() + { + // should not throw + Assert.Throws(() => + { + GlobalTypes.ValidateUnionProxyOrThrow(new NoNameUnionProxy()); + }); + } + + [TestCase(typeof(ValidUnionProxy), true)] + [TestCase(null, false)] + [TestCase(typeof(NoNameUnionProxy), false)] + public void CreateUnionTypeFromProxy(Type proxyType, bool shouldReturnValue) + { + var reslt = GlobalTypes.CreateUnionProxyFromType(proxyType); + + if (shouldReturnValue) + Assert.IsNotNull(reslt); + else + Assert.IsNull(reslt); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/InvalidGraphTypeNameScalar.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/InvalidGraphTypeNameScalar.cs new file mode 100644 index 000000000..d26907195 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/InvalidGraphTypeNameScalar.cs @@ -0,0 +1,32 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData +{ + using System; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class InvalidGraphTypeNameScalar : ScalarGraphTypeBase + { + public InvalidGraphTypeNameScalar() + : base("name", typeof(TwoPropertyObject)) + { + this.Name = "Not#$aName"; + this.ValueType = ScalarValueType.String; + } + + public override ScalarValueType ValueType { get; } + + public override object Resolve(ReadOnlySpan data) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/InvalidNameUnionProxy.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/InvalidNameUnionProxy.cs new file mode 100644 index 000000000..bcba875db --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/InvalidNameUnionProxy.cs @@ -0,0 +1,24 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData +{ + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class InvalidNameUnionProxy : GraphUnionProxy + { + public InvalidNameUnionProxy() + { + this.Name = "NotaGraphName#*$(@#$"; + this.Types.Add(typeof(TwoPropertyObject)); + this.Types.Add(typeof(TwoPropertyObjectV2)); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/InvalidParentAppliedDirectivesScalar.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/InvalidParentAppliedDirectivesScalar.cs new file mode 100644 index 000000000..1beeecdf0 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/InvalidParentAppliedDirectivesScalar.cs @@ -0,0 +1,81 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData +{ + using System; + using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using NSubstitute; + + public class InvalidParentAppliedDirectivesScalar : IScalarGraphType + { + public InvalidParentAppliedDirectivesScalar() + { + this.Name = "ValidName"; + this.ValueType = ScalarValueType.String; + this.SourceResolver = Substitute.For(); + this.ObjectType = typeof(TwoPropertyStruct); + this.AppliedDirectives = new AppliedDirectiveCollection(Substitute.For()); + } + + public ScalarValueType ValueType { get; } + + public ILeafValueResolver SourceResolver { get; set; } + + public string SpecifiedByUrl { get; set; } + + public TypeKind Kind => TypeKind.SCALAR; + + public bool Publish { get; set; } + + public bool IsVirtual { get; } + + public Type ObjectType { get; } + + public string InternalName { get; } + + public SchemaItemPath Route { get; } + + public IAppliedDirectiveCollection AppliedDirectives { get; } + + public string Name { get; } + + public string Description { get; set; } + + public IScalarGraphType Clone(string newName) + { + throw new NotImplementedException(); + } + + public object Resolve(ReadOnlySpan data) + { + throw new NotImplementedException(); + } + + public object Serialize(object item) + { + throw new NotImplementedException(); + } + + public string SerializeToQueryLanguage(object item) + { + throw new NotImplementedException(); + } + + public bool ValidateObject(object item) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoNameOnScalar.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoNameOnScalar.cs new file mode 100644 index 000000000..aaba6db30 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoNameOnScalar.cs @@ -0,0 +1,32 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData +{ + using System; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class NoNameOnScalar : ScalarGraphTypeBase + { + public NoNameOnScalar() + : base("something", typeof(TwoPropertyObject)) + { + this.Name = null; + this.ValueType = ScalarValueType.String; + } + + public override ScalarValueType ValueType { get; } + + public override object Resolve(ReadOnlySpan data) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoNameUnionProxy.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoNameUnionProxy.cs new file mode 100644 index 000000000..7d3b5c161 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoNameUnionProxy.cs @@ -0,0 +1,25 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData +{ + using Castle.Components.DictionaryAdapter; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class NoNameUnionProxy : GraphUnionProxy + { + public NoNameUnionProxy() + { + this.Name = null; + this.Types.Add(typeof(TwoPropertyObject)); + this.Types.Add(typeof(TwoPropertyObjectV2)); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoObjectTypeScalar.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoObjectTypeScalar.cs new file mode 100644 index 000000000..79b76a00a --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoObjectTypeScalar.cs @@ -0,0 +1,80 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData +{ + using System; + using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using NSubstitute; + + public class NoObjectTypeScalar : IScalarGraphType + { + public NoObjectTypeScalar() + { + this.Name = "ValidName"; + this.ValueType = ScalarValueType.String; + this.SourceResolver = Substitute.For(); + this.ObjectType = null; + this.AppliedDirectives = new AppliedDirectiveCollection(this); + } + + public ScalarValueType ValueType { get; } + + public ILeafValueResolver SourceResolver { get; set; } + + public string SpecifiedByUrl { get; set; } + + public TypeKind Kind => TypeKind.SCALAR; + + public bool Publish { get; set; } + + public bool IsVirtual { get; } + + public Type ObjectType { get; } + + public string InternalName { get; } + + public SchemaItemPath Route { get; } + + public IAppliedDirectiveCollection AppliedDirectives { get; } + + public string Name { get; } + + public string Description { get; set; } + + public IScalarGraphType Clone(string newName) + { + throw new NotImplementedException(); + } + + public object Resolve(ReadOnlySpan data) + { + throw new NotImplementedException(); + } + + public object Serialize(object item) + { + throw new NotImplementedException(); + } + + public string SerializeToQueryLanguage(object item) + { + throw new NotImplementedException(); + } + + public bool ValidateObject(object item) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoParameterelessConstructorProxy.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoParameterelessConstructorProxy.cs new file mode 100644 index 000000000..410a4e599 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoParameterelessConstructorProxy.cs @@ -0,0 +1,24 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData +{ + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class NoParameterelessConstructorProxy : GraphUnionProxy + { + public NoParameterelessConstructorProxy(string name) + { + this.Name = name; + this.Types.Add(typeof(TwoPropertyObject)); + this.Types.Add(typeof(TwoPropertyObjectV2)); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoParameterlessConstructorScalar.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoParameterlessConstructorScalar.cs new file mode 100644 index 000000000..abd6423b4 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoParameterlessConstructorScalar.cs @@ -0,0 +1,31 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData +{ + using System; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class NoParameterlessConstructorScalar : ScalarGraphTypeBase + { + public NoParameterlessConstructorScalar(string name) + : base(name, typeof(TwoPropertyObject)) + { + this.ValueType = ScalarValueType.Boolean; + } + + public override ScalarValueType ValueType { get; } + + public override object Resolve(ReadOnlySpan data) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoSourceResolverScalar.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoSourceResolverScalar.cs new file mode 100644 index 000000000..ab828f427 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoSourceResolverScalar.cs @@ -0,0 +1,81 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData +{ + using System; + using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class NoSourceResolverScalar : IScalarGraphType + { + public NoSourceResolverScalar() + { + this.Name = "ValidName"; + this.ValueType = ScalarValueType.String; + this.Kind = TypeKind.SCALAR; + this.SourceResolver = null; + this.ObjectType = typeof(TwoPropertyStruct); + this.AppliedDirectives = new AppliedDirectiveCollection(this); + } + + public ScalarValueType ValueType { get; } + + public ILeafValueResolver SourceResolver { get; set; } + + public string SpecifiedByUrl { get; set; } + + public TypeKind Kind { get; } + + public bool Publish { get; set; } + + public bool IsVirtual { get; } + + public Type ObjectType { get; } + + public string InternalName { get; } + + public SchemaItemPath Route { get; } + + public IAppliedDirectiveCollection AppliedDirectives { get; } + + public string Name { get; } + + public string Description { get; set; } + + public IScalarGraphType Clone(string newName) + { + throw new NotImplementedException(); + } + + public object Resolve(ReadOnlySpan data) + { + throw new NotImplementedException(); + } + + public object Serialize(object item) + { + throw new NotImplementedException(); + } + + public string SerializeToQueryLanguage(object item) + { + throw new NotImplementedException(); + } + + public bool ValidateObject(object item) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoUnionMembersProxy.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoUnionMembersProxy.cs new file mode 100644 index 000000000..0cae246a4 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoUnionMembersProxy.cs @@ -0,0 +1,22 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData +{ + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class NoUnionMembersProxy : GraphUnionProxy + { + public NoUnionMembersProxy() + { + this.Name = "Name"; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoValueTypeScalar.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoValueTypeScalar.cs new file mode 100644 index 000000000..661185242 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NoValueTypeScalar.cs @@ -0,0 +1,81 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData +{ + using System; + using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using NSubstitute; + + public class NoValueTypeScalar : IScalarGraphType + { + public NoValueTypeScalar() + { + this.Name = "ValidName"; + this.ValueType = ScalarValueType.Unknown; + this.SourceResolver = Substitute.For(); + this.ObjectType = typeof(TwoPropertyStruct); + this.AppliedDirectives = new AppliedDirectiveCollection(this); + } + + public ScalarValueType ValueType { get; } + + public ILeafValueResolver SourceResolver { get; set; } + + public string SpecifiedByUrl { get; set; } + + public TypeKind Kind => TypeKind.SCALAR; + + public bool Publish { get; set; } + + public bool IsVirtual { get; } + + public Type ObjectType { get; } + + public string InternalName { get; } + + public SchemaItemPath Route { get; } + + public IAppliedDirectiveCollection AppliedDirectives { get; } + + public string Name { get; } + + public string Description { get; set; } + + public IScalarGraphType Clone(string newName) + { + throw new NotImplementedException(); + } + + public object Resolve(ReadOnlySpan data) + { + throw new NotImplementedException(); + } + + public object Serialize(object item) + { + throw new NotImplementedException(); + } + + public string SerializeToQueryLanguage(object item) + { + throw new NotImplementedException(); + } + + public bool ValidateObject(object item) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NotScalarKindScalar.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NotScalarKindScalar.cs new file mode 100644 index 000000000..2a693c7af --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NotScalarKindScalar.cs @@ -0,0 +1,81 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData +{ + using System; + using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using NSubstitute; + + public class NotScalarKindScalar : IScalarGraphType + { + public NotScalarKindScalar() + { + this.Name = "ValidName"; + this.ValueType = ScalarValueType.String; + this.SourceResolver = Substitute.For(); + this.ObjectType = typeof(TwoPropertyObject); + this.AppliedDirectives = new AppliedDirectiveCollection(this); + } + + public ScalarValueType ValueType { get; } + + public ILeafValueResolver SourceResolver { get; set; } + + public string SpecifiedByUrl { get; set; } + + public TypeKind Kind => TypeKind.OBJECT; + + public bool Publish { get; set; } + + public bool IsVirtual { get; } + + public Type ObjectType { get; } + + public string InternalName { get; } + + public SchemaItemPath Route { get; } + + public IAppliedDirectiveCollection AppliedDirectives { get; } + + public string Name { get; } + + public string Description { get; set; } + + public IScalarGraphType Clone(string newName) + { + throw new NotImplementedException(); + } + + public object Resolve(ReadOnlySpan data) + { + throw new NotImplementedException(); + } + + public object Serialize(object item) + { + throw new NotImplementedException(); + } + + public string SerializeToQueryLanguage(object item) + { + throw new NotImplementedException(); + } + + public bool ValidateObject(object item) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NullAppliedDirectivesScalar.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NullAppliedDirectivesScalar.cs new file mode 100644 index 000000000..994cc6cbf --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NullAppliedDirectivesScalar.cs @@ -0,0 +1,81 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData +{ + using System; + using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using NSubstitute; + + public class NullAppliedDirectivesScalar : IScalarGraphType + { + public NullAppliedDirectivesScalar() + { + this.Name = "ValidName"; + this.ValueType = ScalarValueType.String; + this.SourceResolver = Substitute.For(); + this.ObjectType = typeof(TwoPropertyStruct); + this.AppliedDirectives = null; + } + + public ScalarValueType ValueType { get; } + + public ILeafValueResolver SourceResolver { get; set; } + + public string SpecifiedByUrl { get; set; } + + public TypeKind Kind => TypeKind.SCALAR; + + public bool Publish { get; set; } + + public bool IsVirtual { get; } + + public Type ObjectType { get; } + + public string InternalName { get; } + + public SchemaItemPath Route { get; } + + public IAppliedDirectiveCollection AppliedDirectives { get; } + + public string Name { get; } + + public string Description { get; set; } + + public IScalarGraphType Clone(string newName) + { + throw new NotImplementedException(); + } + + public object Resolve(ReadOnlySpan data) + { + throw new NotImplementedException(); + } + + public object Serialize(object item) + { + throw new NotImplementedException(); + } + + public string SerializeToQueryLanguage(object item) + { + throw new NotImplementedException(); + } + + public bool ValidateObject(object item) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NullTypesUnionProxy.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NullTypesUnionProxy.cs new file mode 100644 index 000000000..5e5439be8 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/NullTypesUnionProxy.cs @@ -0,0 +1,40 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData +{ + using System; + using System.Collections.Generic; + using GraphQL.AspNet.Interfaces.Schema; + + public class NullTypesUnionProxy : IGraphUnionProxy + { + public NullTypesUnionProxy() + { + this.Name = "Name"; + this.InternalName = "NulLTypesUnionProxy"; + this.Types = null; + } + + public HashSet Types { get; } + + public bool Publish { get; set; } + + public string Name { get; } + + public string Description { get; set; } + + public string InternalName { get; } + + public Type MapType(Type runtimeObjectType) + { + return null; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/ObjectTypeIsNullableScalar.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/ObjectTypeIsNullableScalar.cs new file mode 100644 index 000000000..271cfa8a5 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/ObjectTypeIsNullableScalar.cs @@ -0,0 +1,81 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData +{ + using System; + using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using NSubstitute; + + public class ObjectTypeIsNullableScalar : IScalarGraphType + { + public ObjectTypeIsNullableScalar() + { + this.Name = "ValidName"; + this.ValueType = ScalarValueType.String; + this.SourceResolver = Substitute.For(); + this.ObjectType = typeof(TwoPropertyStruct?); + this.AppliedDirectives = new AppliedDirectiveCollection(this); + } + + public ScalarValueType ValueType { get; } + + public ILeafValueResolver SourceResolver { get; set; } + + public string SpecifiedByUrl { get; set; } + + public TypeKind Kind => TypeKind.SCALAR; + + public bool Publish { get; set; } + + public bool IsVirtual { get; } + + public Type ObjectType { get; } + + public string InternalName { get; } + + public SchemaItemPath Route { get; } + + public IAppliedDirectiveCollection AppliedDirectives { get; } + + public string Name { get; } + + public string Description { get; set; } + + public IScalarGraphType Clone(string newName) + { + throw new NotImplementedException(); + } + + public object Resolve(ReadOnlySpan data) + { + throw new NotImplementedException(); + } + + public object Serialize(object item) + { + throw new NotImplementedException(); + } + + public string SerializeToQueryLanguage(object item) + { + throw new NotImplementedException(); + } + + public bool ValidateObject(object item) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/ValidUnionProxy.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/ValidUnionProxy.cs new file mode 100644 index 000000000..75f702cfb --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GlobalTypesTestData/ValidUnionProxy.cs @@ -0,0 +1,24 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.GlobalTypesTestData +{ + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + + public class ValidUnionProxy : GraphUnionProxy + { + public ValidUnionProxy() + { + this.Name = "name"; + this.Types.Add(typeof(TwoPropertyObject)); + this.Types.Add(typeof(TwoPropertyObjectV2)); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GraphFieldArgumentCloningTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphFieldArgumentCloningTests.cs index 9c827cbed..890ac416b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/GraphFieldArgumentCloningTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphFieldArgumentCloningTests.cs @@ -32,11 +32,10 @@ public void ClonedArgument_PropertyCheck() var arg = new GraphFieldArgument( parentField, "argName", + "internalName", + "paramName", GraphTypeExpression.FromDeclaration("String"), new SchemaItemPath("[type]/GraphType1/Field1/Arg1"), - GraphArgumentModifiers.Internal, - "paramName", - "internalName", typeof(string), true, "default value", @@ -54,7 +53,6 @@ public void ClonedArgument_PropertyCheck() Assert.AreEqual(arg.DefaultValue, clonedArg.DefaultValue); Assert.AreEqual(arg.ObjectType, clonedArg.ObjectType); Assert.AreEqual(arg.InternalName, clonedArg.InternalName); - Assert.AreEqual(arg.ArgumentModifiers, clonedArg.ArgumentModifiers); Assert.AreEqual(arg.TypeExpression, clonedArg.TypeExpression); Assert.AreEqual(arg.ParameterName, clonedArg.ParameterName); Assert.AreEqual(arg.AppliedDirectives.Count, arg.AppliedDirectives.Count); diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GraphFieldCloningTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphFieldCloningTests.cs index 6648c69ef..268940b76 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/GraphFieldCloningTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphFieldCloningTests.cs @@ -11,14 +11,16 @@ namespace GraphQL.AspNet.Tests.Schemas { using System.Collections.Generic; using System.Linq; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Execution.Parsing.NodeBuilders; using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.TypeTemplates; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Security; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using Microsoft.AspNetCore.Authorization; using NSubstitute; using NUnit.Framework; @@ -43,10 +45,11 @@ public void MethodField_PropertyCheck() var field = new MethodGraphField( "field1", + "internalFieldName", GraphTypeExpression.FromDeclaration("[Int]"), new SchemaItemPath("[type]/JohnType/field1"), - typeof(TwoPropertyObject), typeof(List), + typeof(TwoPropertyObject), AspNet.Execution.FieldResolutionMode.PerSourceItem, resolver, polices, @@ -57,11 +60,10 @@ public void MethodField_PropertyCheck() field.Arguments.AddArgument(new GraphFieldArgument( field, "arg1", - GraphTypeExpression.FromDeclaration("String"), - field.Route.CreateChild("arg1"), - GraphArgumentModifiers.None, "arg1", "arg1", + GraphTypeExpression.FromDeclaration("String"), + field.Route.CreateChild("arg1"), typeof(string), false)); @@ -84,94 +86,11 @@ public void MethodField_PropertyCheck() Assert.AreEqual(field.Publish, clonedField.Publish); Assert.AreEqual("[type]/BobType/field1", clonedField.Route.Path); Assert.AreEqual(field.Mode, clonedField.Mode); - Assert.AreEqual(field.IsLeaf, clonedField.IsLeaf); Assert.AreEqual(field.IsDeprecated, clonedField.IsDeprecated); Assert.AreEqual(field.DeprecationReason, clonedField.DeprecationReason); Assert.AreEqual(field.Complexity, clonedField.Complexity); Assert.AreEqual(field.FieldSource, clonedField.FieldSource); - - Assert.IsFalse(object.ReferenceEquals(field.TypeExpression, clonedField.TypeExpression)); - Assert.IsTrue(object.ReferenceEquals(field.Resolver, clonedField.Resolver)); - Assert.IsFalse(object.ReferenceEquals(field.AppliedDirectives, clonedField.AppliedDirectives)); - Assert.IsFalse(object.ReferenceEquals(field.SecurityGroups, clonedField.SecurityGroups)); - Assert.IsFalse(object.ReferenceEquals(field.Arguments, clonedField.Arguments)); - - Assert.AreEqual(field.AppliedDirectives.Count, clonedField.AppliedDirectives.Count); - Assert.AreEqual(field.SecurityGroups.Count(), clonedField.SecurityGroups.Count()); - Assert.AreEqual(field.Arguments.Count, clonedField.Arguments.Count); - - foreach (var arg in field.Arguments) - { - var foundArg = clonedField.Arguments.FindArgument(arg.Name); - Assert.IsNotNull(foundArg); - } - } - - [Test] - public void PropertyField_PropertyCheck() - { - var originalParent = Substitute.For(); - originalParent.Route.Returns(new SchemaItemPath("[type]/JohnType")); - originalParent.Name.Returns("JohnType"); - - var resolver = Substitute.For(); - var polices = new List(); - polices.Add(AppliedSecurityPolicyGroup.FromAttributeCollection(typeof(GraphFieldCloningTests))); - - var appliedDirectives = new AppliedDirectiveCollection(); - appliedDirectives.Add(new AppliedDirective("someDirective", 3)); - - var field = new PropertyGraphField( - "field1", - GraphTypeExpression.FromDeclaration("[Int]"), - new SchemaItemPath("[type]/JohnType/field1"), - "Prop1", - typeof(TwoPropertyObject), - typeof(List), - AspNet.Execution.FieldResolutionMode.PerSourceItem, - resolver, - polices, - appliedDirectives); - - field.AssignParent(originalParent); - - field.Arguments.AddArgument(new GraphFieldArgument( - field, - "arg1", - GraphTypeExpression.FromDeclaration("String"), - field.Route.CreateChild("arg1"), - GraphArgumentModifiers.None, - "arg1", - "arg1", - typeof(string), - false)); - - field.Complexity = 1.3f; - field.IsDeprecated = true; - field.DeprecationReason = "Because I said so"; - field.Publish = false; - field.FieldSource = AspNet.Internal.TypeTemplates.GraphFieldSource.Method; - - var clonedParent = Substitute.For(); - clonedParent.Route.Returns(new SchemaItemPath("[type]/BobType")); - clonedParent.Name.Returns("BobType"); - var clonedField = field.Clone(clonedParent) as PropertyGraphField; - - Assert.IsNotNull(clonedField); Assert.AreEqual(field.InternalName, clonedField.InternalName); - Assert.AreEqual(field.Name, clonedField.Name); - Assert.AreEqual(field.ObjectType, clonedField.ObjectType); - Assert.AreEqual(field.DeclaredReturnType, clonedField.DeclaredReturnType); - Assert.AreEqual(clonedField.TypeExpression.ToString(), clonedField.TypeExpression.ToString()); - Assert.AreEqual(field.Description, clonedField.Description); - Assert.AreEqual(field.Publish, clonedField.Publish); - Assert.AreEqual("[type]/BobType/field1", clonedField.Route.Path); - Assert.AreEqual(field.Mode, clonedField.Mode); - Assert.AreEqual(field.IsLeaf, clonedField.IsLeaf); - Assert.AreEqual(field.IsDeprecated, clonedField.IsDeprecated); - Assert.AreEqual(field.DeprecationReason, clonedField.DeprecationReason); - Assert.AreEqual(field.Complexity, clonedField.Complexity); - Assert.AreEqual(field.FieldSource, clonedField.FieldSource); Assert.IsFalse(object.ReferenceEquals(field.TypeExpression, clonedField.TypeExpression)); Assert.IsTrue(object.ReferenceEquals(field.Resolver, clonedField.Resolver)); diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GraphSchemaManagerTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphSchemaManagerTests.cs deleted file mode 100644 index 33ad093f5..000000000 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/GraphSchemaManagerTests.cs +++ /dev/null @@ -1,952 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Tests.Schemas -{ - using System; - using System.Collections.Generic; - using System.Linq; - using GraphQL.AspNet.Common.Extensions; - using GraphQL.AspNet.Configuration; - using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Internal.Resolvers; - using GraphQL.AspNet.Schemas; - using GraphQL.AspNet.Schemas.Structural; - using GraphQL.AspNet.Schemas.TypeSystem; - using GraphQL.AspNet.Tests.CommonHelpers; - using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - using GraphQL.AspNet.Tests.Schemas.SchemaTestData; - using Microsoft.Extensions.DependencyInjection; - using NUnit.Framework; - - [TestFixture] - public class GraphSchemaManagerTests - { - public enum RandomEnum - { - Value0, - Value1, - Value2, - } - - [Test] - public void RebuildIntrospectionData_AllDefaultFieldsAdded() - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - manager.RebuildIntrospectionData(); - - Assert.AreEqual(2, schema.KnownTypes.Count(x => x.Kind == TypeKind.SCALAR)); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.STRING)); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.BOOLEAN)); - - Assert.AreEqual(2, schema.KnownTypes.Count(x => x.Kind == TypeKind.ENUM)); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(DirectiveLocation), TypeKind.ENUM)); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(TypeKind), TypeKind.ENUM)); - - Assert.AreEqual(7, schema.KnownTypes.Count(x => x.Kind == TypeKind.OBJECT)); - Assert.IsTrue(schema.KnownTypes.Contains(Constants.ReservedNames.QUERY_TYPE_NAME)); - Assert.IsTrue(schema.KnownTypes.Contains(Constants.ReservedNames.DIRECTIVE_TYPE)); - Assert.IsTrue(schema.KnownTypes.Contains(Constants.ReservedNames.ENUM_VALUE_TYPE)); - Assert.IsTrue(schema.KnownTypes.Contains(Constants.ReservedNames.FIELD_TYPE)); - Assert.IsTrue(schema.KnownTypes.Contains(Constants.ReservedNames.INPUT_VALUE_TYPE)); - Assert.IsTrue(schema.KnownTypes.Contains(Constants.ReservedNames.SCHEMA_TYPE)); - Assert.IsTrue(schema.KnownTypes.Contains(Constants.ReservedNames.TYPE_TYPE)); - } - - [Test] - public void AddSingleQueryAction_AllDefaults_EnsureFieldStructure() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(); - - var action = GraphQLTemplateHelper.CreateFieldTemplate(nameof(SimpleMethodController.TestActionMethod)); - - // query root exists, mutation does not (nothing was added to it) - Assert.IsTrue(schema.Operations.ContainsKey(GraphOperationType.Query)); - Assert.IsFalse(schema.Operations.ContainsKey(GraphOperationType.Mutation)); - - // field for the controller exists - var topFieldName = nameof(SimpleMethodController).Replace(Constants.CommonSuffix.CONTROLLER_SUFFIX, string.Empty); - Assert.IsTrue(schema.Operations[GraphOperationType.Query].Fields.ContainsKey(topFieldName)); - - // ensure the field on the query is the right name (or throw) - var topField = schema.Operations[GraphOperationType.Query][topFieldName]; - Assert.IsNotNull(topField); - - var type = schema.KnownTypes.FindGraphType(topField) as IObjectGraphType; - - // ensure the action was put into the field collection of the controller operation - Assert.IsTrue(type.Fields.ContainsKey(action.Route.Name)); - } - - [Test] - public void AddSingleQueryAction_NestedRouting_EnsureFieldStructure() - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(); - - // query root exists, mutation does not (nothing was added to it) - Assert.IsTrue(schema.Operations.ContainsKey(GraphOperationType.Query)); - Assert.IsFalse(schema.Operations.ContainsKey(GraphOperationType.Mutation)); - - // field for the controller exists - var fieldName = "path0"; - Assert.IsTrue(schema.Operations[GraphOperationType.Query].Fields.ContainsKey(fieldName)); - - var topField = schema.Operations[GraphOperationType.Query][fieldName]; - var type = schema.KnownTypes.FindGraphType(topField) as IObjectGraphType; - Assert.IsNotNull(type); - Assert.AreEqual(2, type.Fields.Count); // declared field + __typename - - // field contains 1 field for first path segment - Assert.IsTrue(type.Fields.ContainsKey("path1")); - var firstField = type["path1"] as VirtualGraphField; - var firstFieldType = schema.KnownTypes.FindGraphType(firstField) as IObjectGraphType; - - Assert.IsNotNull(firstFieldType); - Assert.AreEqual(2, firstFieldType.Fields.Count); // declared field + __typename - Assert.IsTrue(firstFieldType.Fields.ContainsKey("path2")); - - var actionField = firstFieldType.Fields["path2"]; - Assert.IsNotNull(actionField); - Assert.IsNotNull(actionField.Resolver as GraphControllerActionResolver); - Assert.AreEqual(typeof(TwoPropertyObjectV2), ((GraphControllerActionResolver)actionField.Resolver).ObjectType); - } - - [Test] - public void AddSingleQueryAction_AllDefaults_EnsureTypeStructure() - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(); - - // scalars for arguments on the method exists - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.STRING)); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.INT)); - - // return type exists as an object type - var returnType = typeof(SimpleMethodController).GetMethod(nameof(SimpleMethodController.TestActionMethod)).ReturnType; - Assert.IsTrue(schema.KnownTypes.Contains(returnType, TypeKind.OBJECT)); - } - - [Test] - public void AddSingleQueryAction_NestedRouting_EnsureTypeStructure() - { - var schema = new GraphSchema(); - schema.SetNoAlterationConfiguration(); - - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(); - - // query root exists, mutation does not (nothing was added to it) - Assert.AreEqual(1, schema.Operations.Count); - Assert.IsTrue(schema.Operations.ContainsKey(GraphOperationType.Query)); - Assert.IsFalse(schema.Operations.ContainsKey(GraphOperationType.Mutation)); - - Assert.AreEqual(8, schema.KnownTypes.Count); - - // expect 3 scalars - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.FLOAT)); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.DATETIME)); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.STRING)); - - // expect 5 types to be generated - // ---------------------------------- - // the query operation type - // the top level field representing the controller, "path0" - // the middle level defined on the method "path1" - // the return type from the method itself - // the return type of the routes - Assert.IsTrue(schema.KnownTypes.Contains("Query")); - Assert.IsTrue(schema.KnownTypes.Contains("Query_path0")); - Assert.IsTrue(schema.KnownTypes.Contains("Query_path0_path1")); - Assert.IsTrue(schema.KnownTypes.Contains(nameof(TwoPropertyObjectV2))); - Assert.IsTrue(schema.KnownTypes.Contains(nameof(VirtualResolvedObject))); - - // expect 2 actual reference type to be assigned - // the return type from the method and the virtual result fro the route - // all others are virtual types in this instance - Assert.IsTrue(schema.KnownTypes.Contains(typeof(TwoPropertyObjectV2), TypeKind.OBJECT)); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(VirtualResolvedObject), TypeKind.OBJECT)); - } - - [Test] - public void AddSingleQueryAction_NestedObjectsOnReturnType_EnsureAllTypesAreAdded() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(); - - // mutation root exists and query exists (it must by definition even if blank) - Assert.AreEqual(2, schema.Operations.Count); - Assert.IsTrue(schema.Operations.ContainsKey(GraphOperationType.Query)); - Assert.IsTrue(schema.Operations.ContainsKey(GraphOperationType.Mutation)); - - // 5 distinct scalars (int, uint, float, decimal, string) - Assert.AreEqual(5, schema.KnownTypes.Count(x => x.Kind == TypeKind.SCALAR)); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.STRING)); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.INT)); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.DECIMAL)); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.FLOAT)); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.UINT)); - - // 8 types - // ---------------------- - // mutation operation-type - // query operation-type - // path0 segment - // PersonData - // JobData - // AddressData - // CountryData - // VirtualResolvedObject - Assert.AreEqual(8, schema.KnownTypes.Count(x => x.Kind == TypeKind.OBJECT)); - - // expect a type for the root operation type - Assert.IsTrue(schema.KnownTypes.Contains(Constants.ReservedNames.MUTATION_TYPE_NAME)); - Assert.IsTrue(schema.KnownTypes.Contains(Constants.ReservedNames.QUERY_TYPE_NAME)); - - // expect a type representing the controller top level path - Assert.IsTrue(schema.KnownTypes.Contains($"{Constants.ReservedNames.MUTATION_TYPE_NAME}_path0")); - - // expect a type for the method return type - Assert.IsTrue(schema.KnownTypes.Contains(nameof(PersonData))); - - // person data contains job data - Assert.IsTrue(schema.KnownTypes.Contains(typeof(JobData), TypeKind.OBJECT)); - - // person data contains address data - Assert.IsTrue(schema.KnownTypes.Contains(typeof(AddressData), TypeKind.OBJECT)); - - // address data contains country data - Assert.IsTrue(schema.KnownTypes.Contains(typeof(CountryData), TypeKind.OBJECT)); - } - - [Test] - public void KitchenSinkController_SchemaInjection_FullFieldStructureAndTypeCheck() - { - var server = new TestServerBuilder(TestOptions.UseCodeDeclaredNames) - .AddGraphQL(o => - { - o.DeclarationOptions.DisableIntrospection = true; - o.AddType(); - }) - .Build(); - - var schema = server.Schema; - - // mutation and query - Assert.AreEqual(2, schema.Operations.Count); - - // Query Inspection - // ------------------------------ - - // the controller segment: "Query_path0" - // the method "myActionOperation" should register as a root query - // The Query root itself contains the `__typename` metafield - Assert.AreEqual(3, schema.Operations[GraphOperationType.Query].Fields.Count); - var controllerQueryField = schema.Operations[GraphOperationType.Query]["path0"]; - var methodAsQueryRootField = schema.Operations[GraphOperationType.Query]["myActionOperation"]; - Assert.IsNotNull(schema.Operations[GraphOperationType.Query][Constants.ReservedNames.TYPENAME_FIELD]); - - // deep inspection of the created controller-query-field - Assert.IsNotNull(controllerQueryField); - Assert.AreEqual(0, controllerQueryField.Arguments.Count); - Assert.AreEqual("Kitchen sinks are great", controllerQueryField.Description); - - // the top level controller field should have one field on it - // created from the sub path on the controller route definition "path1" - // that field should be registered as a virtual field - var controllerQueryFieldType = schema.KnownTypes.FindGraphType(controllerQueryField) as IObjectGraphType; - Assert.AreEqual(2, controllerQueryFieldType.Fields.Count); // declared field + __typename - var queryPath1 = controllerQueryFieldType.Fields["path1"]; - Assert.IsTrue(queryPath1 is VirtualGraphField); - Assert.AreEqual(string.Empty, queryPath1.Description); - - // the virtual field (path1) should have two real actions (TestActionMethod, TestAction2) - // and 1 virtual field ("path2") hung off it - var queryPath1Type = schema.KnownTypes.FindGraphType(queryPath1) as IObjectGraphType; - Assert.IsTrue(queryPath1Type is VirtualObjectGraphType); - Assert.AreEqual(3, queryPath1Type.Fields.Count); // declared fields + __typename - - Assert.IsTrue(queryPath1Type.Fields.Any(x => x.Name == "TestActionMethod")); - Assert.IsTrue(queryPath1Type.Fields.Any(x => x.Name == "TestAction2")); - - // path 2 is only declared on mutations - Assert.IsFalse(queryPath1Type.Fields.ContainsKey("path2")); - - // top level query field made from a controller method - Assert.IsNotNull(methodAsQueryRootField); - Assert.AreEqual("myActionOperation", methodAsQueryRootField.Name); - Assert.AreEqual("This is my\n Top Level Query Field", methodAsQueryRootField.Description); - - // Mutation Inspection - // ------------------------------ - var controllerMutationField = schema.Operations[GraphOperationType.Mutation]["path0"]; - var methodAsMutationTopLevelField = schema.Operations[GraphOperationType.Mutation]["SupeMutation"]; - - // deep inspection of the created controller-mutation-field - Assert.IsNotNull(controllerMutationField); - Assert.AreEqual(0, controllerMutationField.Arguments.Count); - Assert.AreEqual("Kitchen sinks are great", controllerMutationField.Description); - - // the controller field on the mutation side should have one field on it - // created from the sub path on the controller route definition "path1" - // that field should be registered as a virtual field - var controllerMutationFieldType = schema.KnownTypes.FindGraphType(controllerMutationField) as IObjectGraphType; - Assert.AreEqual(2, controllerMutationFieldType.Fields.Count); // declared field + __typename - var mutationPath1 = controllerMutationFieldType.Fields["path1"]; - Assert.IsTrue(mutationPath1 is VirtualGraphField); - Assert.AreEqual(string.Empty, mutationPath1.Description); - - // walk down the mutationPath through all its nested layers to the action method - // let an exception be thrown (incorrectly) if any path segment doesnt exist - var mutationPath1Type = schema.KnownTypes.FindGraphType(mutationPath1) as IObjectGraphType; - var childField = mutationPath1Type.Fields["path2"]; - var childFieldType = schema.KnownTypes.FindGraphType(childField) as IObjectGraphType; - Assert.AreEqual(string.Empty, childField.Description); - Assert.IsFalse(childField.IsDeprecated); - - childField = childFieldType.Fields["PAth3"]; - childFieldType = schema.KnownTypes.FindGraphType(childField) as IObjectGraphType; - Assert.AreEqual(string.Empty, childField.Description); - Assert.IsFalse(childField.IsDeprecated); - - childField = childFieldType.Fields["PaTh4"]; - childFieldType = schema.KnownTypes.FindGraphType(childField) as IObjectGraphType; - Assert.AreEqual(string.Empty, childField.Description); - Assert.IsFalse(childField.IsDeprecated); - - childField = childFieldType.Fields["PAT_H5"]; - childFieldType = schema.KnownTypes.FindGraphType(childField) as IObjectGraphType; - Assert.AreEqual(string.Empty, childField.Description); - Assert.IsFalse(childField.IsDeprecated); - - childField = childFieldType.Fields["pathSix"]; - childFieldType = schema.KnownTypes.FindGraphType(childField) as IObjectGraphType; - Assert.AreEqual(string.Empty, childField.Description); - Assert.IsFalse(childField.IsDeprecated); - - var mutationAction = childFieldType.Fields["deepNestedMethod"]; - Assert.AreEqual("This is a mutation", mutationAction.Description); - Assert.IsTrue(mutationAction.IsDeprecated); - Assert.AreEqual("To be removed tomorrow", mutationAction.DeprecationReason); - - // check the top level mutation field - Assert.AreEqual("SupeMutation", methodAsMutationTopLevelField.Name); - Assert.AreEqual("This is my\n Top Level MUtation Field!@@!!", methodAsMutationTopLevelField.Description); - - // Type Checks - // ----------------------------------------------------- - // scalars (2): string, int (from TwoPropertyObject & deprecatedDirective) - // scalars (2): float, datetime (from TwoPropertyObjectV2) - // scalars (2): ulong, long (from method declarations) - // scalars (1): decimal (from CompletePropertyObject) - // scalars (1): bool (from @include and @skip directives) - // the nullable types resolve to their non-nullable scalar in the type list - var scalars = schema.KnownTypes.Where(x => x.Kind == TypeKind.SCALAR).ToList(); - Assert.AreEqual(8, schema.KnownTypes.Count(x => x.Kind == TypeKind.SCALAR)); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(typeof(string))); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(typeof(int))); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(typeof(ulong))); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(typeof(DateTime))); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(typeof(DateTime?))); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(typeof(long))); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(typeof(long?))); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(typeof(decimal))); - - // should not have double - Assert.IsNull(schema.KnownTypes.FindGraphType(typeof(double))); - - // enumerations - Assert.AreEqual(1, schema.KnownTypes.Count(x => x.Kind == TypeKind.ENUM)); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(TestEnumerationOptions), TypeKind.ENUM)); - - // input type checks (TwoPropertyObject, EmptyObject) - Assert.AreEqual(2, schema.KnownTypes.Count(x => x.Kind == TypeKind.INPUT_OBJECT)); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(SimpleObject), TypeKind.INPUT_OBJECT)); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(TwoPropertyObject), TypeKind.INPUT_OBJECT)); - - // general object types - var concreteTypes = schema.KnownTypes.Where(x => (x is ObjectGraphType)).ToList(); - Assert.AreEqual(5, concreteTypes.Count); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(Person), TypeKind.OBJECT)); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(CompletePropertyObject), TypeKind.OBJECT)); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(TwoPropertyObject), TypeKind.OBJECT)); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(TwoPropertyObjectV2), TypeKind.OBJECT)); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(VirtualResolvedObject), TypeKind.OBJECT)); - - // 9 "route" types should ahve been created - // ----------------------------- - // 1. controller query field (path0) - // 2. query virtual path segment path1 - // 3. controller mutation field (path0) - // 4. mutation virtual path segment path1 - // 5. mutation virtual path segment path2 - // 6. mutation virtual path segment PAth3 - // 7. mutation virtual path segment PaTh4 - // 8. mutation virtual path segment PAT_H5 - // 9. mutation virtual path segment pathSix - var virtualTypes = schema.KnownTypes.Where(x => x is VirtualObjectGraphType).ToList(); - Assert.AreEqual(9, virtualTypes.Count); - - // pathSix should have one "real" field, the method named 'deepNestedMethod' - var pathSix = virtualTypes.FirstOrDefault(x => x.Name.Contains("pathSix")) as IObjectGraphType; - Assert.IsNotNull(pathSix); - Assert.AreEqual(2, pathSix.Fields.Count); // declared field + __typename - Assert.IsNotNull(pathSix["deepNestedMethod"]); - - // query_path1 should have two "real" fields, the method named 'TestActionMethod' and 'TestAction2' - var querPath1 = virtualTypes.FirstOrDefault(x => x.Name.Contains("Query_path0_path1")) as IObjectGraphType; - - Assert.IsNotNull(querPath1); - Assert.AreEqual(3, querPath1.Fields.Count); // declared field + __typename - Assert.IsNotNull(querPath1[nameof(KitchenSinkController.TestActionMethod)]); - Assert.IsNotNull(querPath1["TestAction2"]); - } - - [Test] - public void EnsureGraphType_NormalObject_IsAddedWithTypeReference() - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - - manager.EnsureGraphType(typeof(CountryData)); - - // CountryData, string, float?, Query - Assert.AreEqual(4, schema.KnownTypes.Count); - Assert.AreEqual(3, schema.KnownTypes.TypeReferences.Count()); - - Assert.IsTrue(schema.KnownTypes.Contains(typeof(CountryData), TypeKind.OBJECT)); - Assert.IsTrue(schema.KnownTypes.Contains("CountryData")); - } - - [Test] - public void EnsureGraphType_Enum_WithNoKindSupplied_IsAddedCorrectly() - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(typeof(RandomEnum)); - Assert.AreEqual(3, schema.KnownTypes.Count); // added type + query + string - Assert.IsTrue(schema.KnownTypes.Contains(typeof(RandomEnum))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(string))); - } - - [Test] - public void EnsureGraphType_Enum_WithKindSupplied_IsAddedCorrectly() - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(typeof(RandomEnum), TypeKind.ENUM); - Assert.AreEqual(3, schema.KnownTypes.Count); // added type + query + string - Assert.IsTrue(schema.KnownTypes.Contains(typeof(RandomEnum), TypeKind.ENUM)); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(string), TypeKind.SCALAR)); - } - - [Test] - public void EnsureGraphType_Enum_WithIncorrectKindSupplied_ThrowsException() - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - Assert.Throws(() => - { - manager.EnsureGraphType(typeof(RandomEnum), TypeKind.SCALAR); - }); - } - - [Test] - public void EnsureGraphType_Enum_WithIncorrectKindSupplied_ThatIsCoercable_AddsCorrectly() - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - - // object will be coerced to enum - manager.EnsureGraphType(typeof(RandomEnum), TypeKind.OBJECT); - Assert.AreEqual(3, schema.KnownTypes.Count); // added type + query + string - Assert.IsTrue(schema.KnownTypes.Contains(typeof(string), TypeKind.SCALAR)); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(RandomEnum), TypeKind.ENUM)); - Assert.IsFalse(schema.KnownTypes.Contains(typeof(RandomEnum), TypeKind.OBJECT)); - } - - [Test] - public void EnsureGraphType_Scalar_WithNoKindSupplied_IsAddedCorrectly() - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(typeof(string)); - Assert.AreEqual(2, schema.KnownTypes.Count); // added type + query - Assert.IsTrue(schema.KnownTypes.Contains(typeof(string))); - } - - [Test] - public void EnsureGraphType_Scalar_WithKindSupplied_IsAddedCorrectly() - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(typeof(string), TypeKind.SCALAR); - Assert.AreEqual(2, schema.KnownTypes.Count); // added type + query - Assert.IsTrue(schema.KnownTypes.Contains(typeof(string), TypeKind.SCALAR)); - } - - [Test] - public void EnsureGraphType_Scalar_WithIncorrectKindSupplied_ThrowsException() - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - Assert.Throws(() => - { - manager.EnsureGraphType(typeof(string), TypeKind.ENUM); - }); - } - - [Test] - public void EnsureGraphType_ScalarTwice_EndsUpInScalarCollectionOnce() - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(typeof(int)); - manager.EnsureGraphType(typeof(int)); - - Assert.AreEqual(3, schema.KnownTypes.Count); // added type + query + string - Assert.IsNotNull(schema.KnownTypes.FindGraphType(typeof(int))); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.INT)); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.STRING)); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(int))); - } - - [Test] - public void EnsureGraphType_TwoScalar_EndsUpInScalarCollectionOnceEach() - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(typeof(int)); - manager.EnsureGraphType(typeof(long)); - - Assert.AreEqual(4, schema.KnownTypes.Count); // added types + query + string - Assert.IsNotNull(schema.KnownTypes.FindGraphType(typeof(int))); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.INT)); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(typeof(long))); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.LONG)); - Assert.IsNotNull(schema.KnownTypes.FindGraphType(Constants.ScalarNames.STRING)); - } - - [Test] - public void EnsureGraphType_IEnumerableT_EndsWithTInGraphType() - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(typeof(IEnumerable)); - - Assert.AreEqual(3, schema.KnownTypes.Count); // added type + query + string - Assert.IsTrue(schema.KnownTypes.Contains(typeof(int))); - Assert.IsTrue(schema.KnownTypes.Contains(Constants.ScalarNames.INT)); - Assert.IsTrue(schema.KnownTypes.Contains(Constants.ScalarNames.STRING)); - } - - [Test] - public void EnsureGraphType_ListT_EndsWithTInGraphType() - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(typeof(List)); - - Assert.AreEqual(3, schema.KnownTypes.Count); // added type + query + string - Assert.IsTrue(schema.KnownTypes.Contains(typeof(int))); - Assert.IsTrue(schema.KnownTypes.Contains(Constants.ScalarNames.INT)); - Assert.IsTrue(schema.KnownTypes.Contains(Constants.ScalarNames.STRING)); - } - - [Test] - public void EnsureGraphType_ListT_AfterManualAddOfScalar_Succeeds() - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - - var intScalar = GraphQLProviders.ScalarProvider.CreateScalar(typeof(int)); - schema.KnownTypes.EnsureGraphType(intScalar, typeof(int)); - manager.EnsureGraphType(typeof(List)); - - Assert.AreEqual(3, schema.KnownTypes.Count); // added type + query + string - Assert.IsTrue(schema.KnownTypes.Contains(typeof(int))); - Assert.IsTrue(schema.KnownTypes.Contains(Constants.ScalarNames.INT)); - Assert.IsTrue(schema.KnownTypes.Contains(Constants.ScalarNames.STRING)); - - var includedType = schema.KnownTypes.FindGraphType(typeof(int)); - Assert.AreEqual(includedType, intScalar); - } - - [Test] - public void EnsureGraphType_DictionaryTK_ThrowsExeption() - { - Assert.Throws(() => - { - var schema = new GraphSchema() as ISchema; - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(typeof(Dictionary)); - }); - } - - [Test] - public void EnsureGraphType_WithSubTypes_AreAddedCorrectly() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(typeof(PersonData)); - - Assert.AreEqual(10, schema.KnownTypes.Count); // added types + query - Assert.IsTrue(schema.KnownTypes.Contains(typeof(AddressData))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(PersonData))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(CountryData))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(JobData))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(string))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(float))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(float?))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(uint))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(decimal))); - } - - [Test] - public void EnsureGraphType_WhenRootControllerActionOfSameNameAsAType_IsAdded_ThrowsException() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - var manager = new GraphSchemaManager(schema); - - // ThingController declares two routes - // [Query]/Thing (A root operation) - // [Query]/Thing/moreData (a nested operation under a "thing" controller) - // there is also a type [Type]/Thing in the schema - // - // this should create an impossible situation where - // [Query]/Thing returns the "Thing" graph type - // and the controller will try to nest "moreData" into this OBJECT type - // which should fail - Assert.Throws(() => - { - manager.EnsureGraphType(); - }); - } - - [Test] - public void EnsureGraphType_WhenTwoControllersDecalreTheNameRootRouteName_AMeaningfulExceptionisThrown() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - var manager = new GraphSchemaManager(schema); - - manager.EnsureGraphType(); - - Assert.Throws(() => - { - manager.EnsureGraphType(); - }); - } - - [Test] - public void EnsureGraphType_WhenControllerReturnTypeIsGraphTypeIsAFlatArray_IsAddedCorrectly() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(); - - Assert.AreEqual(4, schema.KnownTypes.Count); // added types + query - Assert.IsTrue(schema.KnownTypes.Contains(typeof(int))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(string))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(TwoPropertyObject))); - } - - [Test] - public void EnsureGraphType_WhenControllerReturnTypeDeclarationIsGraphTypeIsAFlatArray_IsAddedCorrectly() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(); - - Assert.AreEqual(4, schema.KnownTypes.Count); // added types + query - Assert.IsTrue(schema.KnownTypes.Contains(typeof(int))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(string))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(TwoPropertyObject))); - } - - [Test] - public void EnsureGraphType_WhenPropertyIsFlatArray_IsAddedCorrectly() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(); - - Assert.AreEqual(5, schema.KnownTypes.Count); // added types + query - Assert.IsTrue(schema.KnownTypes.Contains(typeof(ArrayPropertyObject))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(string))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(int))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(TwoPropertyObject))); - } - - [Test] - public void EnsureGraphType_WhenMethodReturnIsFlatArray_IsAddedCorrectly() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(); - - Assert.AreEqual(5, schema.KnownTypes.Count); // added types + query - - Assert.IsTrue(schema.KnownTypes.Contains(typeof(ArrayMethodObject))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(string))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(int))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(TwoPropertyObject))); - } - - [Test] - public void EnsureGraphType_WhenContainsAGenericType_IsAddedCorrectly() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType(); - - Assert.AreEqual(5, schema.KnownTypes.Count); // added types + query - - Assert.IsTrue(schema.KnownTypes.Contains(typeof(KeyValuePairObject))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(string))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(int))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(KeyValuePair))); - } - - [Test] - public void EnsureGraphType_WhenIsAGenericType_IsAddedCorrectly() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType>(); - manager.EnsureGraphType>(); - - Assert.AreEqual(7, schema.KnownTypes.Count); // added types + query - - Assert.IsTrue(schema.KnownTypes.Contains(typeof(TwoPropertyGenericObject))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(TwoPropertyGenericObject))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(int))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(string))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(DateTime))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(double))); - } - - [Test] - public void EnsureGraphType_WhenIsAGenericType_WithListTypeParam_IsAddedCorrectly() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - - var manager = new GraphSchemaManager(schema); - manager.EnsureGraphType>(); - - Assert.AreEqual(4, schema.KnownTypes.Count); // added types + query - - Assert.IsTrue(schema.KnownTypes.Contains(typeof(TwoPropertyGenericObject))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(int))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(string))); - - var type = schema.KnownTypes.FindGraphType(typeof(TwoPropertyGenericObject)); - } - - [Test] - public void EnsureGraphType_WhenIsAGenericType_ButDeclaresACustomName_ThrowsException() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - - var manager = new GraphSchemaManager(schema); - try - { - manager.EnsureGraphType>(); - } - catch (GraphTypeDeclarationException ex) - { - var name = typeof(GenericObjectTypeWithGraphTypeName).GetGenericTypeDefinition().FriendlyName(); - Assert.IsTrue(ex.Message.Contains(name)); - return; - } - - Assert.Fail("No exception was thrown when one was expected."); - } - - [Test] - public void EnsureGraphType_WhenControllerHasInputParameterAsInterface_ThrowsException() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - - var manager = new GraphSchemaManager(schema); - - var ex = Assert.Throws(() => - { - manager.EnsureGraphType(); - }); - - Assert.AreEqual(typeof(ControllerWithInterfaceInput), ex.FailedObjectType); - Assert.IsNotNull(ex.InnerException); - var name = typeof(IPersonData).FriendlyName(); - Assert.IsTrue(ex.InnerException.Message.Contains(name)); - } - - [Test] - public void AttemptingToExtendATypeDirectly_AndThroughInterface_ThrowsException() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - - var manager = new GraphSchemaManager(schema); - - var ex = Assert.Throws(() => - { - manager.EnsureGraphType(); - }); - - Assert.AreEqual(typeof(ControllerWithDirectAndIndirectTypeExtension), ex.FailedObjectType); - Assert.IsNotNull(ex.InnerException); - - var name = typeof(TwoPropertyObject).FriendlyName(); - Assert.IsTrue(ex.InnerException.Message.Contains(name)); - } - - [Test] - public void EnsureGraphType_WhenATypeWithNoStringsIsAdded_StringIsStillAddedBecauseOfTypeName() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - - var manager = new GraphSchemaManager(schema); - - manager.EnsureGraphType(); - - // query, ObjectWittNoStrings, int, string - Assert.AreEqual(4, schema.KnownTypes.Count); - - Assert.AreEqual(1, schema.Operations.Values.Count); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(ObjectWithNoStrings))); // the item itself - Assert.IsTrue(schema.KnownTypes.Contains(typeof(int))); // for the declared property - Assert.IsTrue(schema.KnownTypes.Contains(typeof(string))); // for __typename - } - - [Test] - public void TwoTypesWithSharePublicInvalidInterface_WhenInterfaceIsNotexplicitlyReferenced_InterfaceIsNotAdded() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - - var manager = new GraphSchemaManager(schema); - - // both types reference INoFieldInterface which would be invalid - // in the schema - // the schema should not see it though - manager.EnsureGraphType(); - manager.EnsureGraphType(); - - // query, Object1, Object2, int, string - Assert.AreEqual(5, schema.KnownTypes.Count); - - Assert.AreEqual(1, schema.Operations.Values.Count); // query type - Assert.AreEqual(GraphOperationType.Query, schema.Operations.Values.First().OperationType); - - Assert.IsTrue(schema.KnownTypes.Contains(typeof(int))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(Object1ReferencesNoFieldInterface))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(Object2ReferencesNoFieldInterface))); - Assert.IsTrue(schema.KnownTypes.Contains(typeof(string))); // for __typename - } - - [Test] - public void TwoTypesWithSharedPublicInvalidInterface_WhenInterfaceIsReturnedFromController_FailsToCreate() - { - var schema = new GraphSchema() as ISchema; - schema.SetNoAlterationConfiguration(); - - var manager = new GraphSchemaManager(schema); - - // both types reference INoFieldInterface which would be invalid - // in the schema - // the schema should not see it though - manager.EnsureGraphType(); - manager.EnsureGraphType(); - - // explicitly references INoFieldInterface, causing it to be parsed - // and causing a failure - var ex = Assert.Throws(() => - { - manager.EnsureGraphType(); - }); - - Assert.AreEqual(typeof(ControllerWithNoFieldInterfaceReturned), ex.FailedObjectType); - } - - [Test] - public void ObjectTypeWithMethodOverloads_WhenOnlyOneWillBeAddedToTheSchema_IsAllowed() - { - var schema = new GraphSchema() as ISchema; - var options = new SchemaOptions(new ServiceCollection()); - options.DeclarationOptions.FieldDeclarationRequirements = TemplateDeclarationRequirements.Method; - - schema.Configuration.Merge(options.CreateConfiguration()); - - var manager = new GraphSchemaManager(schema); - - // even though this object declares a method overloads - // because of the inclusion rules only the explicitly decalred one shouldbe included - // and no exception should be thrown - manager.EnsureGraphType(); - - var type = schema.KnownTypes.FindGraphType(typeof(ObjectWithMethodOverloads)) as IObjectGraphType; - Assert.IsNotNull(type); - Assert.AreEqual(2, type.Fields.Count); - Assert.IsTrue(type.Fields.Any(x => string.Compare(x.Name, nameof(ObjectWithMethodOverloads.Method1), true) == 0)); - Assert.IsTrue(type.Fields.Any(x => x.Name == Constants.ReservedNames.TYPENAME_FIELD)); - } - - [Test] - public void ObjectTypeWithMethodOverloads_WhenBothWillAdd_ThrowsException() - { - var schema = new GraphSchema() as ISchema; - var options = new SchemaOptions(new ServiceCollection()); - options.DeclarationOptions.FieldDeclarationRequirements - = TemplateDeclarationRequirements.None; - - schema.Configuration.Merge(options.CreateConfiguration()); - - var manager = new GraphSchemaManager(schema); - - // even though this object declares a method overloads - // because of the inclusion rules only the explicitly decalred one shouldbe included - // and no exception should be thrown - var ex = Assert.Throws(() => - { - manager.EnsureGraphType(); - }); - - Assert.AreEqual(typeof(ObjectWithMethodOverloads), ex.FailedObjectType); - } - } -} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/GraphTypeExpressionTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphTypeExpressionTests.cs index 320d667b2..9f4cd374f 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/GraphTypeExpressionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphTypeExpressionTests.cs @@ -13,7 +13,7 @@ namespace GraphQL.AspNet.Tests.Schemas using System.Collections.Generic; using System.Linq; using GraphQL.AspNet.Schemas; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using NUnit.Framework; using MGT = GraphQL.AspNet.Schemas.TypeSystem.MetaGraphTypes; @@ -165,34 +165,36 @@ public void ParseDeclaration( } } - [TestCase(null, "String", false)] - [TestCase("String", null, false)] - [TestCase(null, null, false)] - [TestCase("String", "String", true)] - [TestCase("String", "String", true)] - [TestCase("[String]", "[String]", true)] - [TestCase("[String!]", "[String!]", true)] - [TestCase("[String]!", "[String]!", true)] - [TestCase("[String!]!", "[String!]!", true)] - [TestCase("String", "Int", false)] - [TestCase("String", "string", false)] - [TestCase("String", "String!", true)] - [TestCase("String", "[String]", false)] - [TestCase("[String]", "String", false)] - [TestCase("[Int]", "[Int]!", true)] - [TestCase("[Int!]", "[Int]", false)] - [TestCase("[Int]", "[Int!]", true)] - [TestCase("[Int]", "[[Int]]", false)] - [TestCase("[[[Int]!]]", "[[[Int!]!]!]!", true)] - [TestCase("[[[Int]!]]!", "[[[Int!]!]!]!", true)] - [TestCase("[[[Int!]]!]", "[[[Int!]!]!]!", true)] - [TestCase("[[[String!]]!]", "[[[Float!]!]!]!", false)] - public void AreCompatiable(string targetExpression, string suppliedExpression, bool shouldBeCompatiable) + [TestCase(null, "String", true, false)] + [TestCase("String", null, true, false)] + [TestCase(null, null, true, false)] + [TestCase("String", "String", true, true)] + [TestCase("String", "String", true, true)] + [TestCase("[String]", "[String]", true, true)] + [TestCase("[String!]", "[String!]", true, true)] + [TestCase("[String]!", "[String]!", true, true)] + [TestCase("[String!]!", "[String!]!", true, true)] + [TestCase("String", "Int", true, false)] + [TestCase("String", "string", true, false)] + [TestCase("String", "String!", true, true)] + [TestCase("String", "[String]", true, false)] + [TestCase("[String]", "String", true, false)] + [TestCase("[Int]", "[Int]!", true, true)] + [TestCase("[Int!]", "[Int]", true, false)] + [TestCase("[Int]", "[Int!]", true, true)] + [TestCase("[Int]", "[[Int]]", true, false)] + [TestCase("[[[Int]!]]", "[[[Int!]!]!]!", true, true)] + [TestCase("[[[Int]!]]!", "[[[Int!]!]!]!", true, true)] + [TestCase("[[[Int!]]!]", "[[[Int!]!]!]!", true, true)] + [TestCase("[[[String!]]!]", "[[[Float!]!]!]!", true, false)] + [TestCase("[Int!]!", "[String!]!", false, true)] + [TestCase("[Int!]!", "[String!]!", true, false)] + public void AreCompatiable(string targetExpression, string suppliedExpression, bool matchTypeName, bool shouldBeCompatiable) { var target = targetExpression == null ? null : GraphTypeExpression.FromDeclaration(targetExpression); var supplied = suppliedExpression == null ? null : GraphTypeExpression.FromDeclaration(suppliedExpression); - var result = GraphTypeExpression.AreTypesCompatiable(target, supplied); + var result = GraphTypeExpression.AreTypesCompatiable(target, supplied, matchTypeName); Assert.AreEqual(shouldBeCompatiable, result); } diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/GraphValidationTestData/ControllerWithNoSecurityPolicies.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphValidationTestData/ControllerWithNoSecurityPolicies.cs similarity index 89% rename from src/unit-tests/graphql-aspnet-tests/Internal/GraphValidationTestData/ControllerWithNoSecurityPolicies.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/GraphValidationTestData/ControllerWithNoSecurityPolicies.cs index c65b26ea2..7a3cfca0e 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/GraphValidationTestData/ControllerWithNoSecurityPolicies.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphValidationTestData/ControllerWithNoSecurityPolicies.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.GraphValidationTestData +namespace GraphQL.AspNet.Tests.Schemas.GraphValidationTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/GraphValidationTestData/ControllerWithSecurityPolicies.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphValidationTestData/ControllerWithSecurityPolicies.cs similarity index 90% rename from src/unit-tests/graphql-aspnet-tests/Internal/GraphValidationTestData/ControllerWithSecurityPolicies.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/GraphValidationTestData/ControllerWithSecurityPolicies.cs index b20266943..0fd5469c6 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/GraphValidationTestData/ControllerWithSecurityPolicies.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphValidationTestData/ControllerWithSecurityPolicies.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.GraphValidationTestData +namespace GraphQL.AspNet.Tests.Schemas.GraphValidationTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/GraphValidationTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphValidationTests.cs similarity index 89% rename from src/unit-tests/graphql-aspnet-tests/Internal/GraphValidationTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/GraphValidationTests.cs index 42b6e5c40..8572cedab 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/GraphValidationTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphValidationTests.cs @@ -7,16 +7,17 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal +namespace GraphQL.AspNet.Tests.Schemas { using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Internal; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - using GraphQL.AspNet.Tests.Internal.GraphValidationTestData; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Schemas.GraphValidationTestData; + using Microsoft.AspNetCore.Authorization; using NUnit.Framework; [TestFixture] @@ -135,7 +136,7 @@ public void EliminateWrappersFromCoreType( public void RetrieveSecurityPolicies_NullAttributeProvider_YieldsNoPolicies() { var policies = GraphValidation.RetrieveSecurityPolicies(null); - Assert.AreEqual(0, policies.Count()); + Assert.AreEqual(0, Enumerable.Count(policies)); } [Test] @@ -144,7 +145,7 @@ public void RetrieveSecurityPolicies_MethodWithNoSecurityPolicies_ReturnsEmptyEn var info = typeof(ControllerWithNoSecurityPolicies).GetMethod(nameof(ControllerWithNoSecurityPolicies.SomeMethod)); var policies = GraphValidation.RetrieveSecurityPolicies(info); - Assert.AreEqual(0, policies.Count()); + Assert.AreEqual(0, Enumerable.Count(policies)); } [Test] @@ -153,7 +154,17 @@ public void RetrieveSecurityPolicies_MethodWithSecurityPolicies_ReturnsOneItem() var info = typeof(ControllerWithSecurityPolicies).GetMethod(nameof(ControllerWithSecurityPolicies.SomeMethod)); var policies = GraphValidation.RetrieveSecurityPolicies(info); - Assert.AreEqual(1, policies.Count()); + Assert.AreEqual(1, Enumerable.Count(policies)); + } + + [TestCase("BobSmith", true)] + [TestCase("Bob##Smith", false)] + [TestCase("", false)] + [TestCase(null, false)] + public void IsValidName(string name, bool isValid) + { + var result = GraphValidation.IsValidGraphName(name); + Assert.AreEqual(isValid, result); } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/IntrospectionDefaultValueTestData/TestEnum.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/IntrospectionDefaultValueTestData/TestEnum.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/IntrospectionDefaultValueTestData/TestEnum.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/IntrospectionDefaultValueTestData/TestEnum.cs index c42ea4584..3bc3b4409 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/IntrospectionDefaultValueTestData/TestEnum.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/IntrospectionDefaultValueTestData/TestEnum.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.IntrospectionDefaultValueTestData +namespace GraphQL.AspNet.Tests.Schemas.IntrospectionDefaultValueTestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/IntrospectionDefaultValueTestData/TestEnumNoDefaultValue.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/IntrospectionDefaultValueTestData/TestEnumNoDefaultValue.cs similarity index 84% rename from src/unit-tests/graphql-aspnet-tests/Internal/IntrospectionDefaultValueTestData/TestEnumNoDefaultValue.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/IntrospectionDefaultValueTestData/TestEnumNoDefaultValue.cs index 4ac8593ac..6eef27898 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/IntrospectionDefaultValueTestData/TestEnumNoDefaultValue.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/IntrospectionDefaultValueTestData/TestEnumNoDefaultValue.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.IntrospectionDefaultValueTestData +namespace GraphQL.AspNet.Tests.Schemas.IntrospectionDefaultValueTestData { public enum TestEnumNoDefaultValue { diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/IntrospectionDefaultValueTestData/TestEnumNotIncludedDefault.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/IntrospectionDefaultValueTestData/TestEnumNotIncludedDefault.cs similarity index 85% rename from src/unit-tests/graphql-aspnet-tests/Internal/IntrospectionDefaultValueTestData/TestEnumNotIncludedDefault.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/IntrospectionDefaultValueTestData/TestEnumNotIncludedDefault.cs index 0f5f4bd08..ece3561d7 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/IntrospectionDefaultValueTestData/TestEnumNotIncludedDefault.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/IntrospectionDefaultValueTestData/TestEnumNotIncludedDefault.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.IntrospectionDefaultValueTestData +namespace GraphQL.AspNet.Tests.Schemas.IntrospectionDefaultValueTestData { using GraphQL.AspNet.Attributes; diff --git a/src/unit-tests/graphql-aspnet-tests/Internal/IntrospectionDefaultValueValidationTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/IntrospectionDefaultValueValidationTests.cs similarity index 98% rename from src/unit-tests/graphql-aspnet-tests/Internal/IntrospectionDefaultValueValidationTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/IntrospectionDefaultValueValidationTests.cs index bf03456e8..5926a2811 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/IntrospectionDefaultValueValidationTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/IntrospectionDefaultValueValidationTests.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal +namespace GraphQL.AspNet.Tests.Schemas { using System.Collections.Generic; using GraphQL.AspNet.Execution; @@ -17,9 +17,9 @@ namespace GraphQL.AspNet.Tests.Internal using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Schemas.TypeSystem.Introspection.Model; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; - using GraphQL.AspNet.Tests.Internal.IntrospectionDefaultValueTestData; + using GraphQL.AspNet.Tests.Schemas.IntrospectionDefaultValueTestData; using NSubstitute; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/RuntimeFieldsGeneralTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/RuntimeFieldsGeneralTests.cs new file mode 100644 index 000000000..b3e5a925c --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/RuntimeFieldsGeneralTests.cs @@ -0,0 +1,87 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas +{ + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Controllers.ActionResults; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Framework; + using NUnit.Framework; + + [TestFixture] + public class RuntimeFieldsGeneralTests + { + [Test] + public void InternalName_OnQueryField_IssCarriedToSchema() + { + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapQuery("field1", () => 1) + .WithInternalName("field1_internal_name"); + }) + .Build(); + + var operation = server.Schema.Operations[GraphOperationType.Query]; + var field = operation.Fields.FindField("field1"); + Assert.AreEqual("field1_internal_name", field.InternalName); + } + + [Test] + public void InternalName_OnMutationField_IssCarriedToSchema() + { + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapMutation("field1", () => 1) + .WithInternalName("field1_internal_name"); + }) + .Build(); + + var operation = server.Schema.Operations[GraphOperationType.Mutation]; + var field = operation.Fields.FindField("field1"); + Assert.AreEqual("field1_internal_name", field.InternalName); + } + + [Test] + public void InternalName_OnTypeExension_IssCarriedToSchema() + { + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddType(); + o.MapTypeExtension("field1", () => 1) + .WithInternalName("extension_field_Internal_Name"); + }) + .Build(); + + var obj = server.Schema.KnownTypes.FindGraphType(typeof(TwoPropertyObject)) as IObjectGraphType; + var field = obj.Fields.FindField("field1"); + Assert.AreEqual("extension_field_Internal_Name", field.InternalName); + } + + [Test] + public void InternalName_OnDirective_IsCarriedToSchema() + { + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.MapDirective("@myDirective", () => GraphActionResult.Ok()) + .WithInternalName("directive_internal_name"); + }) + .Build(); + + var dir = server.Schema.KnownTypes.FindDirective("myDirective"); + Assert.AreEqual("directive_internal_name", dir.InternalName); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaLanguageGeneratorTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaLanguageGeneratorTests.cs index 8f96ed6d4..d93253421 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaLanguageGeneratorTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaLanguageGeneratorTests.cs @@ -19,8 +19,9 @@ namespace GraphQL.AspNet.Tests.Schemas using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Schemas.TypeSystem.Scalars; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Tests.Schemas.QueryLanguageTestData; using NUnit.Framework; @@ -234,8 +235,7 @@ public SchemaLanguageGeneratorTests() // ensure all scalars represented _unUsedScalarTypes = new List(); - var scalarProvider = new DefaultScalarGraphTypeProvider(); - foreach (var type in scalarProvider.ConcreteTypes) + foreach (var type in GlobalTypes.ScalarConcreteTypes) { if (Validation.IsNullableOfT(type)) continue; diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayInputAndReturnController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayInputAndReturnController.cs index 40b34a27e..679b65617 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayInputAndReturnController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayInputAndReturnController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ArrayInputAndReturnController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayMethodObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayMethodObject.cs index 802eabedd..7ae4dab28 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayMethodObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayMethodObject.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData { using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ArrayMethodObject { diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayObjectInputController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayObjectInputController.cs index 24d09ed52..12377e123 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayObjectInputController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayObjectInputController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ArrayObjectInputController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayPropertyObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayPropertyObject.cs index 7552e9afc..57e327d33 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayPropertyObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayPropertyObject.cs @@ -9,7 +9,7 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData { - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ArrayPropertyObject { diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayReturnController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayReturnController.cs index 9254d1f2c..47fc588e2 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayReturnController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayReturnController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ArrayReturnController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayReturnDeclarationController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayReturnDeclarationController.cs index 0d223cc0b..bf0414125 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayReturnDeclarationController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ArrayReturnDeclarationController.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.Interfaces.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ArrayReturnDeclarationController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ControllerWithDirectAndIndirectTypeExtension.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ControllerWithDirectAndIndirectTypeExtension.cs index fd89dd2e4..76ca4db94 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ControllerWithDirectAndIndirectTypeExtension.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/ControllerWithDirectAndIndirectTypeExtension.cs @@ -11,7 +11,8 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Common.Interfaces; using GraphQL.AspNet.Tests.Framework.Interfaces; public class ControllerWithDirectAndIndirectTypeExtension : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/DictionaryMethodObject.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/DictionaryMethodObject.cs index 8ba0df02f..a23fadbc4 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/DictionaryMethodObject.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/DictionaryMethodObject.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData using System.Collections.Generic; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class DictionaryMethodObject : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/GenericObjectTypeWithGraphTypeName.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/GenericObjectTypeWithGraphTypeName.cs index 4ecb07cfd..877914cce 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/GenericObjectTypeWithGraphTypeName.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/GenericObjectTypeWithGraphTypeName.cs @@ -10,7 +10,7 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData { using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphType(InputName = "OtherName")] public class GenericObjectTypeWithGraphTypeName : TwoPropertyGenericObject diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/KitchenSinkController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/KitchenSinkController.cs index bfcecd1d1..bb6a9ea52 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/KitchenSinkController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/KitchenSinkController.cs @@ -15,8 +15,8 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData using System.Threading.Tasks; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.CommonHelpers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; [GraphRoute("path0/path1")] [Description("Kitchen sinks are great")] diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/NestedQueryMethodController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/NestedQueryMethodController.cs index 38b40b87f..3fd4c0c88 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/NestedQueryMethodController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/NestedQueryMethodController.cs @@ -12,7 +12,7 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData using System; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; [GraphRoute("path0")] public class NestedQueryMethodController : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/PocoWithInterfaceArgument.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/PocoWithInterfaceArgument.cs new file mode 100644 index 000000000..4751d9b89 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/PocoWithInterfaceArgument.cs @@ -0,0 +1,24 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Tests.Common.Interfaces; + using GraphQL.AspNet.Tests.Framework.Interfaces; + + public class PocoWithInterfaceArgument + { + [GraphField] + public string DoMethod(ISinglePropertyObject obj) + { + return string.Empty; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/SimpleMethodController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/SimpleMethodController.cs index f277b4f45..fac602c25 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/SimpleMethodController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/SimpleMethodController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; /// /// A simple controller utilzing all default values with no overrides. diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/TypeExtensionController.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/TypeExtensionController.cs index e85012958..038438cf3 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/TypeExtensionController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTestData/TypeExtensionController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Schemas.SchemaTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class TypeExtensionController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTypeCollectionInterefaceImplementationTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTypeCollectionInterefaceImplementationTests.cs index 0e0804104..ebed56e70 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTypeCollectionInterefaceImplementationTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTypeCollectionInterefaceImplementationTests.cs @@ -11,7 +11,6 @@ namespace GraphQL.AspNet.Tests.Schemas using System; using System.Collections.Generic; using System.Linq; - using GraphQL.AspNet.Engine.TypeMakers; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Schemas.TypeSystem; @@ -19,6 +18,11 @@ namespace GraphQL.AspNet.Tests.Schemas using GraphQL.AspNet.Tests.Framework; using GraphQL.AspNet.Tests.Schemas.SchemaTestData.InterfaceRegistrationTestData; using NUnit.Framework; + using GraphQL.AspNet.Schemas.Generation; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using Microsoft.AspNetCore.Hosting.Server; + using GraphQL.AspNet.Tests.CommonHelpers; [TestFixture] [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] @@ -75,10 +79,13 @@ public SchemaTypeCollectionInterefaceImplementationTests() _donut = this.MakeGraphType(typeof(Donut), TypeKind.OBJECT) as IObjectGraphType; // type extension - var template = GraphQLProviders.TemplateProvider.ParseType(typeof(PastryExtensionController)) as IGraphControllerTemplate; - var hasSugarTemplate = template.Extensions.FirstOrDefault(x => x.InternalName == nameof(PastryExtensionController.HasSugarExtension)); - var hasGlazeTemplate = template.Extensions.FirstOrDefault(x => x.InternalName == nameof(PastryExtensionController.HasGlazeExtension)); - var hasDoubleGlazeTemplate = template.Extensions.FirstOrDefault(x => x.InternalName == nameof(PastryExtensionController.HasDoubleGlazeExtension)); + var template = new GraphControllerTemplate(typeof(PastryExtensionController)) as IGraphControllerTemplate; + template.Parse(); + template.ValidateOrThrow(); + + var hasSugarTemplate = template.Extensions.FirstOrDefault(x => x.DeclaredName == nameof(PastryExtensionController.HasSugarExtension)); + var hasGlazeTemplate = template.Extensions.FirstOrDefault(x => x.DeclaredName == nameof(PastryExtensionController.HasGlazeExtension)); + var hasDoubleGlazeTemplate = template.Extensions.FirstOrDefault(x => x.DeclaredName == nameof(PastryExtensionController.HasDoubleGlazeExtension)); _hasSugarFieldExtension = this.MakeGraphField(hasSugarTemplate); _hasGlazeFieldExtension = this.MakeGraphField(hasGlazeTemplate); _hasDoubleGlazeFieldExtension = this.MakeGraphField(hasDoubleGlazeTemplate); @@ -94,14 +101,20 @@ private IGraphType MakeGraphType(Type type, TypeKind kind) { var testServer = new TestServerBuilder().Build(); - var maker = GraphQLProviders.GraphTypeMakerProvider.CreateTypeMaker(testServer.Schema, kind); - return maker.CreateGraphType(type).GraphType; + var factory = testServer.CreateMakerFactory(); + + var template = factory.MakeTemplate(type, kind); + var maker = factory.CreateTypeMaker(type, kind); + return maker.CreateGraphType(template).GraphType; } private IGraphField MakeGraphField(IGraphFieldTemplate fieldTemplate) { var testServer = new TestServerBuilder().Build(); - var maker = new GraphFieldMaker(testServer.Schema); + + var factory = testServer.CreateMakerFactory(); + + var maker = factory.CreateFieldMaker(); return maker.CreateField(fieldTemplate).Field; } @@ -263,6 +276,9 @@ public void InterfaceAndObjectTypeExtensions_Scenarios( int positionToAddGlazeField, int positiontoAddDoubleGlazeField) { + // Tests the various scenarios of when a type extension, an OBJECT graph type and an INTERFACE + // graph type may be registered to ensure that all graph types contain all extensions by the end + // of the inclusion process regardless of the order encountered for (var i = 1; i <= 7; i++) { if (positionToAddIPastry == i) diff --git a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTypeCollectionTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTypeCollectionTests.cs index c6caabcb3..f54175558 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTypeCollectionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/SchemaTypeCollectionTests.cs @@ -12,18 +12,24 @@ namespace GraphQL.AspNet.Tests.Schemas using System; using System.Collections.Generic; using System.Linq; - using GraphQL.AspNet.Engine.TypeMakers; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Interfaces.Internal; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Schemas.TypeSystem.TypeCollections; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Tests.Schemas.GraphTypeCollectionTestData; using GraphQL.AspNet.Tests.Schemas.SchemaTestData; using NUnit.Framework; using GraphQL.AspNet.Tests.Framework.Interfaces; + using GraphQL.AspNet.Schemas.Generation; + using GraphQL.AspNet.Schemas.Generation.TypeMakers; + using Microsoft.AspNetCore.Hosting.Server; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.Generation.TypeTemplates; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Common.Interfaces; + using GraphQL.AspNet.Tests.CommonHelpers; [TestFixture] public class SchemaTypeCollectionTests @@ -39,23 +45,20 @@ private IGraphType MakeGraphType(Type type, TypeKind kind) { var testServer = new TestServerBuilder().Build(); - switch (kind) - { - case TypeKind.UNION: - var proxy = GraphQLProviders.GraphTypeMakerProvider.CreateUnionProxyFromType(type); - var unionMaker = GraphQLProviders.GraphTypeMakerProvider.CreateUnionMaker(testServer.Schema); - return unionMaker.CreateUnionFromProxy(proxy).GraphType; - - default: - var maker = GraphQLProviders.GraphTypeMakerProvider.CreateTypeMaker(testServer.Schema, kind); - return maker.CreateGraphType(type).GraphType; - } + var factory = testServer.CreateMakerFactory(); + + var template = GraphQLTemplateHelper.CreateGraphTypeTemplate(type, kind); + var typeMaker = factory.CreateTypeMaker(type, kind); + var graphType = typeMaker.CreateGraphType(template).GraphType; + + return graphType; } private IGraphField MakeGraphField(IGraphFieldTemplate fieldTemplate) { var testServer = new TestServerBuilder().Build(); - var maker = new GraphFieldMaker(testServer.Schema); + + var maker = new GraphFieldMaker(testServer.Schema, new GraphArgumentMaker(testServer.Schema)); return maker.CreateField(fieldTemplate).Field; } @@ -220,7 +223,10 @@ public void EnsureGraphType_OverloadedAddsForEqualValueScalars_Succeeds() public void QueueExtension_WhenAddedBeforeType_IsAssignedAfterTypeIsAdded() { var collection = new SchemaTypeCollection(); - var template = GraphQLProviders.TemplateProvider.ParseType(typeof(TypeExtensionController)) as IGraphControllerTemplate; + + var template = new GraphControllerTemplate(typeof(TypeExtensionController)); + template.Parse(); + template.ValidateOrThrow(); var graphType = this.MakeGraphType(typeof(TwoPropertyObject), TypeKind.OBJECT) as IObjectGraphType; @@ -247,7 +253,10 @@ public void QueueExtension_WhenAddedBeforeType_IsAssignedAfterTypeIsAdded() public void QueueExtension_WhenAddedAfterType_IsAddedToTheTypeAndNotQueued() { var collection = new SchemaTypeCollection(); - var template = GraphQLProviders.TemplateProvider.ParseType(typeof(TypeExtensionController)) as IGraphControllerTemplate; + + var template = new GraphControllerTemplate(typeof(TypeExtensionController)); + template.Parse(); + template.ValidateOrThrow(); var twoObjectGraphType = this.MakeGraphType(typeof(TwoPropertyObject), TypeKind.OBJECT) as IObjectGraphType; diff --git a/src/unit-tests/graphql-aspnet-tests/Security/MappedQuery_AuthorizationTests.cs b/src/unit-tests/graphql-aspnet-tests/Security/MappedQuery_AuthorizationTests.cs new file mode 100644 index 000000000..96ffe305b --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Security/MappedQuery_AuthorizationTests.cs @@ -0,0 +1,113 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Security +{ + using System.Runtime.CompilerServices; + using System.Threading.Tasks; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Framework; + using NUnit.Framework; + + [TestFixture] + public class MappedQuery_AuthorizationTests + { + [Test] + public async Task MappedQuery_AuthorizedUser_AccessAllowed() + { + var serverBuilder = new TestServerBuilder(); + serverBuilder.AddGraphQL(o => + { + o.MapQuery("/field1/field2", (int a, int b) => + { + return a + b; + }) + .RequireAuthorization("policy1"); + }); + + serverBuilder.Authorization.AddClaimPolicy("policy1", "claim1", "claimValue1"); + serverBuilder.UserContext.Authenticate(); + serverBuilder.UserContext.AddUserClaim("claim1", "claimValue1"); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field1 { field2(a: 5, b: 33 } } }"); + + var result = await server.RenderResult(builder); + CommonAssertions.AreEqualJsonStrings( + @"{ + ""data"": { + ""field1"": { + ""field2"" : 38 + } + } + }", + result); + } + + [Test] + public async Task MappedQuery_UnauthorizedUser_AccessDenied() + { + var serverBuilder = new TestServerBuilder(); + serverBuilder.AddGraphQL(o => + { + o.MapQuery("/field1/field2", (int a, int b) => + { + return a + b; + }) + .RequireAuthorization("policy1"); + }); + + serverBuilder.Authorization.AddClaimPolicy("policy1", "claim1", "claimValue1"); + serverBuilder.UserContext.Authenticate(); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field1 { field2(a: 5, b: 33 } } }"); + + var result = await server.ExecuteQuery(builder); + + Assert.AreEqual(1, result.Messages.Count); + Assert.AreEqual(GraphMessageSeverity.Critical, result.Messages[0].Severity); + Assert.AreEqual("ACCESS_DENIED", result.Messages[0].Code); + } + + [Test] + public async Task MappedQuery_UnauthenticatedUser_AccessDenied() + { + var serverBuilder = new TestServerBuilder(); + serverBuilder.AddGraphQL(o => + { + o.MapQuery("/field1/field2", (int a, int b) => + { + return a + b; + }) + .RequireAuthorization("policy1"); + }); + + serverBuilder.Authorization.AddClaimPolicy("policy1", "claim1", "claimValue1"); + serverBuilder.UserContext.Authenticate(); + + var server = serverBuilder.Build(); + + var builder = server.CreateQueryContextBuilder(); + builder.AddQueryText(@"query { field1 { field2(a: 5, b: 33 } } }"); + + var result = await server.ExecuteQuery(builder); + + Assert.AreEqual(1, result.Messages.Count); + Assert.AreEqual(GraphMessageSeverity.Critical, result.Messages[0].Severity); + Assert.AreEqual("ACCESS_DENIED", result.Messages[0].Code); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Security/PerFieldAuthorizationTests.cs b/src/unit-tests/graphql-aspnet-tests/Security/PerFieldAuthorizationTests.cs index 1d45d3ba3..8d10c3ded 100644 --- a/src/unit-tests/graphql-aspnet-tests/Security/PerFieldAuthorizationTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Security/PerFieldAuthorizationTests.cs @@ -13,8 +13,8 @@ namespace GraphQL.AspNet.Tests.Security using System.Threading.Tasks; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Security; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Tests.Security.SecurtyGroupData; using NUnit.Framework; diff --git a/src/unit-tests/graphql-aspnet-tests/Security/PerRequestAuthorizationTests.cs b/src/unit-tests/graphql-aspnet-tests/Security/PerRequestAuthorizationTests.cs index 43c4cbd55..fa5c5351d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Security/PerRequestAuthorizationTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Security/PerRequestAuthorizationTests.cs @@ -14,7 +14,7 @@ namespace GraphQL.AspNet.Tests.Security using GraphQL.AspNet.Security; using GraphQL.AspNet.Tests.Framework; using GraphQL.AspNet.Common.Extensions; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using NUnit.Framework; using GraphQL.AspNet.Tests.Security.SecurtyGroupData; diff --git a/src/unit-tests/graphql-aspnet-tests/Security/SecurityPipelineTests.cs b/src/unit-tests/graphql-aspnet-tests/Security/SecurityPipelineTests.cs index ee1c84100..044c47da4 100644 --- a/src/unit-tests/graphql-aspnet-tests/Security/SecurityPipelineTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Security/SecurityPipelineTests.cs @@ -58,7 +58,7 @@ public async Task RolePolicy_UserNotInRole_Fails() builder.UserContext.AddUserRole("role4"); var server = builder.Build(); - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(Controller_NoPolicies.RequireRolePolicy_RequiresRole1)); var authContext = fieldBuilder.CreateSecurityContext(); @@ -79,7 +79,7 @@ public async Task RolePolicy_UserInRole_Success() builder.UserContext.AddUserRole("role1"); var server = builder.Build(); - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(Controller_NoPolicies.RequireRolePolicy_RequiresRole1)); var authContext = fieldBuilder.CreateSecurityContext(); @@ -103,7 +103,7 @@ public async Task MultiPolicyCheck_UserPassesAll_Success() var server = builder.Build(); - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(Controller_NoPolicies.MultiPolicyMethod_RequireRole6_RequireClaim7)); var authContext = fieldBuilder.CreateSecurityContext(); @@ -130,7 +130,7 @@ public async Task MultiPolicyCheck_UserPassesOnly1_Fail() var server = builder.Build(); - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(Controller_NoPolicies.MultiPolicyMethod_RequireRole6_RequireClaim7)); var authContext = fieldBuilder.CreateSecurityContext(); @@ -150,7 +150,7 @@ public async Task DirectRoleCheck_UserDoesNotHaveRole_Fails() var server = builder.Build(); // policy name isnt declared on the controller method - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(Controller_NoPolicies.MethodHasRoles_Role5)); var authContext = fieldBuilder.CreateSecurityContext(); @@ -170,7 +170,7 @@ public async Task DirectRoleCheck_UserHasRole_Succeeds() var server = builder.Build(); // policy name isnt declared on the controller method - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(Controller_NoPolicies.MethodHasRoles_Role5)); var authContext = fieldBuilder.CreateSecurityContext(); @@ -191,7 +191,7 @@ public async Task ClaimsPolicy_UserDoesntHaveClaim_Fails() builder.UserContext.AddUserClaim("testClaim5", "testClaim5Value"); var server = builder.Build(); - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(Controller_NoPolicies.RequireClaimPolicy_RequiresTestClaim6)); var authContext = fieldBuilder.CreateSecurityContext(); @@ -212,7 +212,7 @@ public async Task ClaimsPolicy_UserDoesHaveClaim_Success() builder.UserContext.AddUserClaim("testClaim6", "testClaim6Value"); var server = builder.Build(); - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(Controller_NoPolicies.RequireClaimPolicy_RequiresTestClaim6)); var authContext = fieldBuilder.CreateSecurityContext(); @@ -233,7 +233,7 @@ public async Task ClaimsPolicy_UserDoesHaveClaim_ButWrongValue_Fail() builder.UserContext.AddUserClaim("testClaim6", "differentValueThanRequired"); var server = builder.Build(); - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(Controller_NoPolicies.RequireClaimPolicy_RequiresTestClaim6)); var authContext = fieldBuilder.CreateSecurityContext(); @@ -252,7 +252,7 @@ public async Task NoUserContext_Fails() var server = builder.Build(); // policy name isnt declared on the controller method - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(Controller_NoPolicies.GeneralSecureMethod)); fieldBuilder.AddSecurityContext(null); @@ -276,7 +276,7 @@ public async Task NoAuthService_Fails() var server = builder.Build(); // policy name isnt declared on the controller method - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(Controller_NoPolicies.GeneralSecureMethod)); var authContext = fieldBuilder.CreateSecurityContext(); @@ -301,7 +301,7 @@ public async Task NoAuthSerivce_ButNoDefinedRules_Skipped() var server = builder.Build(); // policy name isnt declared on the controller method - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(Controller_NoPolicies.NoDefinedPolicies)); var authContext = fieldBuilder.CreateSecurityContext(); @@ -320,7 +320,7 @@ public async Task NoUserContext_ButNoDefinedRules_Skipped() var server = builder.Build(); // policy name isnt declared on the controller method - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(Controller_NoPolicies.NoDefinedPolicies)); fieldBuilder.AddSecurityContext(null); @@ -343,7 +343,7 @@ public async Task AllowAnon_WhenUserDoesntPassChecks_Success() builder.UserContext.AddUserClaim("testClaim6", "testClaim6Value"); var server = builder.Build(); - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(Controller_NoPolicies.RequireClaimPolicy_RequiresTestClaim7_ButAlsoAllowAnon)); var authContext = fieldBuilder.CreateSecurityContext(); @@ -373,7 +373,7 @@ public async Task MultiSecurityGroup_PassesOuter_FailsInner_Fails() var server = builder.Build(); - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(Controller_Policy_RequiresPolicy5.Policy_RequiresRole1)); var authContext = fieldBuilder.CreateSecurityContext(); @@ -403,7 +403,7 @@ public async Task MultiSecurityGroup_PassesOuter_PassesInner_Success() var server = builder.Build(); - var fieldBuilder = server.CreateGraphTypeFieldContextBuilder( + var fieldBuilder = server.CreateFieldContextBuilder( nameof(Controller_Policy_RequiresPolicy5.Policy_RequiresRole1)); var authContext = fieldBuilder.CreateSecurityContext(); diff --git a/src/unit-tests/graphql-aspnet-tests/Security/SecurtyGroupData/PerFieldCounterController.cs b/src/unit-tests/graphql-aspnet-tests/Security/SecurtyGroupData/PerFieldCounterController.cs index 76a82ae51..e831107ca 100644 --- a/src/unit-tests/graphql-aspnet-tests/Security/SecurtyGroupData/PerFieldCounterController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Security/SecurtyGroupData/PerFieldCounterController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Security.SecurtyGroupData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using Microsoft.AspNetCore.Authorization; public class PerFieldCounterController : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/Security/SecurtyGroupData/PerRequestCounterController.cs b/src/unit-tests/graphql-aspnet-tests/Security/SecurtyGroupData/PerRequestCounterController.cs index a798cd019..5e0eccc5a 100644 --- a/src/unit-tests/graphql-aspnet-tests/Security/SecurtyGroupData/PerRequestCounterController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Security/SecurtyGroupData/PerRequestCounterController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Security.SecurtyGroupData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using Microsoft.AspNetCore.Authorization; public class PerRequestCounterController : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/Security/SecurtyGroupData/SecuredController.cs b/src/unit-tests/graphql-aspnet-tests/Security/SecurtyGroupData/SecuredController.cs index 196b61dc7..778f853a9 100644 --- a/src/unit-tests/graphql-aspnet-tests/Security/SecurtyGroupData/SecuredController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Security/SecurtyGroupData/SecuredController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Security.SecurtyGroupData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using Microsoft.AspNetCore.Authorization; public class SecuredController : GraphController diff --git a/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/BatchGraphQLHttpResponseWriterTests.cs b/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/BatchGraphQLHttpResponseWriterTests.cs index 0be25cdc7..7c18be196 100644 --- a/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/BatchGraphQLHttpResponseWriterTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/BatchGraphQLHttpResponseWriterTests.cs @@ -20,8 +20,8 @@ namespace GraphQL.AspNet.Tests.ServerExtensions.MutlipartRequests using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Schemas; using GraphQL.AspNet.ServerExtensions.MultipartRequests.Web; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using NSubstitute; diff --git a/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/ConfigTests.cs b/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/ConfigTests.cs index 5b6a8f6a3..4c436eaac 100644 --- a/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/ConfigTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/ConfigTests.cs @@ -14,6 +14,7 @@ namespace GraphQL.AspNet.Tests.ServerExtensions.MutlipartRequests using System.Text; using System.Threading.Tasks; using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Common.JsonNodes; using GraphQL.AspNet.Schemas; using GraphQL.AspNet.ServerExtensions.MultipartRequests; using GraphQL.AspNet.ServerExtensions.MultipartRequests.Configuration; diff --git a/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/FileUploadScalarGraphTypeTests.cs b/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/FileUploadScalarGraphTypeTests.cs index 39571ff88..46482f73c 100644 --- a/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/FileUploadScalarGraphTypeTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/FileUploadScalarGraphTypeTests.cs @@ -82,12 +82,5 @@ public void ScalarValueType_IsAnyType() Assert.IsTrue(scalar.ValueType.HasFlag(ScalarValueType.String)); Assert.IsTrue(scalar.ValueType.HasFlag(ScalarValueType.Boolean)); } - - [Test] - public void Scalar_HasNoOtherKnownTypes() - { - var scalar = new FileUploadScalarGraphType(); - Assert.AreEqual(0, scalar.OtherKnownTypes.Count); - } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/MultiPartRequestServerExtensionTests.cs b/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/MultiPartRequestServerExtensionTests.cs index 2092752be..0572d6296 100644 --- a/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/MultiPartRequestServerExtensionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/MultiPartRequestServerExtensionTests.cs @@ -10,7 +10,6 @@ namespace GraphQL.AspNet.Tests.ServerExtensions.MutlipartRequests { using System.Linq; - using System.Threading.Tasks; using GraphQL.AspNet.Configuration; using GraphQL.AspNet.Configuration.Exceptions; using GraphQL.AspNet.Engine; @@ -20,7 +19,6 @@ namespace GraphQL.AspNet.Tests.ServerExtensions.MutlipartRequests using GraphQL.AspNet.ServerExtensions.MultipartRequests.Engine; using GraphQL.AspNet.ServerExtensions.MultipartRequests.Model; using GraphQL.AspNet.ServerExtensions.MultipartRequests.Schema; - using GraphQL.AspNet.Tests.Execution.TestData.DirectiveProcessorTypeSystemLocationTestData; using GraphQL.AspNet.Tests.Framework; using GraphQL.AspNet.Tests.ServerExtensions.MutlipartRequests.TestData; using Microsoft.Extensions.DependencyInjection; @@ -33,9 +31,6 @@ public class MultiPartRequestServerExtensionTests [Test] public void DefaultUsage_DefaultProcessorIsRegistered() { - using var restorePoint = new GraphQLGlobalRestorePoint(); - GraphQLProviders.ScalarProvider = new DefaultScalarGraphTypeProvider(); - var collection = new ServiceCollection(); var options = new SchemaOptions(collection); @@ -47,25 +42,17 @@ public void DefaultUsage_DefaultProcessorIsRegistered() [Test] public void DefaultUsage_ScalarIsRegistered() { - using var restorePoint = new GraphQLGlobalRestorePoint(); - GraphQLProviders.ScalarProvider = new DefaultScalarGraphTypeProvider(); - var collection = new ServiceCollection(); var options = new SchemaOptions(collection); - Assert.IsFalse(GraphQLProviders.ScalarProvider.IsScalar(typeof(FileUpload))); - options.RegisterExtension(); - Assert.IsTrue(GraphQLProviders.ScalarProvider.IsScalar(typeof(FileUpload))); + Assert.IsTrue(options.SchemaTypesToRegister.Any(x => x.Type == typeof(FileUploadScalarGraphType))); } [Test] public void DefaultUsage_WhenProcessorIsAlreadyRegistered_ThrowsException() { - using var restorePoint = new GraphQLGlobalRestorePoint(); - GraphQLProviders.ScalarProvider = new DefaultScalarGraphTypeProvider(); - var collection = new ServiceCollection(); var options = new SchemaOptions(collection); options.QueryHandler.HttpProcessorType = typeof(DefaultGraphQLHttpProcessor); @@ -79,9 +66,6 @@ public void DefaultUsage_WhenProcessorIsAlreadyRegistered_ThrowsException() [Test] public void DefaultUsage_CustomProcessorIsChangedToSomethingNotCompatiable_ThrowsExceptionOnUsage() { - using var restorePoint = new GraphQLGlobalRestorePoint(); - GraphQLProviders.ScalarProvider = new DefaultScalarGraphTypeProvider(); - var collection = new ServiceCollection(); var options = new SchemaOptions(collection); @@ -102,8 +86,6 @@ public void DefaultUsage_CustomProcessorIsChangedToSomethingNotCompatiable_Throw [Test] public void DeclineDefaultProcessor_CustomProcessorIsSetManually_NoException() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); - var collection = new ServiceCollection(); var options = new SchemaOptions(collection); @@ -125,8 +107,6 @@ public void DeclineDefaultProcessor_CustomProcessorIsSetManually_NoException() [Test] public void UseExtension_WithNoProvider_DoesNothing() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); - var collection = new ServiceCollection(); var options = new SchemaOptions(collection); @@ -148,8 +128,6 @@ public void UseExtension_WithNoProvider_DoesNothing() [Test] public void UseExtension_CheckingForACompatiableProvider_NoConfigChanges_ProviderFirst_ThrowsException() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); - var extension = new MultipartRequestServerExtension((o) => { }); var serverBuilder = new TestServerBuilder(); @@ -171,8 +149,6 @@ public void UseExtension_CheckingForACompatiableProvider_NoConfigChanges_Provide [Test] public void UseExtension_CheckingForACompatiableProvider_NoConfigChanges_ProviderLast_IsValid() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); - var extension = new MultipartRequestServerExtension((o) => { }); var serverBuilder = new TestServerBuilder(); @@ -191,8 +167,6 @@ public void UseExtension_CheckingForACompatiableProvider_NoConfigChanges_Provide [Test] public void UseExtension_NotRegisteringProvider_CheckingForACompatiableProvider_ProviderLast_IsValid() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); - var extension = new MultipartRequestServerExtension((o) => { o.RegisterMultipartRequestHttpProcessor = false; @@ -216,8 +190,6 @@ public void UseExtension_NotRegisteringProvider_CheckingForACompatiableProvider_ [Test] public void UseExtension_NotRegisteringProvider_CheckingForACompatiableProvider_ProviderFirst_IsValid() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); - var extension = new MultipartRequestServerExtension((o) => { o.RegisterMultipartRequestHttpProcessor = false; diff --git a/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/MultipartRequestExtensionMethodsTests.cs b/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/MultipartRequestExtensionMethodsTests.cs index e0cb9360a..fcfc5d342 100644 --- a/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/MultipartRequestExtensionMethodsTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/MultipartRequestExtensionMethodsTests.cs @@ -14,8 +14,6 @@ namespace GraphQL.AspNet.Tests.ServerExtensions.MutlipartRequests using GraphQL.AspNet.Schemas; using GraphQL.AspNet.ServerExtensions.MultipartRequests; using GraphQL.AspNet.ServerExtensions.MultipartRequests.Interfaces; - using GraphQL.AspNet.ServerExtensions.MultipartRequests.Model; - using GraphQL.AspNet.Tests.Framework; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; @@ -28,8 +26,6 @@ public class SecondSchema : GraphSchema [Test] public void AddMultipartRequestSupport_DoesNotThrowExceptionWhenNotPassingAction() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); - var options = new SchemaOptions(new ServiceCollection()); options.AddMultipartRequestSupport(); } @@ -37,8 +33,6 @@ public void AddMultipartRequestSupport_DoesNotThrowExceptionWhenNotPassingAction [Test] public void AddMultipartRequestSupport_ProvidedActionMethodIsCalled() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); - var wasCalled = false; var collection = new ServiceCollection(); @@ -62,16 +56,12 @@ public void AddMultipartRequestSupport_ProvidedActionMethodIsCalled() .SingleOrDefault(x => x.ServiceType == typeof(IFileUploadScalarValueMaker))); - GraphQLProviders.ScalarProvider.IsScalar(typeof(FileUpload)); - Assert.IsTrue(wasCalled); } [Test] public void AddMultipartRequestSupport_OnMultipleSchemas_RegistersGlobalEntitiesOnce() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); - var wasCalled1 = false; var collection = new ServiceCollection(); @@ -112,8 +102,6 @@ public void AddMultipartRequestSupport_OnMultipleSchemas_RegistersGlobalEntities .SingleOrDefault(x => x.ServiceType == typeof(IFileUploadScalarValueMaker))); - GraphQLProviders.ScalarProvider.IsScalar(typeof(FileUpload)); - Assert.IsTrue(wasCalled2); } } diff --git a/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/MultipartRequestGraphQLHttpProcessorTests.cs b/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/MultipartRequestGraphQLHttpProcessorTests.cs index ce0d1686a..56c55c086 100644 --- a/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/MultipartRequestGraphQLHttpProcessorTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/MultipartRequestGraphQLHttpProcessorTests.cs @@ -28,8 +28,8 @@ namespace GraphQL.AspNet.Tests.ServerExtensions.MutlipartRequests using GraphQL.AspNet.ServerExtensions.MultipartRequests.Interfaces; using GraphQL.AspNet.ServerExtensions.MultipartRequests.Schema; using GraphQL.AspNet.ServerExtensions.MultipartRequests.Web; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Tests.ServerExtensions.MutlipartRequests.TestData; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; @@ -93,10 +93,10 @@ public class MultipartRequestGraphQLHttpProcessorTests var builder = new TestServerBuilder(); - GraphQLProviders.ScalarProvider.RegisterCustomScalar(typeof(FileUploadScalarGraphType)); builder.AddSingleton(); builder.AddGraphQL(o => { + o.AddType(); o.AddController(); o.ResponseOptions.TimeStampLocalizer = (d) => _staticFailDate; }); @@ -130,8 +130,6 @@ private string ExtractResponseString(HttpContext context) [Test] public async Task NotAMultiPartRequest_ParsesAsNormal() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); - var (context, processor) = this.CreateTestObjects(); var _options = new JsonSerializerOptions(); @@ -172,8 +170,6 @@ public async Task NotAMultiPartRequest_ParsesAsNormal() [Test] public async Task NonBatchedQuery_NoFiles_ReturnsStandardResult() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); - var (context, processor) = this.CreateTestObjects( fields: new[] { @@ -207,8 +203,6 @@ private string PrepQuery(string queryText) [Test] public async Task NonBatchedQuery_SingleFile_ReturnsStandardResult() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); - var query1 = @" query($inboundFile: Upload!) { @@ -255,8 +249,6 @@ public async Task NonBatchedQuery_SingleFile_ReturnsStandardResult() [Test] public async Task NonBatchedQuery_SingleFile_InvalidMapField_Errors() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); - var query1 = @" query($inboundFile: Upload!) { @@ -292,7 +284,6 @@ public async Task NonBatchedQuery_SingleFile_InvalidMapField_Errors() [Test] public async Task InvalidOperationsJson_HandlesParsingException() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); var (context, processor) = this.CreateTestObjects( fields: new[] { @@ -309,7 +300,6 @@ public async Task InvalidOperationsJson_HandlesParsingException() [Test] public async Task NoQueriesOnBatch_HandlesParsingException() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); var (context, processor) = this.CreateTestObjects( fields: new[] { @@ -323,8 +313,6 @@ public async Task NoQueriesOnBatch_HandlesParsingException() [Test] public async Task MultiPartForm_NoFiles_ReturnsStandardResult() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); - var query1 = @" query { @@ -383,8 +371,6 @@ public async Task MultiPartForm_NoFiles_ReturnsStandardResult() [Test] public async Task RuntimeThrowsException_CustomResultIsGenerated() { - using var restorePoint = new GraphQLGlobalRestorePoint(true); - var runtime = Substitute.For>(); runtime.ExecuteRequestAsync( Arg.Any(), diff --git a/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/TestData/MultiPartFileController.cs b/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/TestData/MultiPartFileController.cs index cb3ef7dec..7e177e26b 100644 --- a/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/TestData/MultiPartFileController.cs +++ b/src/unit-tests/graphql-aspnet-tests/ServerExtensions/MutlipartRequests/TestData/MultiPartFileController.cs @@ -15,7 +15,7 @@ namespace GraphQL.AspNet.Tests.ServerExtensions.MutlipartRequests.TestData using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; using GraphQL.AspNet.ServerExtensions.MultipartRequests.Model; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class MultiPartFileController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Web/CancelTokenTests.cs b/src/unit-tests/graphql-aspnet-tests/Web/CancelTokenTests.cs index afdbdc241..fd90ee043 100644 --- a/src/unit-tests/graphql-aspnet-tests/Web/CancelTokenTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Web/CancelTokenTests.cs @@ -18,8 +18,8 @@ namespace GraphQL.AspNet.Tests.Web using System.Threading.Tasks; using GraphQL.AspNet.Engine; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Tests.Web.CancelTokenTestData; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; diff --git a/src/unit-tests/graphql-aspnet-tests/Web/GetRequestTests.cs b/src/unit-tests/graphql-aspnet-tests/Web/GetRequestTests.cs index 81a0d9cac..d521893d9 100644 --- a/src/unit-tests/graphql-aspnet-tests/Web/GetRequestTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Web/GetRequestTests.cs @@ -17,8 +17,8 @@ namespace GraphQL.AspNet.Tests.Web using System.Web; using GraphQL.AspNet.Engine; using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using GraphQL.AspNet.Tests.Web.CancelTokenTestData; using GraphQL.AspNet.Tests.Web.WebTestData; using Microsoft.AspNetCore.Http; diff --git a/src/unit-tests/graphql-aspnet-tests/Web/HttpContextInjectionTests.cs b/src/unit-tests/graphql-aspnet-tests/Web/HttpContextInjectionTests.cs new file mode 100644 index 000000000..83dff0e1d --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Web/HttpContextInjectionTests.cs @@ -0,0 +1,88 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Web +{ + using System.IO; + using System.Threading.Tasks; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Engine; + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Framework; + using GraphQL.AspNet.Tests.Web.CancelTokenTestData; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + using NUnit.Framework; + + [TestFixture] + public class HttpContextInjectionTests + { + [Test] + public async Task WhenHttpContextIsFound_ItsInjectedAsExpected() + { + DefaultHttpContext httpContext = null; + var serverBuilder = new TestServerBuilder(); + serverBuilder.AddGraphController(); + serverBuilder.AddTransient>(); + serverBuilder.AddGraphQL((o) => + { + o.ExecutionOptions.QueryTimeout = null; + o.MapQuery("field", (HttpContext context) => + { + if (context != null + && context.Items.ContainsKey("Test1") + && context.Items["Test1"].ToString() == "Value1") + { + return 1; + } + + return 0; + }); + }); + + var server = serverBuilder.Build(); + + using var scope = server.ServiceProvider.CreateScope(); + var processor = scope.ServiceProvider.GetRequiredService>(); + + httpContext = new DefaultHttpContext() + { + Response = + { + Body = new MemoryStream(), + }, + RequestServices = scope.ServiceProvider, + }; + + var request = httpContext.Request; + request.Method = "GET"; + request.QueryString = new QueryString("?query=query { field }"); + + httpContext.Items.Add("Test1", "Value1"); + + await processor.InvokeAsync(httpContext); + await httpContext.Response.Body.FlushAsync(); + + httpContext.Response.Body.Seek(0, SeekOrigin.Begin); + var reader = new StreamReader(httpContext.Response.Body); + var text = reader.ReadToEnd(); + + var expectedResult = @" + { + ""data"" : { + ""field"" : 1 + } + }"; + + CommonAssertions.AreEqualJsonStrings(expectedResult, text); + Assert.AreEqual(200, httpContext.Response.StatusCode); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Web/WebTestData/ExternalItemCollectionController.cs b/src/unit-tests/graphql-aspnet-tests/Web/WebTestData/ExternalItemCollectionController.cs index 9a624e60d..65f0f0069 100644 --- a/src/unit-tests/graphql-aspnet-tests/Web/WebTestData/ExternalItemCollectionController.cs +++ b/src/unit-tests/graphql-aspnet-tests/Web/WebTestData/ExternalItemCollectionController.cs @@ -11,7 +11,7 @@ namespace GraphQL.AspNet.Tests.Web.WebTestData { using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ExternalItemCollectionController : GraphController { diff --git a/src/unit-tests/graphql-aspnet-tests/Web/WebTestData/ItemInjectorHttpProcessor.cs b/src/unit-tests/graphql-aspnet-tests/Web/WebTestData/ItemInjectorHttpProcessor.cs index 919fe985a..2eb09ab90 100644 --- a/src/unit-tests/graphql-aspnet-tests/Web/WebTestData/ItemInjectorHttpProcessor.cs +++ b/src/unit-tests/graphql-aspnet-tests/Web/WebTestData/ItemInjectorHttpProcessor.cs @@ -17,7 +17,7 @@ namespace GraphQL.AspNet.Tests.Web.WebTestData using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Logging; using GraphQL.AspNet.Schemas; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Tests.Common.CommonHelpers; public class ItemInjectorHttpProcessor : DefaultGraphQLHttpProcessor {