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/build/strict-linting.ruleset b/build/strict-linting.ruleset index 2dd07c6a0..4e28fd6d6 100644 --- a/build/strict-linting.ruleset +++ b/build/strict-linting.ruleset @@ -84,6 +84,7 @@ + diff --git a/src/CodeMaid.config b/src/CodeMaid.config index d11f56182..5aec956ac 100644 --- a/src/CodeMaid.config +++ b/src/CodeMaid.config @@ -80,6 +80,10 @@ False + + True + \ No newline at end of file diff --git a/src/ancillary-projects/starwars/starwars-api70/Program.cs b/src/ancillary-projects/starwars/starwars-api70/Program.cs deleted file mode 100644 index ddd499734..000000000 --- a/src/ancillary-projects/starwars/starwars-api70/Program.cs +++ /dev/null @@ -1,29 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.StarWarsAPI7X -{ - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.Hosting; - - public static class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} \ No newline at end of file diff --git a/src/ancillary-projects/starwars/starwars-api70/Properties/launchSettings.json b/src/ancillary-projects/starwars/starwars-api70/Properties/launchSettings.json deleted file mode 100644 index 530c470fd..000000000 --- a/src/ancillary-projects/starwars/starwars-api70/Properties/launchSettings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "profiles": { - "StarWars: .NET 7": { - "commandName": "Project", - "applicationUrl": "http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/src/ancillary-projects/starwars/starwars-api70/Startup.cs b/src/ancillary-projects/starwars/starwars-api70/Startup.cs deleted file mode 100644 index 623c735d9..000000000 --- a/src/ancillary-projects/starwars/starwars-api70/Startup.cs +++ /dev/null @@ -1,174 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.StarWarsAPI7X -{ - using System; - using GraphQL.AspNet; - using GraphQL.AspNet.Configuration; - using GraphQL.AspNet.Execution; - using GraphQL.AspNet.StarwarsAPI.Common.Services; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.WebSockets; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.DependencyInjection; - - public class Startup - { - private const string ALL_ORIGINS_POLICY = "_allOrigins"; - - private static readonly TimeSpan SOCKET_CONNECTION_KEEPALIVE = TimeSpan.FromSeconds(10); - - /// - /// Initializes a new instance of the class. - /// - /// The configuration created to govern the - /// application environment. - public Startup(IConfiguration configuration) - { - this.Configuration = configuration; - } - - /// - /// Configures the service collection to be built for this application instance. - /// - /// The services. - public void ConfigureServices(IServiceCollection services) - { - services.AddSingleton(); - services.AddScoped(); - - // apply an unrestricted cors policy for the demo services - // to allow use on many of the tools for testing (graphiql, altair etc.) - // Do not do this in production - services.AddCors(options => - { - options.AddPolicy( - ALL_ORIGINS_POLICY, - builder => - { - builder.AllowAnyOrigin() - .AllowAnyHeader() - .AllowAnyMethod(); - }); - }); - - // ASP.NET websockets implementation must also be added to the runtime - services.AddWebSockets((options) => - { - // here add some common origins of various tools that may be - // used for running this demo - // do not add these in a production app unless you need - // to - options.AllowedOrigins.Add("http://localhost:5000"); - options.AllowedOrigins.Add("http://localhost:4000"); - options.AllowedOrigins.Add("http://localhost:3000"); - options.AllowedOrigins.Add("null"); - options.AllowedOrigins.Add("file://"); - options.AllowedOrigins.Add("ws://"); - - }); - - // ---------------------------------------------------------- - // Register GraphQL with the application - // ---------------------------------------------------------- - // By default graphql will scan your assembly for any GraphControllers - // and automatically wire them up to the schema - // you can control which assemblies are scanned and which classes are registered using - // the schema configuration options set here. - // - // in this example because of the two test projects (netcore3.1 and net5.0) - // we have moved all the shared code to a common assembly (starwars-common) and are injecting it - // as a single unit - // - // we then add subscription services to the schema builder returned from .AddGraphQL() - services.AddGraphQL(options => - { - options.ResponseOptions.ExposeExceptions = true; - options.ResponseOptions.MessageSeverityLevel = GraphMessageSeverity.Information; - - // options.ExecutionOptions.EnableMetrics = true; - // options.ResponseOptions.ExposeMetrics = true; - - var assembly = typeof(StarWarsDataRepository).Assembly; - options.AddAssembly(assembly); - }) - .AddSubscriptions(options => - { - // this route path is set by default - // it is listed here just as a matter of example - options.Route = SubscriptionConstants.Routing.DEFAULT_SUBSCRIPTIONS_ROUTE; - - // for some web based graphql tools such as graphiql and graphql-playground - // the default keep-alive timeout of 2 minutes is too long. - // - // still others (like graphql-playground running in electron) do not respond/configure - // for socket-level ping/pong frames to allow for socket-level keep alives - // - // here we set this demo project websocket keep-alive (at the server level) - // to be below all those thresholds to ensure a hassle free experience. - // In practice, you should configure your server (both subscription keep alives and socket keep alives) - // with an interval that is compatiable with your client side environment. - options.ConnectionKeepAliveInterval = SOCKET_CONNECTION_KEEPALIVE; - }); - - // if you have rest controllers this item be sure they are included. - // Graphql and rest can live side by side in the same project without issue - // -------------------------------------------------- - // builder.Services.AddControllers(); - } - - /// - /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - /// - /// The asp.net application builder. - /// The configured host environment. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - app.AddStarWarsStartedMessageToConsole(); - - app.UseRouting(); - - app.UseCors(ALL_ORIGINS_POLICY); - - app.UseAuthorization(); - - // enable web sockets on this server instance - // this must be done before a call to 'UseGraphQL' if subscriptions are enabled for any - // schema otherwise the subscriptions may not register correctly - app.UseWebSockets(); - - // if you have no rest controllers this item can be safely skipped - // graphql and rest can live side by side in the same project without issue - // app.UseEndpoints(endpoints => - // { - // endpoints.MapControllers(); - // }); - - // ************************************************************ - // Finalize the graphql setup: - // 1) Loading the schema - // 2) Publish the route to hook the graphql runtime to ASP.NET. - // - // Be sure to register it after "UseAuthorization" if you need it. - // - // If the construction of your runtime schema has any errors they will be thrown here - // before your application starts listening for requests. - // ************************************************************ - app.UseGraphQL(); - } - - /// - /// Gets the environment configuration for this instance. - /// - /// The configuration item. - public IConfiguration Configuration { get; } - } -} \ No newline at end of file diff --git a/src/ancillary-projects/starwars/starwars-api70/appsettings.json b/src/ancillary-projects/starwars/starwars-api70/appsettings.json deleted file mode 100644 index 289add7ec..000000000 --- a/src/ancillary-projects/starwars/starwars-api70/appsettings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Information", - "Microsoft.AspNetCore.Hosting.Internal.WebHost": "Debug", - "Microsoft.AspNetCore.Hosting.Diagnostics": "Debug", - "GraphQL.AspNet": "Warning" - } - }, - "AllowedHosts": "*" -} diff --git a/src/ancillary-projects/starwars/starwars-api70/starwars-api70.csproj b/src/ancillary-projects/starwars/starwars-api70/starwars-api70.csproj deleted file mode 100644 index a9df881c7..000000000 --- a/src/ancillary-projects/starwars/starwars-api70/starwars-api70.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - net7.0 - latest - $(NoWarn);1701;1702;1705;1591;NU1603 - GraphQL.AspNet.StarWarsAPI7X - GraphQL.AspNet.StarwarsAPI7X - true - - - - ..\..\..\styles.ruleset - - - - - - - - - - - - - diff --git a/src/ancillary-projects/starwars/starwars-api80/Program.cs b/src/ancillary-projects/starwars/starwars-api80/Program.cs index c04447664..ddea25ab8 100644 --- a/src/ancillary-projects/starwars/starwars-api80/Program.cs +++ b/src/ancillary-projects/starwars/starwars-api80/Program.cs @@ -1,4 +1,4 @@ -// ************************************************************* +// ************************************************************* // project: graphql-aspnet // -- // repo: https://github.com/graphql-aspnet 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..93c4d67e5 100644 --- a/src/graphql-aspnet-subscriptions/Attributes/SubscriptionAttribute.cs +++ b/src/graphql-aspnet-subscriptions/Attributes/SubscriptionAttribute.cs @@ -69,7 +69,7 @@ public SubscriptionAttribute(Type returnType, params Type[] additionalTypes) /// The type of the data object returned from this method. If this type implements /// this field will be declared as returning the union defined by the type. public SubscriptionAttribute(string template, Type returnType) - : base(false, SchemaItemCollections.Subscription, template, returnType) + : base(false, ItemPathRoots.Subscription, template, returnType) { } @@ -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, ItemPathRoots.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, + ItemPathRoots.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..ac9db7af1 100644 --- a/src/graphql-aspnet-subscriptions/Attributes/SubscriptionRootAttribute.cs +++ b/src/graphql-aspnet-subscriptions/Attributes/SubscriptionRootAttribute.cs @@ -11,7 +11,6 @@ namespace GraphQL.AspNet.Attributes { using System; using System.Linq; - using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Interfaces.Schema; @@ -68,7 +67,7 @@ public SubscriptionRootAttribute(Type returnType, params Type[] additionalTypes) /// The type of the object returned from this method. If this type implements /// this field will be declared as returning the union defined by the type. public SubscriptionRootAttribute(string template, Type returnType) - : base(true, SchemaItemCollections.Subscription, template, returnType) + : base(true, ItemPathRoots.Subscription, template, returnType) { this.EventName = null; } @@ -82,7 +81,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, ItemPathRoots.Subscription, template, (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray()) { this.EventName = null; } @@ -92,16 +91,15 @@ 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. + /// The first 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 firstUnionType, params Type[] additionalUnionTypes) : base( true, - SchemaItemCollections.Subscription, + ItemPathRoots.Subscription, template, unionTypeName, - unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).ToArray()) + (new Type[] { firstUnionType }).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/Interfaces/Subscriptions/ISubscription.cs b/src/graphql-aspnet-subscriptions/Interfaces/Subscriptions/ISubscription.cs index 49565ee7b..b0fd3fff0 100644 --- a/src/graphql-aspnet-subscriptions/Interfaces/Subscriptions/ISubscription.cs +++ b/src/graphql-aspnet-subscriptions/Interfaces/Subscriptions/ISubscription.cs @@ -32,10 +32,10 @@ public interface ISubscription ISubscriptionGraphField Field { get; } /// - /// Gets the unique route within a schema this subscription is pointed at. + /// Gets the unique path within a schema this subscription is pointed at. /// /// The route. - SchemaItemPath Route { get; } + ItemPath ItemPath { get; } /// /// Gets the original query data object that generated this subscription. diff --git a/src/graphql-aspnet-subscriptions/Logging/SubscriptionEvents/ClientProxySubscriptionCreatedLogEntry.cs b/src/graphql-aspnet-subscriptions/Logging/SubscriptionEvents/ClientProxySubscriptionCreatedLogEntry.cs index a607717ab..d7307763d 100644 --- a/src/graphql-aspnet-subscriptions/Logging/SubscriptionEvents/ClientProxySubscriptionCreatedLogEntry.cs +++ b/src/graphql-aspnet-subscriptions/Logging/SubscriptionEvents/ClientProxySubscriptionCreatedLogEntry.cs @@ -28,7 +28,7 @@ public ClientProxySubscriptionCreatedLogEntry(ISubscriptionClientProxy client, I { this.ClientId = client?.Id.ToString(); this.SubscriptionId = subscription?.Id; - this.SubscriptionPath = subscription?.Route?.Path; + this.SubscriptionPath = subscription?.ItemPath?.Path; } /// diff --git a/src/graphql-aspnet-subscriptions/Logging/SubscriptionEvents/ClientProxySubscriptionEventReceived.cs b/src/graphql-aspnet-subscriptions/Logging/SubscriptionEvents/ClientProxySubscriptionEventReceived.cs index 8d735f29d..a3e4e0d38 100644 --- a/src/graphql-aspnet-subscriptions/Logging/SubscriptionEvents/ClientProxySubscriptionEventReceived.cs +++ b/src/graphql-aspnet-subscriptions/Logging/SubscriptionEvents/ClientProxySubscriptionEventReceived.cs @@ -34,7 +34,7 @@ public class ClientProxySubscriptionEventReceived : GraphLogEntry /// that will receive the event. public ClientProxySubscriptionEventReceived( ISubscriptionClientProxy client, - SchemaItemPath fieldPath, + ItemPath fieldPath, IReadOnlyList subscriptionsToReceive) : base(SubscriptionLogEventIds.ClientSubscriptionEventRecieved) { diff --git a/src/graphql-aspnet-subscriptions/Logging/SubscriptionEvents/ClientProxySubscriptionStoppedLogEntry.cs b/src/graphql-aspnet-subscriptions/Logging/SubscriptionEvents/ClientProxySubscriptionStoppedLogEntry.cs index 99c28bde4..780bff344 100644 --- a/src/graphql-aspnet-subscriptions/Logging/SubscriptionEvents/ClientProxySubscriptionStoppedLogEntry.cs +++ b/src/graphql-aspnet-subscriptions/Logging/SubscriptionEvents/ClientProxySubscriptionStoppedLogEntry.cs @@ -27,7 +27,7 @@ public ClientProxySubscriptionStoppedLogEntry(ISubscriptionClientProxy client, I { this.ClientId = client?.Id.ToString(); this.SubscriptionId = subscription?.Id; - this.SubscriptionPath = subscription?.Route?.Path; + this.SubscriptionPath = subscription?.ItemPath?.Path; } /// diff --git a/src/graphql-aspnet-subscriptions/Logging/SubscriptionLoggerExtensions.cs b/src/graphql-aspnet-subscriptions/Logging/SubscriptionLoggerExtensions.cs index 545a5e504..69e89c8bf 100644 --- a/src/graphql-aspnet-subscriptions/Logging/SubscriptionLoggerExtensions.cs +++ b/src/graphql-aspnet-subscriptions/Logging/SubscriptionLoggerExtensions.cs @@ -30,7 +30,7 @@ public static class SubscriptionLoggerExtensions /// The type of the schema the route was registered for. /// The logger. /// The relative route path (e.g. '/graphql'). - public static void SchemaSubscriptionRouteRegistered( + public static void SchemaSubscriptionUrlRouteRegistered( this IGraphEventLogger logger, string routePath) where TSchema : class, ISchema @@ -215,7 +215,7 @@ public static void ClientProxySubscriptionCreated(this IGraphEventLogger eventLo /// The client proxy recording the event. /// The field path of the event that was received. /// The subscriptions set to receive the event. - public static void ClientProxySubscriptionEventReceived(this IGraphEventLogger eventLogger, ISubscriptionClientProxy clientProxy, SchemaItemPath fieldPath, IReadOnlyList subscriptionsToReceive) + public static void ClientProxySubscriptionEventReceived(this IGraphEventLogger eventLogger, ISubscriptionClientProxy clientProxy, ItemPath fieldPath, IReadOnlyList subscriptionsToReceive) where TSchema : class, ISchema { eventLogger?.Log( 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..539b7783a --- /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, + ItemPathRoots.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..853e54a5f --- /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.ItemPath, + 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, + ItemPath route, + string eventName) + : base(schemaOptions, route) + { + this.EventName = eventName?.Trim(); + } + + /// + protected override Attribute CreatePrimaryAttribute() + { + var (collection, path) = this.ItemPath; + if (collection == ItemPathRoots.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.ItemPath; + + // 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..f60f89963 --- /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?.ItemPath.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 79% rename from src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionControllerActionGraphFieldTemplate.cs rename to src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionControllerActionGraphFieldTemplate.cs index 10d854b22..fef2347c7 100644 --- a/src/graphql-aspnet-subscriptions/Internal/TypeTemplates/SubscriptionControllerActionGraphFieldTemplate.cs +++ b/src/graphql-aspnet-subscriptions/Schemas/Generation/TypeTemplates/SubscriptionControllerActionGraphFieldTemplate.cs @@ -7,18 +7,19 @@ // 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; + using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Schemas.TypeSystem; /// /// Describes an subscription action on a , that can be registered @@ -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(ParameterModifiers.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,9 +135,9 @@ 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) + if (this.ItemPath.Root == Execution.ItemPathRoots.Subscription) { return new SubscriptionGraphArgumentTemplate( this, @@ -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 61% rename from src/graphql-aspnet-subscriptions/Engine/TypeMakers/SubscriptionEnabledGraphFieldMaker.cs rename to src/graphql-aspnet-subscriptions/Schemas/TypeMakers/SubscriptionEnabledGraphFieldMaker.cs index 2456c393f..6fce303e5 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,33 +27,35 @@ 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( - GraphNameFormatter formatter, - IGraphFieldTemplate template, - List securityGroups) + protected override MethodGraphField InstantiateField(IGraphFieldTemplate template, List securityGroups) { var subTemplate = template as SubscriptionControllerActionGraphFieldTemplate; if (subTemplate != null && subTemplate.FieldSource == GraphFieldSource.Action - && subTemplate.Route.RootCollection == SchemaItemCollections.Subscription) + && subTemplate.ItemPath.Root == ItemPathRoots.Subscription) { var directives = template.CreateAppliedDirectives(); + var schemaTypeName = this.PrepareTypeName(template); + var typeExpression = template.TypeExpression.Clone(schemaTypeName); + return new SubscriptionMethodGraphField( - formatter.FormatFieldName(template.Name), - template.TypeExpression.CloneTo(formatter.FormatGraphTypeName(template.TypeExpression.TypeName)), - template.Route, - template.ObjectType, + template.Name, + typeExpression, + template.ItemPath, + template.InternalName, template.DeclaredReturnType, + template.ObjectType, template.Mode, template.CreateResolver(), securityGroups, @@ -59,7 +63,7 @@ protected override MethodGraphField InstantiateField( directives); } - return base.InstantiateField(formatter, template, securityGroups); + return base.InstantiateField(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..ba73569e9 100644 --- a/src/graphql-aspnet-subscriptions/Schemas/TypeSystem/SubscriptionMethodGraphField.cs +++ b/src/graphql-aspnet-subscriptions/Schemas/TypeSystem/SubscriptionMethodGraphField.cs @@ -26,39 +26,43 @@ 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 .NET type of the item or items that represent the graph type returned by this field. + /// The formal path 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 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 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, - Type objectType = null, + ItemPath itemPath, + string internalFullName, Type declaredReturnType = null, + Type objectType = null, Execution.FieldResolutionMode mode = Execution.FieldResolutionMode.PerSourceItem, Interfaces.Execution.IGraphFieldResolver resolver = null, IEnumerable securityPolicies = null, string eventName = null, IAppliedDirectiveCollection directives = null) - : base(fieldName, typeExpression, route, objectType, declaredReturnType, mode, resolver, securityPolicies, directives) + : base(fieldName, internalFullName, typeExpression, itemPath, declaredReturnType, objectType, mode, resolver, securityPolicies, directives) { this.EventName = eventName; } /// - protected override MethodGraphField CreateNewInstance(IGraphType parent) + protected override MethodGraphField CreateNewInstance() { return new SubscriptionMethodGraphField( this.Name, this.TypeExpression.Clone(), - parent.Route.CreateChild(this.Name), - this.ObjectType, + this.ItemPath.Clone(), + this.InternalName, this.DeclaredReturnType, + this.ObjectType, this.Mode, this.Resolver, this.SecurityGroups, diff --git a/src/graphql-aspnet-subscriptions/SubscriptionServer/ClientSubscription.cs b/src/graphql-aspnet-subscriptions/SubscriptionServer/ClientSubscription.cs index 68c13fab3..a0ed3e9bb 100644 --- a/src/graphql-aspnet-subscriptions/SubscriptionServer/ClientSubscription.cs +++ b/src/graphql-aspnet-subscriptions/SubscriptionServer/ClientSubscription.cs @@ -115,7 +115,7 @@ public ClientSubscription( /// Gets the unique route within a schema this subscription is pointed at. /// /// The route. - public SchemaItemPath Route => this.Field?.Route; + public ItemPath ItemPath => this.Field?.ItemPath; /// /// Gets a value indicating whether this subscription has been properly configured. diff --git a/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionClientProxyBase{TSchema,TMessage}.cs b/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionClientProxyBase{TSchema,TMessage}.cs index 76b778c97..8bbb8808d 100644 --- a/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionClientProxyBase{TSchema,TMessage}.cs +++ b/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionClientProxyBase{TSchema,TMessage}.cs @@ -296,7 +296,7 @@ public async ValueTask ReceiveEventAsync(SubscriptionEvent eventData, Cancellati // find the subscriptions that are registered for the received data // its possible a client discontinued after the data was dispatched // but before the client processed...just stop if this is the case - var targetSubscriptions = _subscriptions.RetreiveByRoute(field); + var targetSubscriptions = _subscriptions.RetreiveByItemPath(field); if (targetSubscriptions.Count == 0) return; diff --git a/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionCollection.cs b/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionCollection.cs index a497dce59..7af7e1098 100644 --- a/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionCollection.cs +++ b/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionCollection.cs @@ -26,7 +26,7 @@ public class SubscriptionCollection : IReadOnlyDictionary> _subsById; - private readonly Dictionary>> _subsByRoute; + private readonly Dictionary>> _subsByRoute; /// /// Initializes a new instance of the class. @@ -34,7 +34,7 @@ public class SubscriptionCollection : IReadOnlyDictionary>(); - _subsByRoute = new Dictionary>>(SchemaItemPathComparer.Instance); + _subsByRoute = new Dictionary>>(ItemPathComparer.Instance); } /// @@ -57,12 +57,12 @@ public int Add(ISubscription subscription) _subsById.Add(subscription.Id, subscription); - if (!_subsByRoute.ContainsKey(subscription.Route)) - _subsByRoute.Add(subscription.Route, new HashSet>()); + if (!_subsByRoute.ContainsKey(subscription.ItemPath)) + _subsByRoute.Add(subscription.ItemPath, new HashSet>()); - _subsByRoute[subscription.Route].Add(subscription); + _subsByRoute[subscription.ItemPath].Add(subscription); - return _subsByRoute[subscription.Route].Count; + return _subsByRoute[subscription.ItemPath].Count; } } @@ -91,15 +91,15 @@ public int Remove(string subscriptionId, out ISubscription removedSub) _subsById.Remove(subscriptionId); } - if (removedSub != null && _subsByRoute.ContainsKey(removedSub.Route)) + if (removedSub != null && _subsByRoute.ContainsKey(removedSub.ItemPath)) { - var hashSet = _subsByRoute[removedSub.Route]; + var hashSet = _subsByRoute[removedSub.ItemPath]; if (hashSet.Contains(removedSub)) hashSet.Remove(removedSub); remaining = hashSet.Count; if (remaining == 0) - _subsByRoute.Remove(removedSub.Route); + _subsByRoute.Remove(removedSub.ItemPath); } } @@ -109,37 +109,37 @@ public int Remove(string subscriptionId, out ISubscription removedSub) /// /// Retrieves the total number of subscriptions registered for the given unique route. /// - /// The route to filter by. + /// The path to filter by. /// System.Int32. - public int CountByRoute(SchemaItemPath route) + public int CountByPath(ItemPath itemPath) { - if (route == null || !route.IsValid) + if (itemPath == null || !itemPath.IsValid) return 0; lock (_syncLock) { - if (!_subsByRoute.ContainsKey(route)) + if (!_subsByRoute.ContainsKey(itemPath)) return 0; - return _subsByRoute[route].Count; + return _subsByRoute[itemPath].Count; } } /// /// Finds the set all known subscriptions for a given route and returns them. /// - /// The route. + /// The route. /// IEnumerable<ISubscription<TSchema>>. - public IReadOnlyList> RetreiveByRoute(SchemaItemPath route) + public IReadOnlyList> RetreiveByItemPath(ItemPath itemPath) { List> subs = new List>(); - if (route != null) + if (itemPath != null) { lock (_syncLock) { - if (_subsByRoute.ContainsKey(route)) + if (_subsByRoute.ContainsKey(itemPath)) { - subs.AddRange(_subsByRoute[route]); + subs.AddRange(_subsByRoute[itemPath]); } } } diff --git a/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionEventSchemaMap.cs b/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionEventSchemaMap.cs index efd7fadc3..8bbbf7423 100644 --- a/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionEventSchemaMap.cs +++ b/src/graphql-aspnet-subscriptions/SubscriptionServer/SubscriptionEventSchemaMap.cs @@ -27,7 +27,7 @@ namespace GraphQL.AspNet.SubscriptionServer public static class SubscriptionEventSchemaMap { private static readonly ConcurrentHashSet PARSED_SCHEMA_TYPES; - private static readonly ConcurrentDictionary SUBSCRIPTION_EVENTNAME_CATALOG; + private static readonly ConcurrentDictionary SUBSCRIPTION_EVENTNAME_CATALOG; private static readonly object _syncLock = new object(); /// @@ -82,9 +82,9 @@ public static void EnsureSubscriptionEventsOrThrow(ISchema schema) /// /// The schema. /// Dictionary<System.String, SchemaItemPath>. - public static Dictionary CreateEventMap(ISchema schema) + public static Dictionary CreateEventMap(ISchema schema) { - var dic = new Dictionary(); + var dic = new Dictionary(); if (schema == null || !schema.Operations.ContainsKey(GraphOperationType.Subscription)) return dic; @@ -92,7 +92,7 @@ public static Dictionary CreateEventMap(I foreach (var field in schema.KnownTypes.OfType() .SelectMany(x => x.Fields.OfType())) { - var route = field.Route.Clone(); + var route = field.ItemPath.Clone(); var eventName = SubscriptionEventName.FromGraphField(schema, field); if (dic.ContainsKey(eventName)) @@ -111,12 +111,12 @@ public static Dictionary CreateEventMap(I } /// - /// Attempts to find the fully qualifed that is pointed at by the supplied event name. + /// Attempts to find the fully qualifed that is pointed at by the supplied event name. /// /// The schema. /// The formally named event. /// SchemaItemPath. - public static SchemaItemPath RetrieveSubscriptionFieldPath(this ISchema schema, SubscriptionEventName eventName) + public static ItemPath RetrieveSubscriptionFieldPath(this ISchema schema, SubscriptionEventName eventName) { Validation.ThrowIfNull(schema, nameof(schema)); Validation.ThrowIfNull(eventName, nameof(eventName)); 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..e63d250e7 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 // ------------------------------------------ @@ -177,7 +191,7 @@ public void UseExtension(IApplicationBuilder app = null, IServiceProvider servic app.UseMiddleware(middlewareType); var logger = serviceProvider.CreateScope().ServiceProvider.GetService(); - logger?.SchemaSubscriptionRouteRegistered(this.SubscriptionOptions.Route); + logger?.SchemaSubscriptionUrlRouteRegistered(this.SubscriptionOptions.Route); } } 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.sln b/src/graphql-aspnet.sln index b796713d9..f3978b89f 100644 --- a/src/graphql-aspnet.sln +++ b/src/graphql-aspnet.sln @@ -31,8 +31,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "graphql-aspnet-subscription EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "graphql-aspnet-subscriptions-tests", "unit-tests\graphql-aspnet-subscriptions-tests\graphql-aspnet-subscriptions-tests.csproj", "{6E4A16F5-1B98-412E-9A88-F56301F5D0E4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "starwars-api70", "ancillary-projects\starwars\starwars-api70\starwars-api70.csproj", "{B92A5C91-F88D-4F26-8775-20C72692BD43}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "graphql-aspnet-tests-common", "unit-tests\graphql-aspnet-tests-common\graphql-aspnet-tests-common.csproj", "{3CB086E3-5E7B-438B-9A95-AEA264009521}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "starwars-api80", "ancillary-projects\starwars\starwars-api80\starwars-api80.csproj", "{43C9EB6E-5FFE-4EC6-B21B-09C715B7B114}" @@ -70,10 +68,6 @@ Global {6E4A16F5-1B98-412E-9A88-F56301F5D0E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6E4A16F5-1B98-412E-9A88-F56301F5D0E4}.Debug|Any CPU.Build.0 = Debug|Any CPU {6E4A16F5-1B98-412E-9A88-F56301F5D0E4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B92A5C91-F88D-4F26-8775-20C72692BD43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B92A5C91-F88D-4F26-8775-20C72692BD43}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B92A5C91-F88D-4F26-8775-20C72692BD43}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B92A5C91-F88D-4F26-8775-20C72692BD43}.Release|Any CPU.Build.0 = Release|Any CPU {3CB086E3-5E7B-438B-9A95-AEA264009521}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3CB086E3-5E7B-438B-9A95-AEA264009521}.Debug|Any CPU.Build.0 = Debug|Any CPU {3CB086E3-5E7B-438B-9A95-AEA264009521}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -96,7 +90,6 @@ Global {5F6EBAF4-B5EB-4DBD-8F51-17BBC2E8984D} = {22C7BC5B-EC8E-4A07-9968-961E86AB44E2} {5DE081AA-494A-4377-B2CA-6952715D513D} = {350D3594-5D97-4D9B-A01D-D3A5C036318C} {6E4A16F5-1B98-412E-9A88-F56301F5D0E4} = {350D3594-5D97-4D9B-A01D-D3A5C036318C} - {B92A5C91-F88D-4F26-8775-20C72692BD43} = {22C7BC5B-EC8E-4A07-9968-961E86AB44E2} {3CB086E3-5E7B-438B-9A95-AEA264009521} = {350D3594-5D97-4D9B-A01D-D3A5C036318C} {43C9EB6E-5FFE-4EC6-B21B-09C715B7B114} = {22C7BC5B-EC8E-4A07-9968-961E86AB44E2} {E67D4FB2-73FF-4EC1-80F8-5D4DEC57F5AA} = {350D3594-5D97-4D9B-A01D-D3A5C036318C} 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..b7231296e 100644 --- a/src/graphql-aspnet/Attributes/GraphFieldAttribute.cs +++ b/src/graphql-aspnet/Attributes/GraphFieldAttribute.cs @@ -26,16 +26,17 @@ public class GraphFieldAttribute : GraphAttributeBase /// Initializes a new instance of the class. /// public GraphFieldAttribute() - : this(false, SchemaItemCollections.Types, Constants.Routing.ACTION_METHOD_META_NAME) + : this(false, ItemPathRoots.Types, Constants.Routing.ACTION_METHOD_META_NAME) { } /// /// 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, ItemPathRoots.Types, template) { } @@ -50,7 +51,7 @@ public GraphFieldAttribute(string name) /// or just setup the field. protected GraphFieldAttribute( bool isRootFragment, - SchemaItemCollections fieldType, + ItemPathRoots fieldType, string template, string unionTypeName, params Type[] typeSet) @@ -69,7 +70,7 @@ protected GraphFieldAttribute( /// or just setup the field. protected GraphFieldAttribute( bool isRootFragment, - SchemaItemCollections fieldType, + ItemPathRoots fieldType, string template, params Type[] typeSet) { @@ -86,10 +87,10 @@ protected GraphFieldAttribute( /// field is a part of. /// /// The type of the field. - public SchemaItemCollections FieldType { get; } + public ItemPathRoots FieldType { get; } /// - /// Gets the template for the route fragment for the field, if provided. + /// Gets the template for the path fragment for the field, if provided. /// /// The name. public string Template { get; } @@ -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/GraphRouteAttribute.cs b/src/graphql-aspnet/Attributes/GraphRouteAttribute.cs index 8567bc70f..0ceba65f8 100644 --- a/src/graphql-aspnet/Attributes/GraphRouteAttribute.cs +++ b/src/graphql-aspnet/Attributes/GraphRouteAttribute.cs @@ -43,7 +43,7 @@ protected GraphRouteAttribute(bool ignoreControllerField) /// /// Gets the template assigned to this instance. /// - /// The route fragment. + /// The path fragment template. public string Template { get; } /// 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..426d24d83 100644 --- a/src/graphql-aspnet/Attributes/MutationAttribute.cs +++ b/src/graphql-aspnet/Attributes/MutationAttribute.cs @@ -11,7 +11,6 @@ namespace GraphQL.AspNet.Attributes { using System; using System.Linq; - using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Interfaces.Schema; @@ -36,7 +35,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) { } @@ -69,7 +78,7 @@ public MutationAttribute(Type returnType, params Type[] additionalTypes) /// The type of the object returned from this method. If this type implements /// this field will be declared as returning the union defined by the type. public MutationAttribute(string template, Type returnType) - : base(false, SchemaItemCollections.Mutation, template, returnType) + : base(false, ItemPathRoots.Mutation, template, returnType) { } @@ -82,7 +91,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, ItemPathRoots.Mutation, template, (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray()) { } @@ -91,16 +100,15 @@ 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. + /// The first 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 firstUnionType, params Type[] additionalUnionTypes) : base( false, - SchemaItemCollections.Mutation, + ItemPathRoots.Mutation, template, unionTypeName, - unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).ToArray()) + (new Type[] { firstUnionType }).Concat(additionalUnionTypes ?? Enumerable.Empty()).ToArray()) { } } diff --git a/src/graphql-aspnet/Attributes/MutationRootAttribute.cs b/src/graphql-aspnet/Attributes/MutationRootAttribute.cs index 9cebba929..f57189d6a 100644 --- a/src/graphql-aspnet/Attributes/MutationRootAttribute.cs +++ b/src/graphql-aspnet/Attributes/MutationRootAttribute.cs @@ -11,7 +11,6 @@ namespace GraphQL.AspNet.Attributes { using System; using System.Linq; - using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Interfaces.Schema; @@ -35,7 +34,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) { } @@ -68,7 +77,7 @@ public MutationRootAttribute(Type returnType, params Type[] additionalTypes) /// The type of the object returned from this method. If this type implements /// this field will be declared as returning the union defined by the type. public MutationRootAttribute(string template, Type returnType) - : base(true, SchemaItemCollections.Mutation, template, returnType) + : base(true, ItemPathRoots.Mutation, template, returnType) { } @@ -81,7 +90,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, ItemPathRoots.Mutation, template, (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray()) { } @@ -90,11 +99,15 @@ 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. + /// The first 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 firstUnionType, params Type[] additionalUnionTypes) + : base( + true, + ItemPathRoots.Mutation, + template, + unionTypeName, + (new Type[] { firstUnionType }).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..e56bb3cbc 100644 --- a/src/graphql-aspnet/Attributes/QueryAttribute.cs +++ b/src/graphql-aspnet/Attributes/QueryAttribute.cs @@ -11,7 +11,6 @@ namespace GraphQL.AspNet.Attributes { using System; using System.Linq; - using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Interfaces.Schema; @@ -36,7 +35,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) { } @@ -69,7 +78,7 @@ public QueryAttribute(Type returnType, params Type[] additionalTypes) /// The type of the data object returned from this method. If this type implements /// this field will be declared as returning the union defined by the type. public QueryAttribute(string template, Type returnType) - : base(false, SchemaItemCollections.Query, template, returnType) + : base(false, ItemPathRoots.Query, template, returnType) { } @@ -82,7 +91,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, ItemPathRoots.Query, template, (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray()) { } @@ -91,16 +100,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 firstUnionType, params Type[] additionalUnionTypes) : base( false, - SchemaItemCollections.Query, + ItemPathRoots.Query, template, unionTypeName, - unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).ToArray()) + (new Type[] { firstUnionType }).Concat(additionalUnionTypes ?? Enumerable.Empty()).ToArray()) { } } diff --git a/src/graphql-aspnet/Attributes/QueryRootAttribute.cs b/src/graphql-aspnet/Attributes/QueryRootAttribute.cs index fe6d73102..3ac6a3e3f 100644 --- a/src/graphql-aspnet/Attributes/QueryRootAttribute.cs +++ b/src/graphql-aspnet/Attributes/QueryRootAttribute.cs @@ -11,7 +11,6 @@ namespace GraphQL.AspNet.Attributes { using System; using System.Linq; - using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Interfaces.Schema; @@ -35,7 +34,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) { } @@ -68,7 +77,7 @@ public QueryRootAttribute(Type returnType, params Type[] additionalTypes) /// The type of the object returned from this method. If this type implements /// this field will be declared as returning the union defined by the type. public QueryRootAttribute(string template, Type returnType) - : base(true, SchemaItemCollections.Query, template, returnType) + : base(true, ItemPathRoots.Query, template, returnType) { } @@ -81,7 +90,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, + ItemPathRoots.Query, + template, + (new Type[] { returnType }).Concat(additionalTypes ?? Enumerable.Empty()).ToArray()) { } @@ -90,16 +103,15 @@ 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. + /// The first 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 firstUnionType, params Type[] additionalUnionTypes) : base( true, - SchemaItemCollections.Query, + ItemPathRoots.Query, template, unionTypeName, - unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).ToArray()) + (new Type[] { firstUnionType }).Concat(additionalUnionTypes ?? Enumerable.Empty()).ToArray()) { } } diff --git a/src/graphql-aspnet/Attributes/TypeExtensionAttribute.cs b/src/graphql-aspnet/Attributes/TypeExtensionAttribute.cs index d8ebf96a7..c311032c6 100644 --- a/src/graphql-aspnet/Attributes/TypeExtensionAttribute.cs +++ b/src/graphql-aspnet/Attributes/TypeExtensionAttribute.cs @@ -45,7 +45,7 @@ public TypeExtensionAttribute(Type typeToExtend, string fieldName) /// The type of the data object returned from this method. If this type implements /// this field will be declared as returning the union defined by the type. public TypeExtensionAttribute(Type typeToExtend, string fieldName, Type returnType) - : base(false, SchemaItemCollections.Types, fieldName, returnType) + : base(false, ItemPathRoots.Types, fieldName, returnType) { this.TypeToExtend = typeToExtend; } @@ -68,7 +68,7 @@ public TypeExtensionAttribute( params Type[] additionalUnionTypes) : base( false, - SchemaItemCollections.Types, + ItemPathRoots.Types, fieldName, unionTypeName, unionTypeA.AsEnumerable().Concat(unionTypeB.AsEnumerable()).Concat(additionalUnionTypes).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/FormatRules/Naming/ApplyArgumentNameFormatRule.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyArgumentNameFormatRule.cs new file mode 100644 index 000000000..10b9db24e --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyArgumentNameFormatRule.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.Configuration.Formatting.FormatRules +{ + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// Applies a given format pattern to field argument names for the target schema. + /// + public class ApplyArgumentNameFormatRule : NameFormatRuleBase, ISchemaItemFormatRule + { + private readonly TextFormatOptions _formatOption; + + /// + /// Initializes a new instance of the class. + /// + /// The format option. + public ApplyArgumentNameFormatRule(TextFormatOptions formatOption) + { + _formatOption = formatOption; + } + + /// + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem + { + if (schemaItem is IGraphArgument argument) + { + var formattedName = this.FormatText(argument.Name, _formatOption); + schemaItem = (TSchemaItemType)argument.Clone(argumentName: formattedName); + } + + return schemaItem; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyArgumentTypeExpressionNameFormatRule.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyArgumentTypeExpressionNameFormatRule.cs new file mode 100644 index 000000000..20deaaf05 --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyArgumentTypeExpressionNameFormatRule.cs @@ -0,0 +1,49 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration.Formatting.FormatRules +{ + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// Applies a graph type name format pattern to the type expression on each argument encounterd + /// on fields in OBJECT and INTERFACE types as well as those defined on DIRECTIVE types for the target schema. + /// + internal class ApplyArgumentTypeExpressionNameFormatRule : NameFormatRuleBase, ISchemaItemFormatRule + { + private readonly TextFormatOptions _formatOption; + + /// + /// Initializes a new instance of the class. + /// + /// The format to use for the type name + /// referneced in the argument's type expression. + public ApplyArgumentTypeExpressionNameFormatRule(TextFormatOptions typeExpressionGraphTypeFormat) + { + _formatOption = typeExpressionGraphTypeFormat; + } + + /// + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem + { + if (schemaItem is IGraphArgument argument) + { + var typeExpression = argument.TypeExpression; + var formattedName = this.FormatGraphTypeName(typeExpression.TypeName, _formatOption); + typeExpression = typeExpression.Clone(formattedName); + + schemaItem = (TSchemaItemType)argument.Clone(typeExpression: typeExpression); + } + + return schemaItem; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyDirectiveNameFormatRule.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyDirectiveNameFormatRule.cs new file mode 100644 index 000000000..1a1092ab1 --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyDirectiveNameFormatRule.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.Formatting.FormatRules +{ + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// Applies a given format pattern to formal name for directives on the target schema. + /// + internal class ApplyDirectiveNameFormatRule : NameFormatRuleBase, ISchemaItemFormatRule + { + private readonly TextFormatOptions _formatOption; + + /// + /// Initializes a new instance of the class. + /// + /// The format option. + public ApplyDirectiveNameFormatRule(TextFormatOptions formatOption) + { + _formatOption = formatOption; + } + + /// + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem + { + // ensure all path segments of the virtual type are + // named according to the rules of this schema + if (schemaItem is IDirective directive) + { + var formattedName = this.FormatText(directive.Name, _formatOption); + schemaItem = (TSchemaItemType)directive.Clone(formattedName); + } + + return schemaItem; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyEnumNameFormatRule.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyEnumNameFormatRule.cs new file mode 100644 index 000000000..9d023a064 --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyEnumNameFormatRule.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.Configuration.Formatting.FormatRules +{ + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// Applies a given name format pattern to enum values within the target schema. + /// + internal class ApplyEnumNameFormatRule : NameFormatRuleBase, ISchemaItemFormatRule + { + private readonly TextFormatOptions _formatOption; + + /// + /// Initializes a new instance of the class. + /// + /// The format option. + public ApplyEnumNameFormatRule(TextFormatOptions formatOption) + { + _formatOption = formatOption; + } + + /// + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem + { + if (schemaItem is IEnumValue enumValue) + { + var formattedName = this.FormatText(enumValue.Name, _formatOption); + schemaItem = (TSchemaItemType)enumValue.Clone(valueName: formattedName); + } + + return schemaItem; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyFieldNameFormatRule.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyFieldNameFormatRule.cs new file mode 100644 index 000000000..32df9bfe4 --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyFieldNameFormatRule.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.Configuration.Formatting.FormatRules +{ + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// Applies a name given format pattern to each field in the targetschema. + /// + internal class ApplyFieldNameFormatRule : NameFormatRuleBase, ISchemaItemFormatRule + { + private readonly TextFormatOptions _formatOption; + + /// + /// Initializes a new instance of the class. + /// + /// The format option. + public ApplyFieldNameFormatRule(TextFormatOptions formatOption) + { + _formatOption = formatOption; + } + + /// + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem + { + if (schemaItem is IGraphField field) + { + var formattedName = this.FormatText(field.Name, _formatOption); + schemaItem = (TSchemaItemType)field.Clone(fieldName: formattedName); + } + + return schemaItem; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyFieldTypeExpressionNameFormatRule.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyFieldTypeExpressionNameFormatRule.cs new file mode 100644 index 000000000..e11f80d9e --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyFieldTypeExpressionNameFormatRule.cs @@ -0,0 +1,49 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration.Formatting.FormatRules +{ + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// Applies a name graph type name format pattern to the type expression on each field in + /// the OBJECTs and INTERFACE types in the target schema. + /// + internal class ApplyFieldTypeExpressionNameFormatRule : NameFormatRuleBase, ISchemaItemFormatRule + { + private readonly TextFormatOptions _formatOption; + + /// + /// Initializes a new instance of the class. + /// + /// The format to use for the type name + /// referneced in the fields type expression. + public ApplyFieldTypeExpressionNameFormatRule(TextFormatOptions typeExpressionGraphTypeFormat) + { + _formatOption = typeExpressionGraphTypeFormat; + } + + /// + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem + { + if (schemaItem is IGraphField field) + { + var typeExpression = field.TypeExpression; + var formattedName = this.FormatGraphTypeName(typeExpression.TypeName, _formatOption); + typeExpression = typeExpression.Clone(formattedName); + + schemaItem = (TSchemaItemType)field.Clone(typeExpression: typeExpression); + } + + return schemaItem; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyGraphTypeNameFormatRule.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyGraphTypeNameFormatRule.cs new file mode 100644 index 000000000..db3af6897 --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyGraphTypeNameFormatRule.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.Configuration.Formatting.FormatRules +{ + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// Applies a given format pattern to formal graph type names for the target schema. + /// + internal class ApplyGraphTypeNameFormatRule : NameFormatRuleBase, ISchemaItemFormatRule + { + private readonly TextFormatOptions _formatOption; + + /// + /// Initializes a new instance of the class. + /// + /// The format option. + public ApplyGraphTypeNameFormatRule(TextFormatOptions formatOption) + { + _formatOption = formatOption; + } + + /// + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem + { + // ensure all path segments of the virtual type are + // named according to the rules of this schema + switch (schemaItem) + { + case VirtualObjectGraphType virtualType: + var newName = VirtualObjectGraphType.MakeSafeTypeNameFromItemPath( + virtualType.ItemPathTemplate, + (segment) => this.FormatText(segment, _formatOption)); + + schemaItem = (TSchemaItemType)virtualType.Clone(newName); + break; + + // do nothing with directives + // when naming graph types + // they name under the explicit directive rule + case IDirective directive: + break; + + case IGraphType graphType: + var formattedName = this.FormatGraphTypeName(graphType.Name, _formatOption); + schemaItem = (TSchemaItemType)graphType.Clone(formattedName); + break; + } + + return schemaItem; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyGraphTypeNameFormatToUnionTypeMembersRule.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyGraphTypeNameFormatToUnionTypeMembersRule.cs new file mode 100644 index 000000000..c68ee1b9f --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyGraphTypeNameFormatToUnionTypeMembersRule.cs @@ -0,0 +1,45 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration.Formatting.FormatRules +{ + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// Applies the given format pattern to the graph type names contained in a union type. + /// + public class ApplyGraphTypeNameFormatToUnionTypeMembersRule : NameFormatRuleBase, ISchemaItemFormatRule + { + private readonly TextFormatOptions _formatOption; + + /// + /// Initializes a new instance of the class. + /// + /// The format option. + public ApplyGraphTypeNameFormatToUnionTypeMembersRule(TextFormatOptions formatOption) + { + _formatOption = formatOption; + } + + /// + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem + { + if (schemaItem is IUnionGraphType ugt) + { + schemaItem = (TSchemaItemType)ugt.Clone( + possibleGraphTypeNameFormatter: (typeName) + => this.FormatGraphTypeName(typeName, _formatOption)); + } + + return schemaItem; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyInputFieldNameFormatRule.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyInputFieldNameFormatRule.cs new file mode 100644 index 000000000..b03545cbd --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyInputFieldNameFormatRule.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.Configuration.Formatting.FormatRules +{ + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// Applies the given format option to fields on INPUT_OBJECTs for the given schema. + /// + internal class ApplyInputFieldNameFormatRule : NameFormatRuleBase, ISchemaItemFormatRule + { + private readonly TextFormatOptions _formatOption; + + /// + /// Initializes a new instance of the class. + /// + /// The format option. + public ApplyInputFieldNameFormatRule(TextFormatOptions formatOption) + { + _formatOption = formatOption; + } + + /// + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem + { + if (schemaItem is IInputGraphField field) + { + var formattedName = this.FormatText(field.Name, _formatOption); + schemaItem = (TSchemaItemType)field.Clone(fieldName: formattedName); + } + + return schemaItem; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyInputFieldTypeExpressionNameFormatRule.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyInputFieldTypeExpressionNameFormatRule.cs new file mode 100644 index 000000000..ba6297229 --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/ApplyInputFieldTypeExpressionNameFormatRule.cs @@ -0,0 +1,49 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration.Formatting.FormatRules +{ + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// Applies a graph type name format pattern to the type expression on each field on the INPUT_OBJECTs in + /// the target schema. + /// + internal class ApplyInputFieldTypeExpressionNameFormatRule : NameFormatRuleBase, ISchemaItemFormatRule + { + private readonly TextFormatOptions _formatOption; + + /// + /// Initializes a new instance of the class. + /// + /// The format to use for the type name + /// referenced in the fields type expression. + public ApplyInputFieldTypeExpressionNameFormatRule(TextFormatOptions typeExpressionGraphTypeFormat) + { + _formatOption = typeExpressionGraphTypeFormat; + } + + /// + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem + { + if (schemaItem is IInputGraphField field) + { + var typeExpression = field.TypeExpression; + var formattedName = this.FormatGraphTypeName(typeExpression.TypeName, _formatOption); + typeExpression = typeExpression.Clone(formattedName); + + schemaItem = (TSchemaItemType)field.Clone(typeExpression: typeExpression); + } + + return schemaItem; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/NameFormatRuleBase.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/NameFormatRuleBase.cs new file mode 100644 index 000000000..eb3a45cab --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Naming/NameFormatRuleBase.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.Configuration.Formatting.FormatRules +{ + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// A base class for all the individual name format rules. + /// + public abstract class NameFormatRuleBase + { + /// + /// Formats or reformats the name according to the rules of this formatter. + /// + /// The name value being formatted. + /// The selected strategy to format with. + /// System.String. + protected virtual string FormatText(string name, TextFormatOptions strategy) + { + if (name == null) + return null; + + switch (strategy) + { + case TextFormatOptions.ProperCase: + return name.FirstCharacterToUpperInvariant(); + + case TextFormatOptions.CamelCase: + return name.FirstCharacterToLowerInvariant(); + + case TextFormatOptions.UpperCase: + return name.ToUpperInvariant(); + + case TextFormatOptions.LowerCase: + return name.ToLowerInvariant(); + + // ReSharper disable once RedundantCaseLabel + case TextFormatOptions.NoChanges: + default: + return name; + } + } + + /// + /// Treats the text as if it were to be a graph type name and enforces global type rule + /// rename restrictions before renaming the text value. The string is returned altered + /// or unaltered depending on its original value. + /// + /// The name to format. + /// The format to apply, if allowed. + /// System.String. + protected virtual string FormatGraphTypeName(string name, TextFormatOptions formatOption) + { + if (GlobalTypes.CanBeRenamed(name)) + name = this.FormatText(name, formatOption); + + return name; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyArgumentNonNullItemTypeExpressionFormatRule.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyArgumentNonNullItemTypeExpressionFormatRule.cs new file mode 100644 index 000000000..eb92bfcfa --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyArgumentNonNullItemTypeExpressionFormatRule.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.Configuration.Formatting.FormatRules +{ + using System; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Configuration.Formatting.FormatRules.Nullability; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// For a field argument that matches the provided predicate + /// this rule will ensure that the root object of the type expression cannot be null. + /// + public class ApplyArgumentNonNullItemTypeExpressionFormatRule : NullabilityFormatRuleBase, ISchemaItemFormatRule + { + private readonly Func _predicate; + + /// + /// Initializes a new instance of the class. + /// + /// The predicate function that will be used to match an argument. + public ApplyArgumentNonNullItemTypeExpressionFormatRule(Func predicate) + { + _predicate = Validation.ThrowIfNullOrReturn(predicate, nameof(predicate)); + } + + /// + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem + { + if (schemaItem is IGraphArgument argument && _predicate(argument)) + { + var newTypeExpression = this.ConvertItemReferenceToNonNull(argument.TypeExpression); + argument = argument.Clone(typeExpression: newTypeExpression); + + if (!newTypeExpression.IsNullable && argument.HasDefaultValue && argument.DefaultValue is null) + { + // when the input argument, as a whole, becomes non-nullable and has a default value of null + // the field must become "required" without a default value because of the rules + // of the schema + argument = argument.Clone(defaultValueOptions: DefaultValueCloneOptions.MakeRequired); + } + + schemaItem = (TSchemaItemType)argument; + } + + return schemaItem; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyArgumentNonNullListTypeExpressionFormatRule.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyArgumentNonNullListTypeExpressionFormatRule.cs new file mode 100644 index 000000000..7d8384dcb --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyArgumentNonNullListTypeExpressionFormatRule.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.Configuration.Formatting.FormatRules +{ + using System; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Configuration.Formatting.FormatRules.Nullability; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// For a given field argument, this rule will force all encountered lists + /// and nested lists on the type expression to be non-null. + /// + internal class ApplyArgumentNonNullListTypeExpressionFormatRule : NullabilityFormatRuleBase, ISchemaItemFormatRule + { + private readonly Func _predicate; + + /// + /// Initializes a new instance of the class. + /// + /// The predicate function that will be used to match an argument. + public ApplyArgumentNonNullListTypeExpressionFormatRule(Func predicate) + { + _predicate = Validation.ThrowIfNullOrReturn(predicate, nameof(predicate)); + } + + /// + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem + { + if (schemaItem is IGraphArgument argument && _predicate(argument)) + { + var newTypeExpression = this.ConvertAllListsToNonNull(argument.TypeExpression); + argument = argument.Clone(typeExpression: newTypeExpression); + + if (!newTypeExpression.IsNullable && argument.HasDefaultValue && argument.DefaultValue is null) + { + // when the input argument, as a whole, becomes non-nullable and has a default value of null + // the field must become "required" without a default value because of the rules + // of the schema + argument = argument.Clone(defaultValueOptions: DefaultValueCloneOptions.MakeRequired); + } + + schemaItem = (TSchemaItemType)argument; + } + + return schemaItem; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyFieldNonNullItemTypeExpressionFormatRule.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyFieldNonNullItemTypeExpressionFormatRule.cs new file mode 100644 index 000000000..38aa98c78 --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyFieldNonNullItemTypeExpressionFormatRule.cs @@ -0,0 +1,49 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration.Formatting.FormatRules +{ + using System; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Configuration.Formatting.FormatRules.Nullability; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// For a given graph field (OBJECT or INTERFACE) that matches the provided predicate + /// this rule will ensure that the root object of the type expression + /// cannot be null. + /// + public class ApplyFieldNonNullItemTypeExpressionFormatRule : NullabilityFormatRuleBase, ISchemaItemFormatRule + { + private readonly Func _predicate; + + /// + /// Initializes a new instance of the class. + /// + /// The predicate function that will be used to match a graph field. + public ApplyFieldNonNullItemTypeExpressionFormatRule(Func predicate) + { + _predicate = Validation.ThrowIfNullOrReturn(predicate, nameof(predicate)); + } + + /// + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem + { + if (schemaItem is IGraphField field && _predicate(field)) + { + var newTypeExpression = this.ConvertItemReferenceToNonNull(field.TypeExpression); + schemaItem = (TSchemaItemType)field.Clone(typeExpression: newTypeExpression); + } + + return schemaItem; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyFieldNonNullListTypeExpressionFormatRule.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyFieldNonNullListTypeExpressionFormatRule.cs new file mode 100644 index 000000000..e6332a2b5 --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyFieldNonNullListTypeExpressionFormatRule.cs @@ -0,0 +1,48 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration.Formatting.FormatRules +{ + using System; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Configuration.Formatting.FormatRules.Nullability; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// For a given input field (on an INPUT_OBJECT) will force all encountered lists + /// and nested lists on the type expression to be non-null. + /// + public class ApplyFieldNonNullListTypeExpressionFormatRule : NullabilityFormatRuleBase, ISchemaItemFormatRule + { + private readonly Func _predicate; + + /// + /// Initializes a new instance of the class. + /// + /// The predicate function that will be used to match a graph field. + public ApplyFieldNonNullListTypeExpressionFormatRule(Func predicate) + { + _predicate = Validation.ThrowIfNullOrReturn(predicate, nameof(predicate)); + } + + /// + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem + { + if (schemaItem is IGraphField field && _predicate(field)) + { + var newTypeExpression = this.ConvertAllListsToNonNull(field.TypeExpression); + schemaItem = (TSchemaItemType)field.Clone(typeExpression: newTypeExpression); + } + + return schemaItem; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyInputFieldNonNullItemTypeExpressionFormatRule.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyInputFieldNonNullItemTypeExpressionFormatRule.cs new file mode 100644 index 000000000..54e6dba28 --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyInputFieldNonNullItemTypeExpressionFormatRule.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.Formatting.FormatRules +{ + using System; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Configuration.Formatting.FormatRules.Nullability; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// For a given inputfield (on an INPUT_OBJECT) that matches the provided predicate + /// this rule will ensure that the root object of the type expression + /// cannot be null. + /// + public class ApplyInputFieldNonNullItemTypeExpressionFormatRule : NullabilityFormatRuleBase, ISchemaItemFormatRule + { + private readonly Func _predicate; + + /// + /// Initializes a new instance of the class. + /// + /// The predicate function that will be used to match a graph field. + public ApplyInputFieldNonNullItemTypeExpressionFormatRule(Func predicate) + { + _predicate = Validation.ThrowIfNullOrReturn(predicate, nameof(predicate)); + } + + /// + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem + { + if (schemaItem is IInputGraphField field && _predicate(field)) + { + var newTypeExpression = this.ConvertItemReferenceToNonNull(field.TypeExpression); + field = field.Clone(typeExpression: newTypeExpression); + + if (!newTypeExpression.IsNullable && field.HasDefaultValue && field.DefaultValue is null) + { + // when the field, as a whole, becomes non-nullable and has a default value of null + // the field must become "required" without a default value because of the rules + // of the schema + field = field.Clone(defaultValueOptions: DefaultValueCloneOptions.MakeRequired); + } + + schemaItem = (TSchemaItemType)field; + } + + return schemaItem; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyInputFieldNonNullListTypeExpressionFormatRule.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyInputFieldNonNullListTypeExpressionFormatRule.cs new file mode 100644 index 000000000..9b852425b --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/ApplyInputFieldNonNullListTypeExpressionFormatRule.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.Configuration.Formatting.FormatRules +{ + using System; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Configuration.Formatting.FormatRules.Nullability; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + + /// + /// For a given input field (on an INPUT_OBJECT) will force all encountered lists + /// and nested lists on the type expression to be non-null. + /// + internal class ApplyInputFieldNonNullListTypeExpressionFormatRule : NullabilityFormatRuleBase, ISchemaItemFormatRule + { + private readonly Func _predicate; + + /// + /// Initializes a new instance of the class. + /// + /// The predicate function that will be used to match a graph field. + public ApplyInputFieldNonNullListTypeExpressionFormatRule(Func predicate) + { + _predicate = Validation.ThrowIfNullOrReturn(predicate, nameof(predicate)); + } + + /// + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem + { + if (schemaItem is IInputGraphField field && _predicate(field)) + { + var newTypeExpression = this.ConvertAllListsToNonNull(field.TypeExpression); + field = field.Clone(typeExpression: newTypeExpression); + + if (!newTypeExpression.IsNullable && field.HasDefaultValue && field.DefaultValue is null) + { + // when the field, as a whole, becomes non-nullable and has a default value of null + // the field must become "required" without a default value because of the rules + // of the schema + field = field.Clone(defaultValueOptions: DefaultValueCloneOptions.MakeRequired); + } + + schemaItem = (TSchemaItemType)field; + } + + return schemaItem; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/NullabilityFormatRuleBase.cs b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/NullabilityFormatRuleBase.cs new file mode 100644 index 000000000..82e24dbec --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/FormatRules/Nullability/NullabilityFormatRuleBase.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.Configuration.Formatting.FormatRules.Nullability +{ + using GraphQL.AspNet.Schemas; + + /// + /// A common class for nullability format rules to centralize common logic + /// + public abstract class NullabilityFormatRuleBase + { + /// + /// For the given type expression converts all lists (nested or otherwise) to be + /// non-nullabe. + /// + /// The type expression. + /// GraphTypeExpression. + protected virtual GraphTypeExpression ConvertAllListsToNonNull(GraphTypeExpression typeExpression) + { + if (typeExpression.IsFixed) + return typeExpression; + + typeExpression = typeExpression.Clone(GraphTypeExpressionNullabilityStrategies.NonNullLists); + return typeExpression; + } + + /// + /// For the given type expression converts the core item reference to be non-nullable. + /// + /// The type expression. + /// GraphTypeExpression. + protected virtual GraphTypeExpression ConvertItemReferenceToNonNull(GraphTypeExpression typeExpression) + { + if (!typeExpression.IsFixed) + typeExpression = typeExpression.Clone(GraphTypeExpressionNullabilityStrategies.NonNullType); + + return typeExpression; + } + } +} diff --git a/src/graphql-aspnet/Configuration/Formatting/GraphNameFormatter.cs b/src/graphql-aspnet/Configuration/Formatting/GraphNameFormatter.cs deleted file mode 100644 index 6311ea0b9..000000000 --- a/src/graphql-aspnet/Configuration/Formatting/GraphNameFormatter.cs +++ /dev/null @@ -1,129 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Configuration.Formatting -{ - using System.Diagnostics; - using GraphQL.AspNet.Common.Extensions; - - /// - /// A formatter class capable of altering a graph item name before its added to a schema. - /// - [DebuggerDisplay("Type = {_typeNameStrategy}, Field = {_fieldNameStrategy}, Enum = {_enumValueStrategy}")] - public class GraphNameFormatter - { - private readonly GraphNameFormatStrategy _typeNameStrategy; - private readonly GraphNameFormatStrategy _fieldNameStrategy; - private readonly GraphNameFormatStrategy _enumValueStrategy; - - /// - /// Initializes a new instance of the class. - /// - protected GraphNameFormatter() - : this( - typeNameStrategy: GraphNameFormatStrategy.ProperCase, - fieldNameStrategy: GraphNameFormatStrategy.CamelCase, - enumValueStrategy: GraphNameFormatStrategy.UpperCase) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A single strategy to use for all naming options. - public GraphNameFormatter(GraphNameFormatStrategy singleStrategy) - { - _typeNameStrategy = singleStrategy; - _fieldNameStrategy = singleStrategy; - _enumValueStrategy = singleStrategy; - } - - /// - /// Initializes a new instance of the class. - /// - /// The strategy to use for naming graph types. - /// The strategy to use for naming fields and arguments. - /// The strategy to use for naming individual enum values. - public GraphNameFormatter( - GraphNameFormatStrategy typeNameStrategy = GraphNameFormatStrategy.ProperCase, - GraphNameFormatStrategy fieldNameStrategy = GraphNameFormatStrategy.CamelCase, - GraphNameFormatStrategy enumValueStrategy = GraphNameFormatStrategy.UpperCase) - { - _typeNameStrategy = typeNameStrategy; - _fieldNameStrategy = fieldNameStrategy; - _enumValueStrategy = enumValueStrategy; - } - - /// - /// Formats a field or argument name according to the strategy declared on this formatter. - /// - /// The field or argument name to format. - /// System.String. - public virtual string FormatFieldName(string name) - { - return this.FormatName(name, _fieldNameStrategy); - } - - /// - /// Formats the enum value name according to the strategy declared on this formatter. - /// - /// The enum value name to format. - /// System.String. - public virtual string FormatEnumValueName(string name) - { - return this.FormatName(name, _enumValueStrategy); - } - - /// - /// Formats the graph type name according to the strategy declared on this formatter. - /// - /// The type name to format. - /// 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); - } - - /// - /// Formats or reformats the name according to the rules of this formatter. - /// - /// The name value being formatted. - /// The selected strategy to format with. - /// System.String. - protected virtual string FormatName(string name, GraphNameFormatStrategy strategy) - { - if (name == null) - return null; - - switch (strategy) - { - case GraphNameFormatStrategy.ProperCase: - return name.FirstCharacterToUpperInvariant(); - - case GraphNameFormatStrategy.CamelCase: - return name.FirstCharacterToLowerInvariant(); - - case GraphNameFormatStrategy.UpperCase: - return name.ToUpperInvariant(); - - case GraphNameFormatStrategy.LowerCase: - return name.ToLowerInvariant(); - - // ReSharper disable once RedundantCaseLabel - case GraphNameFormatStrategy.NoChanges: - default: - return name; - } - } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/Formatting/SchemaFormatStrategy.cs b/src/graphql-aspnet/Configuration/Formatting/SchemaFormatStrategy.cs new file mode 100644 index 000000000..ab448a17b --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/SchemaFormatStrategy.cs @@ -0,0 +1,45 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration.Formatting +{ + using System.Collections.Generic; + using System.Linq; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// A strategy, employed by a schema, to make programatic alterations + /// to schema items as they are being constructed. + /// + public class SchemaFormatStrategy : ISchemaFormatStrategy + { + private List _formatRules; + + /// + /// Initializes a new instance of the class. + /// + /// The format rules to apply to schema items passing through + /// this instance. + public SchemaFormatStrategy(params ISchemaItemFormatRule[] formatRules) + { + _formatRules = formatRules.ToList() ?? []; + } + + /// + public virtual T ApplySchemaItemRules(ISchemaConfiguration configuration, T schemaItem) + where T : ISchemaItem + { + foreach (var rule in _formatRules) + schemaItem = rule.Execute(schemaItem); + + return schemaItem; + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/Formatting/SchemaFormatStrategyBuilder.cs b/src/graphql-aspnet/Configuration/Formatting/SchemaFormatStrategyBuilder.cs new file mode 100644 index 000000000..f65698e30 --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/SchemaFormatStrategyBuilder.cs @@ -0,0 +1,402 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration.Formatting +{ + using System; + using System.Collections.Generic; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Configuration.Formatting.FormatRules; + using GraphQL.AspNet.Interfaces.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// A builder class to formulate a + /// by applying a varied set of rules according to the devleoper's configuration requirements. + /// + public class SchemaFormatStrategyBuilder + { + /// + /// Creates a format strategy builder and applies the given format option for all name format rules. + /// + /// The single format option to use for all generated names. + /// if set to true default format rules + /// are applied to the builder. When false, no rules are applied and the builder is created + /// in an empty state. + /// SchemaFormatStrategy. + public static SchemaFormatStrategyBuilder Create( + TextFormatOptions formatOption, + bool applyDefaultRules = true) + { + return CreateInternal( + false, + formatOption, + formatOption, + formatOption, + formatOption, + formatOption); + } + + /// + /// Creates a format strategy builder with the provided text format overrides + /// will be applied as defined. + /// + /// The name format to use for graph type names. + /// The name format to use for fields. + /// The name format to use for enum values. + /// The name format to use for arguments on fields and directives. + /// The name format to use for directive type names. + /// if set to true default format rules + /// are applied to the builder. When false, no rules are applied and the builder is created + /// in an empty state. + /// SchemaFormatStrategy. + public static SchemaFormatStrategyBuilder Create( + TextFormatOptions? typeNameFormat = TextFormatOptions.ProperCase, + TextFormatOptions? fieldNameFormat = TextFormatOptions.CamelCase, + TextFormatOptions? enumValueNameFormat = TextFormatOptions.UpperCase, + TextFormatOptions? argumentNameFormat = TextFormatOptions.CamelCase, + TextFormatOptions? directiveNameFormat = TextFormatOptions.CamelCase, + bool applyDefaultRules = true) + { + return CreateInternal( + applyDefaultRules, + typeNameFormat, + fieldNameFormat, + enumValueNameFormat, + argumentNameFormat, + directiveNameFormat); + } + + private static SchemaFormatStrategyBuilder CreateInternal( + bool applyDefaultRules, + TextFormatOptions? typeNameFormat, + TextFormatOptions? fieldNameFormat, + TextFormatOptions? enumValueNameFormat, + TextFormatOptions? argumentNameFormat, + TextFormatOptions? directiveNameFormat) + { + var builder = new SchemaFormatStrategyBuilder(); + + if (!applyDefaultRules) + builder = builder.Clear(); + + if (typeNameFormat.HasValue) + builder = builder.WithGraphTypeNameFormat(typeNameFormat.Value); + if (fieldNameFormat.HasValue) + builder = builder.WithFieldNameFormat(fieldNameFormat.Value); + if (enumValueNameFormat.HasValue) + builder = builder.WithEnumValueFormat(enumValueNameFormat.Value); + if (directiveNameFormat.HasValue) + builder = builder.WithDirectiveNameFormat(directiveNameFormat.Value); + if (argumentNameFormat.HasValue) + builder = builder.WithFieldArgumentNameFormat(argumentNameFormat.Value); + + return builder; + } + + private TextFormatOptions? _typeNameFormat = null; + private TextFormatOptions? _fieldNameFormat = null; + private TextFormatOptions? _enumValueFormat = null; + private TextFormatOptions? _argumentNameFormat = null; + private TextFormatOptions? _directiveNameFormat = null; + private List _customRules = null; + + private bool _includeIntermediateTypeNonNullRules; + + /// + /// Initializes a new instance of the class. + /// + protected SchemaFormatStrategyBuilder() + { + _customRules = new List(); + + // set all aspects of virtual fields as "non null" by default + _includeIntermediateTypeNonNullRules = true; + } + + /// + /// Sets a rule such that intermediate graph type fields are declared as "non null". + /// These are guaranteed to exist by the runtime to always be present and this declaration is made + /// by default. + /// + /// + /// Use the method to unset this rule. This can be useful for backwards + /// compatiability with v1.x of this library. + /// + /// SchemaFormatStrategyBuilder. + public virtual SchemaFormatStrategyBuilder DeclareIntermediateTypesAsNonNull() + { + _includeIntermediateTypeNonNullRules = true; + return this; + } + + /// + /// Sets a rule such fields on INPUT_OBJECT types that match the given predicate + /// will be declared as "not null" by default for all generated graph types. + /// This can be overriden on a per field basis using . + /// + /// Example: [String] => [String!] + /// The predicate function that a field must match for the rule to be invoked. + /// SchemaFormatStrategyBuilder. + public virtual SchemaFormatStrategyBuilder DeclareInputFieldValuesAsNonNull(Func predicate) + { + Validation.ThrowIfNull(predicate, nameof(predicate)); + + return this.DeclareCustomRule(new ApplyInputFieldNonNullItemTypeExpressionFormatRule(predicate)); + } + + /// + /// Sets a rule such fields on INPUT_OBJECT types that match the given predicate and return a list + /// will be declared as "not null" by default for all generated graph types. Nested lists will also + /// be made non-nullable. Matching fields that do not declare a list will be quietly skipped. + /// This can be overriden on a per field basis using . + /// + /// Example: [String] => [String]! + /// The predicate function that a field must match for the rule to be invoked. + /// SchemaFormatStrategyBuilder. + public virtual SchemaFormatStrategyBuilder DeclareInputFieldListsAsNonNull(Func predicate) + { + return this.DeclareCustomRule(new ApplyInputFieldNonNullListTypeExpressionFormatRule(predicate)); + } + + /// + /// Sets a rule such fields on OBJECT and INTERFACE types that match the given predicate + /// will be declared as "not null" by default for all generated graph types. + /// This can be overriden on a per field basis using . + /// + /// Example: [String] => [String!] + /// The predicate function that a field must match for the rule to be invoked. + /// SchemaFormatStrategyBuilder. + public virtual SchemaFormatStrategyBuilder DeclareFieldValuesAsNonNull(Func predicate) + { + Validation.ThrowIfNull(predicate, nameof(predicate)); + return this.DeclareCustomRule(new ApplyFieldNonNullItemTypeExpressionFormatRule(predicate)); + } + + /// + /// Sets a rule such that fields on OBJECT and INTERFACE types which match the given predicate and + /// return a list of items will be declared as "not null" by default for all generated graph types. Nested lists will also + /// be made non-nullable. Matching fields that do not declare a list will be quietly skipped. + /// This can be overriden on a per field basis using . + /// + /// Example: [String] => [String]! + /// The predicate function that a field must match for the rule to be invoked. + /// SchemaFormatStrategyBuilder. + public virtual SchemaFormatStrategyBuilder DeclareFieldListsAsNonNull(Func predicate) + { + Validation.ThrowIfNull(predicate, nameof(predicate)); + return this.DeclareCustomRule(new ApplyFieldNonNullListTypeExpressionFormatRule(predicate)); + } + + /// + /// Sets a rule such that arguments on OBJECT and INTERFACE fields that match the given predicate + /// will have their object value be declared as "not null" by default for all generated graph types. + /// This can be overriden on a per field basis using . + /// + /// Example: [String] => [String!] + /// The predicate function that a field must match for the rule to be invoked. + /// SchemaFormatStrategyBuilder. + public virtual SchemaFormatStrategyBuilder DeclareArgumentValuesAsNonNull(Func predicate) + { + Validation.ThrowIfNull(predicate, nameof(predicate)); + return this.DeclareCustomRule(new ApplyArgumentNonNullItemTypeExpressionFormatRule(predicate)); + } + + /// + /// Sets a rule such that arguments on OBJECT and INTERFACE fields which match the given predicate and + /// return a list of items will be declared as "not null" by default for all generated graph types. Nested lists will also + /// be made non-nullable. Matching arguments that do not declare a list will be quietly skipped. + /// This can be overriden on a per field basis using . + /// + /// /// Example: [String] => [String]! + /// The predicate function that a field must match for the rule to be invoked. + /// SchemaFormatStrategyBuilder. + public virtual SchemaFormatStrategyBuilder DeclareArgumentListsAsNonNull(Func predicate) + { + Validation.ThrowIfNull(predicate, nameof(predicate)); + return this.DeclareCustomRule(new ApplyArgumentNonNullListTypeExpressionFormatRule(predicate)); + } + + /// + /// Declares a custom built rule on the strategy. This rule will be executed against each + /// just before its added to the target schema. + /// + /// The rule to declare. + /// SchemaFormatStrategyBuilder. + public virtual SchemaFormatStrategyBuilder DeclareCustomRule(ISchemaItemFormatRule rule) + { + Validation.ThrowIfNull(rule, nameof(rule)); + _customRules.Add(rule); + return this; + } + + /// + /// Sets the formating of graph type names (object, interface etc.) to the supplied built in strategy. + /// + /// + /// DEFAULT: ProperCase + /// + /// The strategy to use for graph type names. + /// SchemaFormatStrategyBuilder. + public virtual SchemaFormatStrategyBuilder WithGraphTypeNameFormat(TextFormatOptions format) + { + _typeNameFormat = format; + return this; + } + + /// + /// Sets the formating of field names on INPUT_OBJECT, OBJECT and INTERFACE types + /// to the supplied built in strategy. + /// + /// + /// DEFAULT: camelCase + /// + /// The format to use for field names. + /// SchemaFormatStrategyBuilder. + public virtual SchemaFormatStrategyBuilder WithFieldNameFormat(TextFormatOptions format) + { + _fieldNameFormat = format; + return this; + } + + /// + /// Sets the formating of enum values to the supplied built in strategy. + /// + /// + /// Default: UPPER CASE + /// + /// The strategy to use for graph type names. + /// SchemaFormatStrategyBuilder. + public virtual SchemaFormatStrategyBuilder WithEnumValueFormat(TextFormatOptions format) + { + _enumValueFormat = format; + return this; + } + + /// + /// Sets the formating of argument names defined on fields in INTERFACE and OBJECT + /// graph types as well as arguments on DIRECTIVEs. + /// + /// + /// Default: camelCase + /// + /// The format to use for all field argument names. + /// SchemaFormatStrategyBuilder. + public virtual SchemaFormatStrategyBuilder WithFieldArgumentNameFormat(TextFormatOptions format) + { + _argumentNameFormat = format; + return this; + } + + /// + /// Sets the formating of directive names for the target schema (e.g. @camelCasedDirectiveName) + /// + /// + /// Default: camelCase + /// + /// The format to use for all directive names. + /// SchemaFormatStrategyBuilder. + public virtual SchemaFormatStrategyBuilder WithDirectiveNameFormat(TextFormatOptions format) + { + _directiveNameFormat = format; + return this; + } + + /// + /// Clears all custom rules and name format options. + /// + /// SchemaFormatStrategyBuilder. + public virtual SchemaFormatStrategyBuilder Clear() + { + _customRules.Clear(); + + _typeNameFormat = null; + _fieldNameFormat = null; + _enumValueFormat = null; + _argumentNameFormat = null; + _directiveNameFormat = null; + + _includeIntermediateTypeNonNullRules = false; + + return this; + } + + /// + /// Creates the format strategy from all the rules and options set on this builder. + /// + /// ISchemaFormatStrategy. + public virtual ISchemaFormatStrategy Build() + { + return new SchemaFormatStrategy(this.GatherRules()); + } + + /// + /// Gathers the rules set on this builder into a single array. + /// + /// ISchemaItemFormatRule[]. + protected virtual ISchemaItemFormatRule[] GatherRules() + { + var ruleSet = new List(); + + // name rules first + ruleSet.AddRange(this.CreateNameFormatRules()); + + // intermediate type rules second + if (_includeIntermediateTypeNonNullRules) + { + ruleSet.Add(new ApplyFieldNonNullItemTypeExpressionFormatRule(x => x.IsVirtual)); + ruleSet.Add(new ApplyFieldNonNullListTypeExpressionFormatRule(x => x.IsVirtual)); + } + + // used defined rules last + ruleSet.AddRange(_customRules); + + return ruleSet.ToArray(); + } + + /// + /// Creates a set of rules to apply the name formats requested on this builder. + /// + /// IEnumerable<ISchemaItemFormatRule>. + protected virtual IEnumerable CreateNameFormatRules() + { + // formal name formatting + if (_typeNameFormat.HasValue) + { + yield return new ApplyGraphTypeNameFormatRule(_typeNameFormat.Value); + yield return new ApplyGraphTypeNameFormatToUnionTypeMembersRule(_typeNameFormat.Value); + yield return new ApplyFieldTypeExpressionNameFormatRule(_typeNameFormat.Value); + yield return new ApplyArgumentTypeExpressionNameFormatRule(_typeNameFormat.Value); + yield return new ApplyInputFieldTypeExpressionNameFormatRule(_typeNameFormat.Value); + } + + if (_fieldNameFormat.HasValue) + { + yield return new ApplyFieldNameFormatRule(_fieldNameFormat.Value); + yield return new ApplyInputFieldNameFormatRule(_fieldNameFormat.Value); + } + + if (_directiveNameFormat.HasValue) + { + yield return new ApplyDirectiveNameFormatRule(_directiveNameFormat.Value); + } + + if (_argumentNameFormat.HasValue) + { + yield return new ApplyArgumentNameFormatRule(_argumentNameFormat.Value); + } + + if (_enumValueFormat.HasValue) + { + yield return new ApplyEnumNameFormatRule(_enumValueFormat.Value); + } + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/Formatting/SchemaItemNameFormatOptions.cs b/src/graphql-aspnet/Configuration/Formatting/SchemaItemNameFormatOptions.cs new file mode 100644 index 000000000..ac00b307e --- /dev/null +++ b/src/graphql-aspnet/Configuration/Formatting/SchemaItemNameFormatOptions.cs @@ -0,0 +1,43 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Configuration.Formatting +{ + /// + /// A set of internally supported name formatting options for various names and values created + /// for a schema. + /// + public enum TextFormatOptions + { + /// + /// Text is rendered with a captial first letter (e.g. ItemNumber1, ItemNumber2). + /// + ProperCase, + + /// + /// Text is rendered with a lower case first letter (e.g. itemNumber1, itemNumber2). + /// + CamelCase, + + /// + /// Text is rendered in all upper case letters (e.g. ITEMNUMBER1, ITEMNUMBER2). + /// + UpperCase, + + /// + /// Text is rendered in all lower case letters (e.g. itemnumber1, itemnumber2). + /// + LowerCase, + + /// + /// Text is rendered as provided, no text changes are made. + /// + NoChanges, + } +} \ No newline at end of file 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..ff291bd48 --- /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, + (ItemPathRoots)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..6207a9162 --- /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 schema. + /// + 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, ItemPathRoots.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..fda9a8391 --- /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, ItemPathRoots.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 9f852f978..ea125c3ed 100644 --- a/src/graphql-aspnet/Configuration/GraphQLSchemaBuilderExtensions.cs +++ b/src/graphql-aspnet/Configuration/GraphQLSchemaBuilderExtensions.cs @@ -15,7 +15,6 @@ namespace GraphQL.AspNet.Configuration 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; @@ -105,7 +104,7 @@ public static ISchemaBuilder AddGraphQL( var injectorCollection = GetOrAddSchemaInjectorCollection(serviceCollection); if (injectorCollection.ContainsKey(typeof(TSchema))) { - 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."); } @@ -115,6 +114,7 @@ public static ISchemaBuilder AddGraphQL( injectorCollection.Add(typeof(TSchema), injector); injector.ConfigureServices(); + return injector.SchemaBuilder; } 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..3d0df73da 100644 --- a/src/graphql-aspnet/Configuration/SchemaDeclarationConfiguration.cs +++ b/src/graphql-aspnet/Configuration/SchemaDeclarationConfiguration.cs @@ -31,12 +31,14 @@ public SchemaDeclarationConfiguration() this.AllowedOperations = new HashSet(); this.AllowedOperations.Add(GraphOperationType.Query); this.AllowedOperations.Add(GraphOperationType.Mutation); + this.ArgumentBindingRule = SchemaArgumentBindingRules.ParametersPreferQueryResolution; + this.SchemaFormatStrategy = SchemaFormatStrategyBuilder.Create().Build(); } /// /// 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) @@ -44,7 +46,8 @@ public void Merge(ISchemaDeclarationConfiguration config) this.DisableIntrospection = config.DisableIntrospection; this.FieldDeclarationRequirements = config.FieldDeclarationRequirements; - this.GraphNamingFormatter = config.GraphNamingFormatter; + this.SchemaFormatStrategy = config.SchemaFormatStrategy; + this.ArgumentBindingRule = config.ArgumentBindingRule; if (config.AllowedOperations != null) { @@ -56,11 +59,14 @@ public void Merge(ISchemaDeclarationConfiguration config) /// public bool DisableIntrospection { get; set; } + /// + public SchemaArgumentBindingRules ArgumentBindingRule { get; set; } + /// public TemplateDeclarationRequirements FieldDeclarationRequirements { get; set; } = TemplateDeclarationRequirements.Default; /// - public GraphNameFormatter GraphNamingFormatter { get; set; } = new GraphNameFormatter(); + public ISchemaFormatStrategy SchemaFormatStrategy { get; set; } /// public HashSet AllowedOperations { get; } 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..04769bd6f 100644 --- a/src/graphql-aspnet/Configuration/SchemaItemFilterExtensions.cs +++ b/src/graphql-aspnet/Configuration/SchemaItemFilterExtensions.cs @@ -34,7 +34,7 @@ public static bool IsIntrospectionItem(this ISchemaItem item) /// (i.e. an item that starts with '__'). /// /// The item to inspect. - /// true if the specified item is system level data; otherwise, false. + /// true if the specified item an internal system level item; otherwise, false. public static bool IsSystemItem(this ISchemaItem item) { // name regex matches on valid "user supplied names" any schema items @@ -83,7 +83,7 @@ public static bool IsField( } /// - /// Determines whether the given schema item is a field on an OBJECT or INPUT_OBJECT graph type. + /// Determines whether the given schema item is a field on an OBJECT, INTERFACE or INPUT_OBJECT graph type. /// /// The schema item to inspect. /// the name of the graph type that owns the field. @@ -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/SchemaQueryHandlerConfiguration.cs b/src/graphql-aspnet/Configuration/SchemaQueryHandlerConfiguration.cs index 2dec91707..6de6bd563 100644 --- a/src/graphql-aspnet/Configuration/SchemaQueryHandlerConfiguration.cs +++ b/src/graphql-aspnet/Configuration/SchemaQueryHandlerConfiguration.cs @@ -33,7 +33,7 @@ public class SchemaQueryHandlerConfiguration /// HTTP request routing manually. /// /// - /// Default: false (i.e. "DO INCLUDE the default route"). + /// Default: false (i.e. "YES, DO INCLUDE the default route"). /// /// /// true if the route should be disabled; otherwise, false. 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..bc3b8e76f --- /dev/null +++ b/src/graphql-aspnet/Configuration/ServiceCollectionExtensions.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.Configuration +{ + using GraphQL.AspNet.Controllers; + using GraphQL.AspNet.Directives; + 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..80996633a 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) @@ -217,7 +237,7 @@ public void UseSchema(IApplicationBuilder appBuilder) _handler.Execute); appBuilder?.ApplicationServices.WriteLogEntry( - (l) => l.SchemaRouteRegistered( + (l) => l.SchemaUrlRouteRegistered( _options.QueryHandler.Route)); } } @@ -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..60996845b 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. /// @@ -180,7 +193,7 @@ public static class ErrorCodes public const string INVALID_BATCH_RESULT = "INVALID_BATCH_RESULT"; public const string FIELD_REQUEST_ABORTED = "FIELD_REQUEST_ABORTED"; public const string REQUEST_ABORTED = "REQUEST_ABORTED"; - public const string INVALID_ROUTE = "INVALID_ROUTE"; + public const string INVALID_PATH = "INVALID_PATH"; public const string MODEL_VALIDATION_ERROR = "MODEL_VALIDATION_ERROR"; public const string BAD_REQUEST = "BAD_REQUEST"; public const string EXECUTION_ERROR = "EXECUTION_ERROR"; @@ -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 } /// @@ -271,11 +281,11 @@ public static class ReservedNames { /// /// Gets a collection of reserved names, defined by the graphql schema, that - /// may appear as route path segments in an introspection query but are otherwise + /// may appear as item path segments in an introspection query but are otherwise /// not allowed by user created controllers or types. /// /// A read only hashset of all the known reserved names. - public static IImmutableSet IntrospectableRouteNames { get; } + public static IImmutableSet IntrospectablePathNames { get; } // public directive names public const string SKIP_DIRECTIVE = "skip"; @@ -364,7 +374,7 @@ static ReservedNames() dicTypeToTypeName.Add(GraphOperationType.Subscription, SUBSCRIPTION_TYPE_NAME); GRAPH_OPERATION_TYPE_NAME_BY_TYPE = dicTypeToTypeName; - IntrospectableRouteNames = ImmutableHashSet.Create( + IntrospectablePathNames = ImmutableHashSet.Create( QUERY_TYPE_NAME, MUTATION_TYPE_NAME, SUBSCRIPTION_TYPE_NAME, @@ -445,64 +455,49 @@ public static class Routing public const string PARAMETER_META_NAME = "[parameter]"; /// - /// A phrase, used at the start of a route string, to indicate the route should not map + /// A phrase, used at the start of a path string, to indicate the route should not map /// into the graph. /// public const string NOOP_ROOT = DELIMITER_ROOT_START + "noop" + DELIMITER_ROOT_END; /// - /// A phrase, used at the start of a route string, to indicate its part of the query root. + /// A phrase, used at the start of a path string, to indicate its part of the query root. /// public const string QUERY_ROOT = DELIMITER_ROOT_START + "query" + DELIMITER_ROOT_END; /// - /// A phrase, used at the start of a route string, to indicate its part of the mutation root. + /// A phrase, used at the start of a path string, to indicate its part of the mutation root. /// public const string MUTATION_ROOT = DELIMITER_ROOT_START + "mutation" + DELIMITER_ROOT_END; /// - /// A phrase, used at the start of a route string, to indicate its part of the object type tree. + /// A phrase, used at the start of a path string, to indicate its part of the object type tree. /// public const string TYPE_ROOT = DELIMITER_ROOT_START + "type" + DELIMITER_ROOT_END; /// - /// A phrase, used at the start of a route string, to indicate its part of the global scalar tree. - /// - public const string SCALAR_ROOT = DELIMITER_ROOT_START + "scalar" + DELIMITER_ROOT_END; - - /// - /// A phrase, used at the start of a route string, to indicate that the object is a top level schema. + /// A phrase, used at the start of a path string, to indicate that the object is a top level schema. /// public const string SCHEMA_ROOT = DELIMITER_ROOT_START + "schema" + DELIMITER_ROOT_END; /// - /// A phrase, used at the start of a route string, to indicate its part of the subscription root. + /// A phrase, used at the start of a path string, to indicate its part of the subscription root. /// public const string SUBSCRIPTION_ROOT = DELIMITER_ROOT_START + "subscription" + DELIMITER_ROOT_END; /// - /// A phrase, used at the start of a route string, to indicate its part of the enum type tree. - /// - public const string ENUM_ROOT = DELIMITER_ROOT_START + "enum" + DELIMITER_ROOT_END; - - /// - /// A phrase, used at the start of a route string, to indicate its part of the directive tree. + /// A phrase, used at the start of a path string, to indicate its part of the directive tree. /// public const string DIRECTIVE_ROOT = DELIMITER_ROOT_START + "directive" + DELIMITER_ROOT_END; /// - /// A phrase, used at the start of a route string, to indicate its part of the introspection + /// A phrase, used at the start of a path string, to indicate its part of the introspection /// item collection. /// public const string INTROSPECTION_ROOT = DELIMITER_ROOT_START + "introspection" + DELIMITER_ROOT_END; /// - /// A phrase, used at the start of a route string, to indicate its part of a document. - /// - public const string DOCUMENT_ROOT = DELIMITER_ROOT_START + "document" + DELIMITER_ROOT_END; - - /// - /// The phrase used to seperate individual elements of a route fragement. + /// The phrase used to seperate individual elements of a path fragement. /// public const string PATH_SEPERATOR = "/"; @@ -523,7 +518,7 @@ public static class Routing public const string DELIMITER_ROOT_END = "]"; /// - /// A single string containing all used special characters in objects. + /// A single string containing all used special characters in objects. /// public const string DELIMITERS_ALL = PATH_SEPERATOR + DELIMITER_ROOT_START + DELIMITER_ROOT_END; 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..5a71f59f9 --- /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 PathNotFoundGraphActionResult(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/PathNotFoundGraphActionResult.cs similarity index 51% rename from src/graphql-aspnet/Controllers/ActionResults/RouteNotFoundGraphActionResult.cs rename to src/graphql-aspnet/Controllers/ActionResults/PathNotFoundGraphActionResult.cs index cc417f989..3bbae2578 100644 --- a/src/graphql-aspnet/Controllers/ActionResults/RouteNotFoundGraphActionResult.cs +++ b/src/graphql-aspnet/Controllers/ActionResults/PathNotFoundGraphActionResult.cs @@ -16,70 +16,84 @@ namespace GraphQL.AspNet.Controllers.ActionResults using GraphQL.AspNet.Interfaces.Execution; /// - /// An action result thrown when the requested route was not found or otherwise - /// not navigable. + /// An action result returned when the requested field on a type + /// was not found or was otherwise not navigable. /// /// - public class RouteNotFoundGraphActionResult : IGraphActionResult + public class PathNotFoundGraphActionResult : IGraphActionResult { - private readonly IGraphFieldResolverMethod _invokeDef; + private readonly IGraphFieldResolverMetaData _invokeDef; private readonly Exception _thrownException; private readonly string _message; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The invoked action at the route location. + /// The resolver that was being targeted at item path location. /// The thrown exception that occured when invoking the action, if any. - public RouteNotFoundGraphActionResult(IGraphFieldResolverMethod invokedAction, Exception thrownException = null) + public PathNotFoundGraphActionResult(IGraphFieldResolverMetaData targetResolver, Exception thrownException = null) { - _invokeDef = invokedAction; + _invokeDef = targetResolver; _thrownException = thrownException; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The message. - public RouteNotFoundGraphActionResult(string message) + public PathNotFoundGraphActionResult(string message) { _message = message; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public RouteNotFoundGraphActionResult() + public PathNotFoundGraphActionResult() { } /// 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.", - Constants.ErrorCodes.INVALID_ROUTE, - context.Request.Origin, - _thrownException); + _message, + Constants.ErrorCodes.INVALID_PATH, + context.Request.Origin); + + return Task.CompletedTask; } - else if (!string.IsNullOrWhiteSpace(_message)) + + string fieldName = context.ItemPath?.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, - Constants.ErrorCodes.INVALID_ROUTE, - context.Request.Origin); + $"The field '{fieldName}' was not found or the resolver could not be invoked.", + Constants.ErrorCodes.INVALID_PATH, + context.Request.Origin, + exception); } else { context.Messages.Critical( - "The item was not routable or otherwise not available.", - Constants.ErrorCodes.INVALID_ROUTE, - context.Request.Origin); + $"The field '{fieldName}' was not routable or otherwise not available.", + Constants.ErrorCodes.INVALID_PATH, + 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..cc704cf7a 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,48 +86,99 @@ 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) { switch (ex) { - // catch any other invocation exceptions and treat them as an invalid route + // catch any other invocation exceptions and treat them as an invalid resolver reference // 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 PathNotFoundGraphActionResult(_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..d56d6d0e7 --- /dev/null +++ b/src/graphql-aspnet/Controllers/RuntimeFieldExecutionController.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.Controllers +{ + using GraphQL.AspNet.Attributes; + + /// + /// A special controller instance for executing runtime configured controller + /// actions (e.g. minimal api defined fields and type exensions). This class can only be + /// instantiated by the library runtime. + /// + [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/Global/DeprecatedDirective.cs b/src/graphql-aspnet/Directives/Global/DeprecatedDirective.cs index d032c2261..7e4cded51 100644 --- a/src/graphql-aspnet/Directives/Global/DeprecatedDirective.cs +++ b/src/graphql-aspnet/Directives/Global/DeprecatedDirective.cs @@ -33,7 +33,7 @@ public sealed class DeprecatedDirective : GraphDirective /// IGraphActionResult. [DirectiveLocations(DirectiveLocation.FIELD_DEFINITION | DirectiveLocation.ENUM_VALUE)] public IGraphActionResult Execute( - [FromGraphQL("reason")] + [FromGraphQL("reason", TypeExpression = "Type")] [Description("An optional human-friendly reason explaining why the schema item is being deprecated.")] string reason = "No longer supported") { diff --git a/src/graphql-aspnet/Directives/Global/IncludeDirective.cs b/src/graphql-aspnet/Directives/Global/IncludeDirective.cs index 55fcbf301..a81c85896 100644 --- a/src/graphql-aspnet/Directives/Global/IncludeDirective.cs +++ b/src/graphql-aspnet/Directives/Global/IncludeDirective.cs @@ -32,7 +32,7 @@ public sealed class IncludeDirective : GraphDirective /// IGraphActionResult. [DirectiveLocations(DirectiveLocation.FIELD | DirectiveLocation.FRAGMENT_SPREAD | DirectiveLocation.INLINE_FRAGMENT)] public IGraphActionResult Execute( - [FromGraphQL("if")] + [FromGraphQL("if", TypeExpression = "Type!")] [Description("When true, the field or fragment is included in the query results.")] bool ifArgument) { diff --git a/src/graphql-aspnet/Directives/Global/SkipDirective.cs b/src/graphql-aspnet/Directives/Global/SkipDirective.cs index 90e78f15e..a28489213 100644 --- a/src/graphql-aspnet/Directives/Global/SkipDirective.cs +++ b/src/graphql-aspnet/Directives/Global/SkipDirective.cs @@ -32,7 +32,7 @@ public sealed class SkipDirective : GraphDirective /// IGraphActionResult. [DirectiveLocations(DirectiveLocation.FIELD | DirectiveLocation.FRAGMENT_SPREAD | DirectiveLocation.INLINE_FRAGMENT)] public IGraphActionResult Execute( - [FromGraphQL("if")] + [FromGraphQL("if", TypeExpression = "Type!")] [Description("When true, the field or fragment is excluded from the query results.")] bool ifArgument) { diff --git a/src/graphql-aspnet/Directives/Global/SpecifiedByDirective.cs b/src/graphql-aspnet/Directives/Global/SpecifiedByDirective.cs index 1bb3358f9..fc68eb87b 100644 --- a/src/graphql-aspnet/Directives/Global/SpecifiedByDirective.cs +++ b/src/graphql-aspnet/Directives/Global/SpecifiedByDirective.cs @@ -62,7 +62,7 @@ public IGraphActionResult Execute( if (url == null) { throw new GraphTypeDeclarationException( - $"A non-null url must be provided with @{Constants.ReservedNames.SPECIFIED_BY_DIRECTIVE}. (Target: {scalarItem.Route.Path})"); + $"A non-null url must be provided with @{Constants.ReservedNames.SPECIFIED_BY_DIRECTIVE}. (Target: {scalarItem.ItemPath.Path})"); } scalarItem.SpecifiedByUrl = url; 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..506349617 --- /dev/null +++ b/src/graphql-aspnet/Directives/RuntimeExecutionDirective.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.Directives +{ + using GraphQL.AspNet.Common.Generics; + 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..286b7ea3c 100644 --- a/src/graphql-aspnet/Engine/DefaultGraphEventLogger.cs +++ b/src/graphql-aspnet/Engine/DefaultGraphEventLogger.cs @@ -85,7 +85,7 @@ public virtual void SchemaPipelineRegistered(ISchemaPipeline pipleine) } /// - public virtual void SchemaRouteRegistered(string routePath) + public virtual void SchemaUrlRouteRegistered(string routePath) where TSchema : class, ISchema { if (!this.IsEnabled(LogLevel.Debug)) @@ -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..1be393b36 --- /dev/null +++ b/src/graphql-aspnet/Engine/DefaultGraphQLSchemaFactory_Controllers.cs @@ -0,0 +1,247 @@ +// ************************************************************* +// 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.Execution; + using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Interfaces.Internal; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas; + 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.ItemPath.Root.ToGraphOperationType(); + if (this.Schema.Configuration.DeclarationOptions.AllowedOperations.Contains(operation)) + { + this.EnsureGraphOperationType(operation); + var parentField = this.AddOrRetrieveVirtualTypeOwner(action); + this.AddActionAsField(parentField, action); + } + else + { + throw new ArgumentOutOfRangeException( + nameof(action), + $"The '{action.InternalName}' action's operation root ({action.ItemPath.Root}) is not " + + $"allowed by the target schema (Name: {this.Schema.Name})."); + } + } + + /// + /// Inspects the field template and ensures that any intermediate, virtual types (and fields) + /// are accounted for. This method then returns a reference to the intermediate graph type + /// this action should be added to as a field reference. + /// + /// The action. + /// IGraphField. + protected virtual IObjectGraphType AddOrRetrieveVirtualTypeOwner(IGraphFieldTemplate action) + { + var pathSegments = action.ItemPath.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.ItemPath.Root.ToGraphOperationType()]; + + for (var i = 0; i < pathSegments.Count; i++) + { + var segment = pathSegments[i]; + + (var virtualField, var virtualFieldGaphType) = this.CreateVirtualField( + parentType, + segment.Name, + segment, + i == 0 ? action.Parent : null); + + // its possible this field and its graph type already exist because its + // been referenced in path segments elsewhere on this controller. + // If this is the case reference the already added version on the schema + // then perform checks to ensure that the value in the schema matches expectations + // for this newly created virtual field. + // Once verified just use the existing field and continue down the tree. + if (parentType.Fields.ContainsKey(virtualField.Name)) + { + var existingChildField = parentType[virtualField.Name]; + var foundType = Schema.KnownTypes.FindGraphType(existingChildField.TypeExpression.TypeName); + + var ogt = foundType as IObjectGraphType; + if (ogt != null) + { + if (ogt.IsVirtual) + { + parentType = ogt; + continue; + } + + throw new GraphTypeDeclarationException( + $"The action '{action.ItemPath}' attempted to nest itself under the {foundType.Kind} graph type '{foundType.Name}', which is returned by " + + $"the path '{existingChildField.ItemPath}'. Actions can only be added to virtual graph types created by their parent controller."); + } + + if (foundType != null) + { + throw new GraphTypeDeclarationException( + $"The action '{action.ItemPath.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.ItemPath.Path}' attempted to nest itself under the field '{existingChildField.ItemPath}' but no graph type was found " + + "that matches its type."); + } + } + + // field doesn't already exist on the schema + // add it and continue down the tree + parentType.Extend(virtualField); + this.Schema.KnownTypes.EnsureGraphType(virtualFieldGaphType); + this.EnsureDependents(virtualField); + parentType = virtualFieldGaphType; + } + + return parentType; + } + + /// + /// Generates a fully qualified field and an associated graph type that can be added to + /// the schema to represent a segment in a field path definition added by the developer. + /// + /// 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. + /// A reference to the new field and the graph type created to represent the fields returned value. + protected virtual (VirtualGraphField VirtualField, IObjectGraphType VirtualFieldGraphType) CreateVirtualField( + IObjectGraphType parentType, + string fieldName, + ItemPath pathTemplate, + ISchemaItemTemplate definition = null) + { + var returnedGraphType = VirtualObjectGraphType.FromControllerFieldPathTemplate(pathTemplate); + + returnedGraphType = this.Schema + .Configuration + .DeclarationOptions + .SchemaFormatStrategy? + .ApplySchemaItemRules( + this.Schema.Configuration, + returnedGraphType) ?? returnedGraphType; + + var typeExpression = GraphTypeExpression.FromDeclaration(returnedGraphType.Name); + + var childField = new VirtualGraphField( + fieldName, + pathTemplate, + typeExpression) + { + IsDepreciated = false, + DepreciationReason = string.Empty, + Description = definition?.Description ?? string.Empty, + }; + + // configure the field for the schema + // and add it to its appropriate parent + childField = this.Schema + .Configuration + .DeclarationOptions + .SchemaFormatStrategy? + .ApplySchemaItemRules( + this.Schema.Configuration, + childField) ?? childField; + + return (childField, returnedGraphType); + } + + /// + /// 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) + { + var field = this.Schema + .Configuration + .DeclarationOptions + .SchemaFormatStrategy? + .ApplySchemaItemRules( + this.Schema.Configuration, + fieldResult.Field) ?? fieldResult.Field; + + 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(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..b5ba7dba2 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 ItemPath ItemPath => _directiveRequest?.Directive.ItemPath; + + /// + 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..a80501392 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 ItemPath ItemPath => _fieldRequest?.Field.ItemPath; + + /// + 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/FieldValidationContext.cs b/src/graphql-aspnet/Execution/Contexts/FieldValidationContext.cs index b2447e4ea..02d8fcc32 100644 --- a/src/graphql-aspnet/Execution/Contexts/FieldValidationContext.cs +++ b/src/graphql-aspnet/Execution/Contexts/FieldValidationContext.cs @@ -79,7 +79,7 @@ public IEnumerable CreateChildContexts() /// Gets the schema path for the field being evaluated. /// /// The path. - public string FieldPath => this.Field?.Route.Path; + public string FieldPath => this.Field?.ItemPath.Path; /// /// Gets the collection of messages tracked by this context. diff --git a/src/graphql-aspnet/Execution/Contexts/GraphFieldExecutionContext.cs b/src/graphql-aspnet/Execution/Contexts/GraphFieldExecutionContext.cs index 7f37932f3..4f977d07f 100644 --- a/src/graphql-aspnet/Execution/Contexts/GraphFieldExecutionContext.cs +++ b/src/graphql-aspnet/Execution/Contexts/GraphFieldExecutionContext.cs @@ -22,7 +22,7 @@ namespace GraphQL.AspNet.Execution.Contexts /// /// A middleware context targeting the field execution pipeline. /// - [DebuggerDisplay("Field: {Field.Route.Path} (Mode = {Field.Mode})")] + [DebuggerDisplay("Field: {Field.ItemPath.Path} (Mode = {Field.Mode})")] public class GraphFieldExecutionContext : MiddlewareExecutionContextBase { /// diff --git a/src/graphql-aspnet/Execution/Contexts/SchemaItemResolutionContext.cs b/src/graphql-aspnet/Execution/Contexts/SchemaItemResolutionContext.cs index 14eb31d2d..7c73e3234 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 path to the item being resolved. + /// + /// The path of the item being resolved. + public abstract ItemPath ItemPath { 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/Contexts/SchemaItemSecurityChallengeContext.cs b/src/graphql-aspnet/Execution/Contexts/SchemaItemSecurityChallengeContext.cs index a9c2e1fa6..30736e046 100644 --- a/src/graphql-aspnet/Execution/Contexts/SchemaItemSecurityChallengeContext.cs +++ b/src/graphql-aspnet/Execution/Contexts/SchemaItemSecurityChallengeContext.cs @@ -21,7 +21,7 @@ namespace GraphQL.AspNet.Execution.Contexts /// A context for handling a security challenge of a single schema item /// issued through the authorization pipeline. /// - [DebuggerDisplay("Auth Context, Item: {SecureSchemaItem.Route.Path}")] + [DebuggerDisplay("Auth Context, Item: {SecureSchemaItem.ItemPath.Path}")] public class SchemaItemSecurityChallengeContext : MiddlewareExecutionContextBase { /// diff --git a/src/graphql-aspnet/Execution/DirectiveProcessorTypeSystem.cs b/src/graphql-aspnet/Execution/DirectiveProcessorTypeSystem.cs index 349c7b488..5daaf1fe0 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(); @@ -98,7 +107,7 @@ private bool ApplyDirectivesToItem(TSchema schema, ISchemaItem item) var failureMessage = $"Type System Directive Invocation Failure. " + $"The directive type named '{directiveName}' " + - $"does not represent a valid directive on the target schema. (Target: '{item.Route.Path}', Schema: {schema.Name})"; + $"does not represent a valid directive on the target schema. (Target: '{item.ItemPath.Path}', Schema: {schema.Name})"; throw new SchemaConfigurationException(failureMessage); } @@ -111,7 +120,7 @@ private bool ApplyDirectivesToItem(TSchema schema, ISchemaItem item) { throw new SchemaConfigurationException( $"Unable to construct the schema '{schema.Name}'. " + - $"The non-repeatable directive @{targetDirective.Name} is repeated on the schema item '{item.Name}'. (Target: '{item.Route.Path}', Schema: {schema.Name})"); + $"The non-repeatable directive @{targetDirective.Name} is repeated on the schema item '{item.Name}'. (Target: '{item.ItemPath.Path}', Schema: {schema.Name})"); } } @@ -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..56a81f074 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?.ItemPath.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?.ItemPath.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..132ea9fad 100644 --- a/src/graphql-aspnet/Execution/ExecutionExtensionMethods.cs +++ b/src/graphql-aspnet/Execution/ExecutionExtensionMethods.cs @@ -13,9 +13,9 @@ 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; + using PathConstants = GraphQL.AspNet.Constants.Routing; /// /// Helper methods useful during document execution. @@ -27,30 +27,27 @@ public static class ExecutionExtensionMethods /// /// The value to convert. /// System.String. - internal static string ToRouteRoot(this SchemaItemCollections value) + internal static string ToPathRootString(this ItemPathRoots value) { switch (value) { - case SchemaItemCollections.Query: - return RouteConstants.QUERY_ROOT; + case ItemPathRoots.Query: + return PathConstants.QUERY_ROOT; - case SchemaItemCollections.Mutation: - return RouteConstants.MUTATION_ROOT; + case ItemPathRoots.Mutation: + return PathConstants.MUTATION_ROOT; - case SchemaItemCollections.Subscription: - return RouteConstants.SUBSCRIPTION_ROOT; + case ItemPathRoots.Subscription: + return PathConstants.SUBSCRIPTION_ROOT; - case SchemaItemCollections.Types: - return RouteConstants.TYPE_ROOT; + case ItemPathRoots.Types: + return PathConstants.TYPE_ROOT; - case SchemaItemCollections.Enums: - return RouteConstants.ENUM_ROOT; - - case SchemaItemCollections.Directives: - return RouteConstants.DIRECTIVE_ROOT; + case ItemPathRoots.Directives: + return PathConstants.DIRECTIVE_ROOT; default: - return RouteConstants.NOOP_ROOT; + return PathConstants.NOOP_ROOT; } } diff --git a/src/graphql-aspnet/Execution/FieldResolution/FieldDataItem.cs b/src/graphql-aspnet/Execution/FieldResolution/FieldDataItem.cs index bfe6f39ed..d235d89dc 100644 --- a/src/graphql-aspnet/Execution/FieldResolution/FieldDataItem.cs +++ b/src/graphql-aspnet/Execution/FieldResolution/FieldDataItem.cs @@ -17,14 +17,13 @@ namespace GraphQL.AspNet.Execution.FieldResolution using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution.Exceptions; - using GraphQL.AspNet.Interfaces.Execution; - using GraphQL.AspNet.Execution.Source; using GraphQL.AspNet.Execution.Response; + using GraphQL.AspNet.Execution.Source; + using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Interfaces.Execution.Response; 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. @@ -70,7 +69,7 @@ public FieldDataItem AddChildField(IGraphFieldInvocationContext childInvocationC if (this.IsListField) { throw new GraphExecutionException( - $"The field {this.FieldContext.Field.Route.Path} represents " + + $"The field {this.FieldContext.Field.ItemPath.Path} represents " + "a list of items, a child field context cannot be directly added to it."); } @@ -94,7 +93,7 @@ public void AddListItem(FieldDataItem childListItem) if (!this.IsListField) { throw new GraphExecutionException( - $"The field {this.FieldContext.Field.Route.Path} represents " + + $"The field {this.FieldContext.Field.ItemPath.Path} represents " + "a collection of fields, a child list item cannot be directly added to it."); } @@ -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 @@ -417,7 +417,24 @@ private bool GenerateSingleValueResult(out IQueryResponseItem result) { var type = resultData.GetType(); var graphType = this.Schema.KnownTypes.FindGraphType(type); - if (graphType is IScalarGraphType sgt) + + if (graphType is IEnumGraphType egt) + { + // convert the enum to its appropriate label + // from the graph type + var enumValue = egt.Values.FindByEnumValue(resultData); + if (enumValue == null) + { + throw new GraphExecutionException( + $"The data value of '{resultData}' " + + $"was expected to be of enum graph type '{egt.Name}' but could not be " + + $"found as a valid enum value registered with the schema.", + this.FieldContext.FieldDocumentPart.Origin); + } + + resultData = enumValue.Name; + } + else if (graphType is IScalarGraphType sgt) { resultData = sgt.Serialize(resultData); } diff --git a/src/graphql-aspnet/Execution/FieldSourceCollection.cs b/src/graphql-aspnet/Execution/FieldSourceCollection.cs index f1ed3786b..8baa97286 100644 --- a/src/graphql-aspnet/Execution/FieldSourceCollection.cs +++ b/src/graphql-aspnet/Execution/FieldSourceCollection.cs @@ -14,16 +14,16 @@ 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; /// /// A collection of objects supplied to a pipeline that can act as an input object for /// a . /// - public class FieldSourceCollection : IEnumerable> + public class FieldSourceCollection : IEnumerable> { - private readonly Dictionary _actionSources; + private readonly Dictionary _actionSources; private readonly GraphFieldSource _sourceTemplateTypes; /// @@ -33,7 +33,7 @@ public class FieldSourceCollection : IEnumerable public FieldSourceCollection(GraphFieldSource sourcableTemplateTypes = GraphFieldSource.Action) { - _actionSources = new Dictionary(SchemaItemPathComparer.Instance); + _actionSources = new Dictionary(ItemPathComparer.Instance); _sourceTemplateTypes = sourcableTemplateTypes; } @@ -47,10 +47,10 @@ public bool TryRetrieveSource(IGraphField field, out object result) { Validation.ThrowIfNull(field, nameof(field)); result = null; - if (field == null || !_actionSources.ContainsKey(field.Route)) + if (field == null || !_actionSources.ContainsKey(field.ItemPath)) return false; - result = _actionSources[field.Route]; + result = _actionSources[field.ItemPath]; return true; } @@ -66,10 +66,10 @@ public void AddSource(IGraphField field, object sourceData) { lock (_actionSources) { - if (_actionSources.ContainsKey(field.Route)) - _actionSources[field.Route] = sourceData; + if (_actionSources.ContainsKey(field.ItemPath)) + _actionSources[field.ItemPath] = sourceData; else - _actionSources.Add(field.Route, sourceData); + _actionSources.Add(field.ItemPath, sourceData); } } } @@ -81,14 +81,14 @@ public void AddSource(IGraphField field, object sourceData) /// true if the specified field has a defined value; otherwise, false. public bool ContainsKey(IGraphField field) { - return field?.Route != null && _actionSources.ContainsKey(field.Route); + return field?.ItemPath != null && _actionSources.ContainsKey(field.ItemPath); } /// /// Returns an enumerator that iterates through the collection. /// /// An enumerator that can be used to iterate through the collection. - public IEnumerator> GetEnumerator() + public IEnumerator> GetEnumerator() { return _actionSources.GetEnumerator(); } 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/GraphCollection.cs b/src/graphql-aspnet/Execution/ItemPathRoots.cs similarity index 77% rename from src/graphql-aspnet/Execution/GraphCollection.cs rename to src/graphql-aspnet/Execution/ItemPathRoots.cs index 8270c92d6..06c635891 100644 --- a/src/graphql-aspnet/Execution/GraphCollection.cs +++ b/src/graphql-aspnet/Execution/ItemPathRoots.cs @@ -14,16 +14,13 @@ namespace GraphQL.AspNet.Execution /// /// An enumeration depicting the various collections of items tracked by graphql. /// - public enum SchemaItemCollections : int + public enum ItemPathRoots : int { - // negative numbers represent internally defined collections - // (not part of the graph schema) - Document = -70, + // negative numbers represent internally defined roots + // no mappable by developer code Introspection = -60, Schemas = -50, Directives = -40, - Scalars = -30, - Enums = -20, Types = -10, Unknown = GraphOperationType.Unknown, Query = GraphOperationType.Query, diff --git a/src/graphql-aspnet/Execution/GraphCollectionExtensions.cs b/src/graphql-aspnet/Execution/ItemPathRootsExtensions.cs similarity index 71% rename from src/graphql-aspnet/Execution/GraphCollectionExtensions.cs rename to src/graphql-aspnet/Execution/ItemPathRootsExtensions.cs index a924184a9..a1253b354 100644 --- a/src/graphql-aspnet/Execution/GraphCollectionExtensions.cs +++ b/src/graphql-aspnet/Execution/ItemPathRootsExtensions.cs @@ -11,27 +11,27 @@ namespace GraphQL.AspNet.Execution using GraphQL.AspNet.Schemas.TypeSystem; /// - /// Extension methods for working with enumeration. + /// Extension methods for working with enumeration. /// - internal static class GraphCollectionExtensions + internal static class ItemPathRootsExtensions { /// - /// Converts the given value to + /// Converts the given value to /// its equivilant value, if one exists. /// /// The collection item to convert. /// GraphOperationType. - public static GraphOperationType ToGraphOperationType(this SchemaItemCollections collectionItem) + public static GraphOperationType ToGraphOperationType(this ItemPathRoots collectionItem) { switch (collectionItem) { - case SchemaItemCollections.Query: + case ItemPathRoots.Query: return GraphOperationType.Query; - case SchemaItemCollections.Mutation: + case ItemPathRoots.Mutation: return GraphOperationType.Mutation; - case SchemaItemCollections.Subscription: + case ItemPathRoots.Subscription: return GraphOperationType.Subscription; default: 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/Metrics/ApolloTracingMetricsV1.cs b/src/graphql-aspnet/Execution/Metrics/ApolloTracingMetricsV1.cs index cf68ef8f6..d5c939bd4 100644 --- a/src/graphql-aspnet/Execution/Metrics/ApolloTracingMetricsV1.cs +++ b/src/graphql-aspnet/Execution/Metrics/ApolloTracingMetricsV1.cs @@ -183,7 +183,7 @@ private IQueryResponseFieldSet GenerateExecutionResult() if (timeEntry.Key?.Request?.Field?.Mode == FieldResolutionMode.Batch) { - var parentName = timeEntry.Key?.Request?.Field?.Route?.Parent?.Name; + var parentName = timeEntry.Key?.Request?.Field?.ItemPath?.Parent?.Name; if (!string.IsNullOrWhiteSpace(parentName)) parentType = _schema.KnownTypes.FindGraphType(parentName); } diff --git a/src/graphql-aspnet/Execution/ParameterModifiers.cs b/src/graphql-aspnet/Execution/ParameterModifiers.cs new file mode 100644 index 000000000..0f0455f89 --- /dev/null +++ b/src/graphql-aspnet/Execution/ParameterModifiers.cs @@ -0,0 +1,74 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Execution +{ + using System; + + /// + /// A set of modifiers that can be assigned to individual parameters on methods to modify their behavior + /// during execution. + /// + public enum ParameterModifiers + { + // 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 declared to contain the result of the value returned from the parent field's resolver. Applicable to + /// type extension and batch extension action methods. + /// + ParentFieldResult, + + /// + /// This parameter is declared to be a cancellation token + /// governing the request or the default token if none was supplied on said request. + /// + CancellationToken, + + /// + /// 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. + /// + HttpContext, + } +} \ No newline at end of file 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..3f1c4dc04 --- /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.Execution; + using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Schemas; + + /// + /// 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, + ParameterModifiers 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 ParameterModifiers 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 83% rename from src/graphql-aspnet/Internal/Resolvers/GraphControllerActionResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/GraphControllerActionResolver.cs index 9fac1eb0e..2617b32f4 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? @@ -60,15 +58,15 @@ public async Task ResolveAsync(FieldResolutionContext context, CancellationToken if (controller == null) { - result = new RouteNotFoundGraphActionResult( - $"The controller assigned to process the field '{context.Request.InvocationContext.Field.Route.Path}' " + + result = new PathNotFoundGraphActionResult( + $"The controller assigned to process the field '{context.Request.InvocationContext.Field.ItemPath.Path}' " + "was not found."); } else if (isolationManager == null) { throw new GraphExecutionException( $"No {nameof(IGraphQLFieldResolverIsolationManager)} was configured for the request. " + - $"Unable to determine the isolation requirements for the resolver of field '{context.Request.InvocationContext.Field.Route.Path}'"); + $"Unable to determine the isolation requirements for the resolver of field '{context.Request.InvocationContext.Field.ItemPath.Path}'"); } else { @@ -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/Execution/Resolvers/GraphControllerVirtualFieldResolver.cs b/src/graphql-aspnet/Execution/Resolvers/GraphControllerVirtualFieldResolver.cs new file mode 100644 index 000000000..759538679 --- /dev/null +++ b/src/graphql-aspnet/Execution/Resolvers/GraphControllerVirtualFieldResolver.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.Execution.Resolvers +{ + using System.Threading; + using System.Threading.Tasks; + using GraphQL.AspNet.Common; + using GraphQL.AspNet.Execution.Contexts; + using GraphQL.AspNet.Interfaces.Execution; + using GraphQL.AspNet.Schemas.Structural; + + /// + /// A special resolver for resolving intermediate fields created from controller action + /// path templates. + /// + /// + internal class GraphControllerVirtualFieldResolver : IGraphFieldResolver + { + private readonly VirtualResolvedObject _dataObject; + + /// + /// Initializes a new instance of the class. + /// + /// The data object instance to return as the "result" of resolving this field. If not supplied a + /// new instance of will be returned. + public GraphControllerVirtualFieldResolver(VirtualResolvedObject dataObject) + { + Validation.ThrowIfNull(dataObject, nameof(dataObject)); + _dataObject = dataObject; + + this.MetaData = InternalFieldResolverMetaData.CreateMetadata(this.GetType()); + } + + /// + public Task ResolveAsync(FieldResolutionContext context, CancellationToken cancelToken = default) + { + context.Result = _dataObject; + return Task.CompletedTask; + } + + /// + 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 76% rename from src/graphql-aspnet/Internal/Resolvers/GraphDirectiveActionResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/GraphDirectiveActionResolver.cs index db5213a48..a5458a7fd 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) { @@ -82,8 +82,8 @@ public async Task ResolveAsync(DirectiveResolutionContext context, CancellationT if (directive == null) { - result = new RouteNotFoundGraphActionResult( - $"The directive '{_directiveTemplate.InternalFullName}' " + + result = new PathNotFoundGraphActionResult( + $"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 2bb8e8399..6efd5b2f3 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 75% rename from src/graphql-aspnet/Internal/Resolvers/ObjectMethodGraphFieldResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/ObjectMethodGraphFieldResolver.cs index 2ce9f09fc..88ded5198 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.ItemPath.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.ItemPath.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; @@ -84,7 +85,7 @@ public virtual async Task ResolveAsync(FieldResolutionContext context, Cancellat { throw new GraphExecutionException( $"No {nameof(IGraphQLFieldResolverIsolationManager)} was configured for the request. " + - $"Unable to determine the isolation requirements for the resolver of field '{context.Request.InvocationContext.Field.Route.Path}'"); + $"Unable to determine the isolation requirements for the resolver of field '{context.Request.InvocationContext.Field.ItemPath.Path}'"); } var shouldIsolate = isolationManager.ShouldIsolate(context.Schema, context.Request.Field.FieldSource); @@ -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.ItemPath.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 73% rename from src/graphql-aspnet/Internal/Resolvers/ObjectPropertyGraphFieldResolver.cs rename to src/graphql-aspnet/Execution/Resolvers/ObjectPropertyGraphFieldResolver.cs index 1d4f6a630..2482164c7 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.ItemPath.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.ItemPath.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.ItemPath.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; @@ -102,7 +100,7 @@ public async Task ResolveAsync(FieldResolutionContext context, CancellationToken { throw new GraphExecutionException( $"No {nameof(IGraphQLFieldResolverIsolationManager)} was configured for the request. " + - $"Unable to determine the isolation requirements for the resolver of field '{context.Request.InvocationContext.Field.Route.Path}'"); + $"Unable to determine the isolation requirements for the resolver of field '{context.Request.InvocationContext.Field.ItemPath.Path}'"); } var shouldIsolate = isolationManager.ShouldIsolate(context.Schema, context.Request.Field.FieldSource); @@ -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.ItemPath.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.ItemPath.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.ItemPath.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/Response/ResponseWriterBase.cs b/src/graphql-aspnet/Execution/Response/ResponseWriterBase.cs index b5efeb8a8..002336e50 100644 --- a/src/graphql-aspnet/Execution/Response/ResponseWriterBase.cs +++ b/src/graphql-aspnet/Execution/Response/ResponseWriterBase.cs @@ -15,10 +15,10 @@ namespace GraphQL.AspNet.Execution.Response using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Execution.Source; - using GraphQL.AspNet.Configuration.Formatting; + using GraphQL.AspNet.Interfaces.Configuration; using GraphQL.AspNet.Interfaces.Execution; - using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Interfaces.Execution.Response; + using GraphQL.AspNet.Interfaces.Schema; /// /// A class containing many shared methods for writing all or part of a @@ -45,7 +45,7 @@ protected ResponseWriterBase(ISchema schema) { Validation.ThrowIfNull(schema, nameof(schema)); this.TimeLocalizer = schema.Configuration.ResponseOptions.TimeStampLocalizer; - this.NameFormatter = schema.Configuration.DeclarationOptions.GraphNamingFormatter; + this.Formatter = schema.Configuration.DeclarationOptions.SchemaFormatStrategy; this.SerializerSettings = _serializerSettings; } @@ -97,7 +97,7 @@ protected virtual void WriteMessage(Utf8JsonWriter writer, IGraphMessage message var timestamp = this.TimeLocalizer?.Invoke(message.TimeStamp) ?? message.TimeStamp; this.WriteLeaf(writer, "timestamp", timestamp); - this.WriteLeaf(writer, "severity", message.Severity); + this.WriteLeaf(writer, "severity", message.Severity.ToString().ToUpperInvariant()); if (message.MetaData != null && message.MetaData.Count > 0) { @@ -248,7 +248,7 @@ protected virtual void WriteLeafValue(Utf8JsonWriter writer, object value, bool } if (value.GetType().IsEnum) - value = this.NameFormatter.FormatEnumValueName(value.ToString()); + value = value.ToString(); switch (value) { @@ -312,7 +312,6 @@ protected virtual void WriteLeafValue(Utf8JsonWriter writer, object value, bool writer.WriteNumberValue(ush); break; -#if NET6_0_OR_GREATER case DateOnly dateOnly: this.WritePreEncodedStringValue(writer, dateOnly.ToRfc3339String()); break; @@ -320,7 +319,6 @@ protected virtual void WriteLeafValue(Utf8JsonWriter writer, object value, bool case TimeOnly timeOnly: this.WritePreEncodedStringValue(writer, timeOnly.ToRfc3339String()); break; -#endif default: if (convertUnsupportedToString) @@ -374,7 +372,7 @@ protected virtual void WritePreEncodedString(Utf8JsonWriter writer, string prope /// Gets the formatter used when writing graph names to the stream. /// /// The name formatter. - protected virtual GraphNameFormatter NameFormatter { get; } + protected virtual ISchemaFormatStrategy Formatter { get; } /// /// Gets a set of settings to use whenever the serializer needs to be directly invoked. 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..be8509f38 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 @@ -128,7 +128,7 @@ private bool CompareArguments( this.ValidationError( context, $"Invalid Directive Invocation. The directive '{context.Directive.Name}' " + - $"requires that the argument '{suppliedArg.Name}' be coercable to type '{directiveArg.TypeExpression.CloneTo(argType.Name)}'. " + + $"requires that the argument '{suppliedArg.Name}' be coercable to type '{directiveArg.TypeExpression.Clone(argType.Name)}'. " + $"See exception for details.", exception); completedSuccessfully = false; @@ -142,7 +142,7 @@ private bool CompareArguments( this.ValidationError( context, $"Invalid Directive Invocation. The argument value for '{directiveArg.Name}' on directive '{context.Directive.Name}' " + - $"cannot be coerced to '{directiveArg.TypeExpression.CloneTo(argType.Name)}'"); + $"cannot be coerced to '{directiveArg.TypeExpression.Clone(argType.Name)}'"); completedSuccessfully = false; } 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_2_3_1_1_SubscriptionsRequire1EncounteredSubscriptionField.cs b/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/QueryOperationSteps/Rule_5_2_3_1_1_SubscriptionsRequire1EncounteredSubscriptionField.cs index 2414cdcea..4a48f8e51 100644 --- a/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/QueryOperationSteps/Rule_5_2_3_1_1_SubscriptionsRequire1EncounteredSubscriptionField.cs +++ b/src/graphql-aspnet/Execution/RulesEngine/RuleSets/DocumentValidation/QueryOperationSteps/Rule_5_2_3_1_1_SubscriptionsRequire1EncounteredSubscriptionField.cs @@ -16,7 +16,7 @@ namespace GraphQL.AspNet.Execution.RulesEngine.RuleSets.DocumentValidation.Query using GraphQL.AspNet.Schemas.TypeSystem; /// - /// An extension on 5.2.3.1 to ensure that the virtual fields registered by controllers and routes + /// An extension on 5.2.3.1 to ensure that the virtual fields registered by controllers /// can exist along side the first "top-level" encountered subscription field. /// internal class Rule_5_2_3_1_1_SubscriptionsRequire1EncounteredSubscriptionField @@ -44,8 +44,8 @@ public override bool Execute(DocumentValidationContext context) ----------------------------------------------------- subscription { ctrlPath { - routePath1 { - routePath2 { + itemPath1 { + itemPath2 { subscriptionAction { } } } @@ -61,8 +61,8 @@ public override bool Execute(DocumentValidationContext context) ----------------------------------------------------- subscription { ctrlPath { - routePath1 { - routePath2 { + itemPath1 { + itemPath2 { subscriptionAction1 { } subscriptionAction2 { } // two subscription actions encountered (must be 1) } @@ -72,8 +72,8 @@ public override bool Execute(DocumentValidationContext context) subscription { ctrlPath { - routePath1 { - routePath2 { + itemPath1 { + itemPath2 { queryActionField { } // not a subscription field } } @@ -82,8 +82,8 @@ public override bool Execute(DocumentValidationContext context) subscription { controller { - routePath1 { - routePath2 { + itemPath1 { + itemPath2 { subscriptionActionField2 { } } } 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..9d8db2a3f 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; /// @@ -86,7 +85,7 @@ public override bool Execute(FieldValidationContext context) // however, use the type name of the actual data object, not the graph type itself // we only want to check the type expression wrappers in this step var rootSourceType = GraphValidation.EliminateWrappersFromCoreType(dataObject.GetType()); - var mangledTypeExpression = dataItemTypeExpression.CloneTo(rootSourceType.Name); + var mangledTypeExpression = dataItemTypeExpression.Clone(rootSourceType.Name); if (!mangledTypeExpression.Matches(dataObject)) { @@ -96,7 +95,7 @@ public override bool Execute(FieldValidationContext context) // Fake the type expression against the real graph type // this step only validates the meta graph types the actual type may be different (but castable to the concrete // type of the graphType). Don't confuse the user in this step. - actualExpression = actualExpression.CloneTo(expectedGraphType.Name); + actualExpression = actualExpression.Clone(expectedGraphType.Name); // 6.4.3 section 4 & 5 this.ValidationError( 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..7de2d8c34 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; /// @@ -110,7 +110,7 @@ public override bool Execute(FieldValidationContext context) this.ValidationError( context, - $"A field resolver for '{context.Field.Route.Path}' generated a result " + + $"A field resolver for '{context.Field.ItemPath.Path}' generated a result " + "not compatible with the field's allowed graph types. See exception for details.", new GraphExecutionException(exceptionText)); 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..445d709a7 100644 --- a/src/graphql-aspnet/Interfaces/Configuration/ISchemaDeclarationConfiguration.cs +++ b/src/graphql-aspnet/Interfaces/Configuration/ISchemaDeclarationConfiguration.cs @@ -12,8 +12,6 @@ namespace GraphQL.AspNet.Interfaces.Configuration using System.Collections.Generic; 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; @@ -43,19 +41,17 @@ public interface ISchemaDeclarationConfiguration TemplateDeclarationRequirements FieldDeclarationRequirements { get; } /// - /// Gets an object used to format the declared names in your C# code as various items in the type system - /// for this . - /// - /// - /// - /// Defaults: - /// Graph Type Names : ProperCase. - /// Field Names: camelCase. - /// Enum Values: UPPERCASE. - /// + /// 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 instance of a format strategy object that will apply custom + /// formats and other rules to a newly created schema item just before its added to a schema. /// - /// The graph naming formatter. - GraphNameFormatter GraphNamingFormatter { get; } + /// The schema item formatter. + ISchemaFormatStrategy SchemaFormatStrategy { get; } /// /// Gets the set of operation types that can be registered to this schema. 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/Configuration/ISchemaFormatStrategy.cs b/src/graphql-aspnet/Interfaces/Configuration/ISchemaFormatStrategy.cs new file mode 100644 index 000000000..fb85dfc53 --- /dev/null +++ b/src/graphql-aspnet/Interfaces/Configuration/ISchemaFormatStrategy.cs @@ -0,0 +1,49 @@ +// ************************************************************* +// 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; + + /// + /// A strategy that defines specific rules and overrides to apply to schema items as they + /// are generated and added to a schema being built. + /// + public interface ISchemaFormatStrategy + { + /// + /// Applies custom, programatic level formatting rules (name formatting, nullability standards etc.) to the + /// target schema item before it is added to the schema. + /// + /// + /// + /// The is not yet added to the schema + /// and may not be be fully populated. + /// For example, a field will not yet have a parent assignment and graph types + /// will not have any rendered fields when this method is processed. + /// + /// + /// Schema items are immutable for any essential data values. Use the various + /// implementations of .Clone() to create copies of a schema item with + /// updated values. + /// + /// + /// Applied changes are accepted as is with no further validation, ensure your + /// formats are consistant with the target schema's expectations. + /// + /// + /// The type of schema item being formatted. + /// The complete configuration settings setup + /// at application start for the target schema. + /// The schema item to apply formatting rules to. + /// The updated or altered schema item instance that was formatted. + T ApplySchemaItemRules(ISchemaConfiguration configuration, T schemaItem) + where T : ISchemaItem; + } +} diff --git a/src/graphql-aspnet/Interfaces/Configuration/ISchemaInjector.cs b/src/graphql-aspnet/Interfaces/Configuration/ISchemaInjector.cs index 81e6d2883..460bad9eb 100644 --- a/src/graphql-aspnet/Interfaces/Configuration/ISchemaInjector.cs +++ b/src/graphql-aspnet/Interfaces/Configuration/ISchemaInjector.cs @@ -32,7 +32,7 @@ public interface ISchemaInjector /// /// Performs a final setup and initial cache build for the schema in the app domain. Also registers - /// the route to the application if required. + /// the url to the application if required. /// /// The application builder. void UseSchema(IApplicationBuilder appBuilder); diff --git a/src/graphql-aspnet/Interfaces/Configuration/ISchemaItemFormatRule.cs b/src/graphql-aspnet/Interfaces/Configuration/ISchemaItemFormatRule.cs new file mode 100644 index 000000000..e1caaba8c --- /dev/null +++ b/src/graphql-aspnet/Interfaces/Configuration/ISchemaItemFormatRule.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.Interfaces.Configuration +{ + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// A rule that defines a specific set of business logic that can be executed against + /// a schema item just before its inserted intoa schema. + /// + public interface ISchemaItemFormatRule + { + /// + /// Executes this rule against the target schema item + /// + /// The actual type of the schema item being processed. + /// The schema item. + /// The original schema item or a modified version of the schema item. + public TSchemaItemType Execute(TSchemaItemType schemaItem) + where TSchemaItemType : ISchemaItem; + } +} 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..2ae824ed7 --- /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.Execution; + using GraphQL.AspNet.Interfaces.Schema; + + /// + /// 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. + ParameterModifiers 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..03467878a 100644 --- a/src/graphql-aspnet/Interfaces/Internal/IGraphArgumentTemplate.cs +++ b/src/graphql-aspnet/Interfaces/Internal/IGraphArgumentTemplate.cs @@ -10,6 +10,8 @@ namespace GraphQL.AspNet.Interfaces.Internal { using System; + using GraphQL.AspNet.Execution; + using GraphQL.AspNet.Interfaces.Execution; using GraphQL.AspNet.Schemas; using GraphQL.AspNet.Schemas.TypeSystem; @@ -18,6 +20,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 +45,25 @@ 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 whether the + /// of this instance is custom or otherwise supplied by the devloper and not inferred + /// by the code written. + /// + /// true if this instance is custom type expression; otherwise, false. + bool IsCustomTypeExpression { get; } + + /// + /// 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. + ParameterModifiers 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..d986ccbb3 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; /// @@ -53,5 +53,13 @@ public interface IGraphFieldTemplateBase : ISchemaItemTemplate /// /// The custom wrappers. MetaGraphTypes[] DeclaredTypeWrappers { get; } + + /// + /// Gets a value indicating whether the + /// of this instance is custom or otherwise supplied by the devloper and not inferred + /// by the code written. + /// + /// true if this instance is custom type expression; otherwise, false. + bool IsCustomTypeExpression { get; } } } \ No newline at end of file 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/IInputGraphFieldTemplate.cs b/src/graphql-aspnet/Interfaces/Internal/IInputGraphFieldTemplate.cs index 64231ce08..4e1ed9480 100644 --- a/src/graphql-aspnet/Interfaces/Internal/IInputGraphFieldTemplate.cs +++ b/src/graphql-aspnet/Interfaces/Internal/IInputGraphFieldTemplate.cs @@ -64,5 +64,13 @@ public interface IInputGraphFieldTemplate : ISchemaItemTemplate /// /// The custom wrappers. MetaGraphTypes[] DeclaredTypeWrappers { get; } + + /// + /// Gets a value indicating whether the + /// of this instance is custom or otherwise supplied by the devloper and not inferred + /// by the code written. + /// + /// true if this instance is custom type expression; otherwise, false. + bool IsCustomTypeExpression { 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..1e07e74da 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; /// @@ -53,8 +53,8 @@ public interface ISchemaItemTemplate : INamedItemTemplate /// /// Gets a the canonical path on the graph where this item sits. /// - /// The route. - SchemaItemPath Route { get; } + /// The schema item's path on a schema. + ItemPath ItemPath { get; } /// /// Gets the singular concrete type this definition supplies to the object graph any Task or IEnumerable @@ -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..32c4ef89e 100644 --- a/src/graphql-aspnet/Interfaces/Logging/IGraphEventLogger.cs +++ b/src/graphql-aspnet/Interfaces/Logging/IGraphEventLogger.cs @@ -46,7 +46,7 @@ void SchemaPipelineRegistered(ISchemaPipeline pipeline) /// /// The type of the schema the route was registered for. /// The relative route path (e.g. '/graphql'). - void SchemaRouteRegistered(string routePath) + void SchemaUrlRouteRegistered(string routePath) where TSchema : class, ISchema; /// @@ -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..aaa3a2b45 100644 --- a/src/graphql-aspnet/Interfaces/Schema/IEnumValue.cs +++ b/src/graphql-aspnet/Interfaces/Schema/IEnumValue.cs @@ -14,6 +14,14 @@ namespace GraphQL.AspNet.Interfaces.Schema /// public interface IEnumValue : ISchemaItem, IDeprecatable { + /// + /// Creates a shallow clone of this instance, replacing specific argument values if supplied. + /// + /// When not null, represents the new parent item that will own this new field. + /// When supplied, represents a new name to use for this enum value in a schema. + /// IEnumValue. + IEnumValue Clone(IEnumGraphType parent = null, string valueName = null); + /// /// Gets the parent enum graph type that owns this value. /// @@ -21,16 +29,21 @@ public interface IEnumValue : ISchemaItem, IDeprecatable IEnumGraphType Parent { get; } /// - /// Gets the declared numerical value of the enum. + /// Gets the declared numerical value of the enum, as its defined in source code. /// - /// The value of the neum. - object InternalValue { get; } + /// The assigned value of the enum. + 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 casing on this value will match the enum label in source code and may be + /// different than the name value which will match + /// the rules of the target schema. + /// + /// 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..77f588eae 100644 --- a/src/graphql-aspnet/Interfaces/Schema/IGraphArgument.cs +++ b/src/graphql-aspnet/Interfaces/Schema/IGraphArgument.cs @@ -18,17 +18,22 @@ namespace GraphQL.AspNet.Interfaces.Schema public interface IGraphArgument : ITypedSchemaItem, IDefaultValueSchemaItem, ISchemaItem { /// - /// Clones this instance to a new argument. + /// Creates a shallow clone of this instance, replacing specific argument values if supplied. /// - /// The parent item to assign the newly cloned argument to. + /// When not null, represents the new parent field that will own the new instance. + /// When not null, represents the new argument name to use for the cloned instance. + /// When not null, represents the new type expression to use + /// for this field. + /// A value indicating what to do with field requirements + /// and default values in the cloned field. + /// The new default value if so requested to be applied. /// 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; } + IGraphArgument Clone( + ISchemaItem parent = null, + string argumentName = null, + GraphTypeExpression typeExpression = null, + DefaultValueCloneOptions defaultValueOptions = DefaultValueCloneOptions.None, + object newDefaultValue = null); /// /// Gets the type expression that represents the data of this argument (i.e. the '[SomeType!]' 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..4dfeb3ed5 100644 --- a/src/graphql-aspnet/Interfaces/Schema/IGraphField.cs +++ b/src/graphql-aspnet/Interfaces/Schema/IGraphField.cs @@ -11,7 +11,8 @@ namespace GraphQL.AspNet.Interfaces.Schema { using GraphQL.AspNet.Execution; using GraphQL.AspNet.Interfaces.Execution; - using GraphQL.AspNet.Internal.TypeTemplates; + using GraphQL.AspNet.Schemas; + 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 @@ -19,6 +20,19 @@ namespace GraphQL.AspNet.Interfaces.Schema /// public interface IGraphField : IDeprecatable, IGraphArgumentContainer, ISecurableSchemaItem, IGraphFieldBase { + /// + /// Creates a shallow clone of this instance, replacing specific field values if supplied. + /// + /// When not null, represents the new parent item that will own this new field. + /// When not null, represents the new field name to use for the cloned value. + /// When not null, represents the new type expression to use + /// for this field. + /// IGraphField. + IGraphField Clone( + ISchemaItem parent = null, + string fieldName = null, + GraphTypeExpression typeExpression = null); + /// /// Updates the field resolver used by this graph field. /// @@ -35,20 +49,6 @@ public interface IGraphField : IDeprecatable, IGraphArgumentContainer, ISecurabl /// true if this field can be returned by specified graph type; otherwise, false. bool CanResolveForGraphType(IGraphType graphType); - /// - /// Clones this instance to a new field. - /// - /// The new parent item that will own this new field. - /// 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..2abf33911 100644 --- a/src/graphql-aspnet/Interfaces/Schema/IGraphFieldBase.cs +++ b/src/graphql-aspnet/Interfaces/Schema/IGraphFieldBase.cs @@ -15,14 +15,8 @@ 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. - /// - /// The new parent. - void AssignParent(IGraphType parent); - /// /// Gets the type expression that represents the data returned from this field (i.e. the '[SomeType!]' /// declaration used in schema definition language.) @@ -31,9 +25,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 +43,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/IGraphType.cs b/src/graphql-aspnet/Interfaces/Schema/IGraphType.cs index cc59f3916..1b5aae943 100644 --- a/src/graphql-aspnet/Interfaces/Schema/IGraphType.cs +++ b/src/graphql-aspnet/Interfaces/Schema/IGraphType.cs @@ -16,6 +16,14 @@ namespace GraphQL.AspNet.Interfaces.Schema /// public interface IGraphType : ISchemaItem { + /// + /// Creates a shallow clone of this instance, replacing the type name with the + /// provided value if provided. + /// + /// When provided, represents the new graph type name to use for the cloned value. + /// IGraphType. + IGraphType Clone(string typeName = null); + /// /// Determines whether the provided item is of a concrete type represented by this graph type. /// diff --git a/src/graphql-aspnet/Interfaces/Schema/IInputGraphField.cs b/src/graphql-aspnet/Interfaces/Schema/IInputGraphField.cs index 514fdefb1..1a9390870 100644 --- a/src/graphql-aspnet/Interfaces/Schema/IInputGraphField.cs +++ b/src/graphql-aspnet/Interfaces/Schema/IInputGraphField.cs @@ -9,10 +9,36 @@ namespace GraphQL.AspNet.Interfaces.Schema { + using GraphQL.AspNet.Schemas; + using GraphQL.AspNet.Schemas.TypeSystem; + /// /// A field of data on an INPUT_OBJECT graph type. /// public interface IInputGraphField : IGraphFieldBase, IDefaultValueSchemaItem, ITypedSchemaItem { + /// + /// Creates a shallow clone of this instance, replacing specific field values if supplied. + /// + /// When not null, represents the new parent item that will own this new field. + /// When not null, represents the new field name to use for the cloned value. + /// When not null, represents the new type expression to use + /// for this field. + /// A value indicating what to do with field requirements + /// and default values in the cloned field. + /// The new default value if so requested to be applied. + /// IInputGraphField. + IInputGraphField Clone( + ISchemaItem parent = null, + string fieldName = null, + GraphTypeExpression typeExpression = null, + DefaultValueCloneOptions defaultValueOptions = DefaultValueCloneOptions.None, + object newDefaultValue = null); + + /// + /// 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/IReadOnlyGraphFieldCollection.cs b/src/graphql-aspnet/Interfaces/Schema/IReadOnlyGraphFieldCollection.cs index 7e0db8725..125b5d03c 100644 --- a/src/graphql-aspnet/Interfaces/Schema/IReadOnlyGraphFieldCollection.cs +++ b/src/graphql-aspnet/Interfaces/Schema/IReadOnlyGraphFieldCollection.cs @@ -16,12 +16,6 @@ namespace GraphQL.AspNet.Interfaces.Schema /// public interface IReadOnlyGraphFieldCollection : IEnumerable { - /// - /// Gets the that owns this field collection. - /// - /// The owner. - IGraphType Owner { get; } - /// /// Attempts to find a field of the given name. Returns null if the field is not found. /// diff --git a/src/graphql-aspnet/Interfaces/Schema/IReadOnlyInputGraphFieldCollection.cs b/src/graphql-aspnet/Interfaces/Schema/IReadOnlyInputGraphFieldCollection.cs index 2c5075377..36f5f9f21 100644 --- a/src/graphql-aspnet/Interfaces/Schema/IReadOnlyInputGraphFieldCollection.cs +++ b/src/graphql-aspnet/Interfaces/Schema/IReadOnlyInputGraphFieldCollection.cs @@ -16,12 +16,6 @@ namespace GraphQL.AspNet.Interfaces.Schema ///
public interface IReadOnlyInputGraphFieldCollection : IEnumerable { - /// - /// Gets the that owns this field collection. - /// - /// The owner. - IInputObjectGraphType Owner { get; } - /// /// Attempts to find a field of the given name. Returns null if the field is not found. /// diff --git a/src/graphql-aspnet/Interfaces/Schema/IScalarGraphType.cs b/src/graphql-aspnet/Interfaces/Schema/IScalarGraphType.cs index 617f75516..71b7b37c3 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. diff --git a/src/graphql-aspnet/Interfaces/Schema/ISchemaItem.cs b/src/graphql-aspnet/Interfaces/Schema/ISchemaItem.cs index a8a472ccd..371450e49 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; /// @@ -17,11 +18,11 @@ namespace GraphQL.AspNet.Interfaces.Schema public interface ISchemaItem : INamedItem { /// - /// Gets the unique route string assigned to this item - /// in the object graph. + /// Gets the unique path string assigned to this item + /// identifing it's unique location in an object graph. /// - /// The route. - SchemaItemPath Route { get; } + /// The path object assigned to this schema item. + ItemPath ItemPath { get; } /// /// Gets a collection of directives applied to this schema item 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/IUnionGraphType.cs b/src/graphql-aspnet/Interfaces/Schema/IUnionGraphType.cs index e2d604745..e7283aeb2 100644 --- a/src/graphql-aspnet/Interfaces/Schema/IUnionGraphType.cs +++ b/src/graphql-aspnet/Interfaces/Schema/IUnionGraphType.cs @@ -18,6 +18,16 @@ namespace GraphQL.AspNet.Interfaces.Schema ///
public interface IUnionGraphType : IGraphType { + /// + /// Creates a shallow clone of this instance, replacing the type name with the + /// provided value if provided. + /// + /// When provided, represents the new graph type name to use for the cloned value. + /// A format function that, if supplied, will update all the + /// possible graph type names on the clone to match the provided format. + /// IGraphType. + IGraphType Clone(string typeName = null, Func possibleGraphTypeNameFormatter = null); + /// /// Gets the complete list of possible concrete types contained in this union. /// 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..f8ccef92d --- /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. (e.g. minimal api defined fields and directives). + /// + 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 path name that will be given to the item on the target schema. + /// + /// The fully qualified path name for this item. + /// (e.g. '[directive]/@myDirective', '[query]/path1/path2'). + ItemPath ItemPath { 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/Internal/TypeTemplates/GraphArgumentTemplate.cs b/src/graphql-aspnet/Internal/TypeTemplates/GraphArgumentTemplate.cs deleted file mode 100644 index 8946df5ad..000000000 --- a/src/graphql-aspnet/Internal/TypeTemplates/GraphArgumentTemplate.cs +++ /dev/null @@ -1,314 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Internal.TypeTemplates -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Diagnostics; - using System.Linq; - using System.Reflection; - using System.Threading; - 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; - using GraphQL.AspNet.Schemas.Structural; - using GraphQL.AspNet.Schemas.TypeSystem; - - /// - /// A template describing an argument declared a field. - /// - [DebuggerDisplay("{Name} (Type: {FriendlyObjectTypeName})")] - public class GraphArgumentTemplate : IGraphArgumentTemplate - { - private FromGraphQLAttribute _argDeclaration; - private bool _invalidTypeExpression; - - /// - /// Initializes a new instance of the class. - /// - /// The owner of this argument. - /// The parameter on which this - /// argument template is made. - public GraphArgumentTemplate(IGraphFieldTemplateBase parent, ParameterInfo parameter) - { - Validation.ThrowIfNull(parent, nameof(parent)); - Validation.ThrowIfNull(parameter, nameof(parameter)); - - this.Parent = parent; - this.Parameter = parameter; - } - - /// - public virtual void Parse() - { - this.DeclaredArgumentType = this.Parameter.ParameterType; - this.ObjectType = GraphValidation.EliminateWrappersFromCoreType(this.Parameter.ParameterType); - this.AppliedDirectives = this.ExtractAppliedDirectiveTemplates(); - - // set the name - _argDeclaration = this.Parameter.SingleAttributeOrDefault(); - string name = null; - if (_argDeclaration != null) - name = _argDeclaration?.ArgumentName?.Trim(); - - if (string.IsNullOrWhiteSpace(name)) - name = Constants.Routing.PARAMETER_META_NAME; - - 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(); - - if (_argDeclaration?.TypeExpression == null) - { - this.DeclaredTypeWrappers = null; - } - else - { - var expression = GraphTypeExpression.FromDeclaration(_argDeclaration.TypeExpression); - if (!expression.IsValid) - { - _invalidTypeExpression = true; - } - else - { - this.DeclaredTypeWrappers = expression.Wrappers; - } - } - - this.HasDefaultValue = this.Parameter.HasDefaultValue; - this.DefaultValue = null; - - if (this.HasDefaultValue) - { - if (this.Parameter.DefaultValue != null) - { - // enums will present their default value as a raw int - // convert it to a labelled value - if (this.ObjectType.IsEnum) - { - this.DefaultValue = Enum.ToObject(this.ObjectType, this.Parameter.DefaultValue); - } - else - { - this.DefaultValue = this.Parameter.DefaultValue; - } - } - } - - // 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)); - - // 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; - } - } - - /// - /// Determines whether this instance represents a parameter that should be marked as the "source data" - /// for the field its attached to. - /// - /// System.Boolean. - protected virtual bool IsSourceDataArgument() - { - if (this.ObjectType == this.Parent.SourceObjectType) - { - var sourceType = this.ObjectType; - if (this.TypeExpression.IsListOfItems) - { - sourceType = typeof(IEnumerable<>).MakeGenericType(sourceType); - } - - if (this.Parent.Arguments.Any(x => x.ArgumentModifiers.HasFlag(GraphArgumentModifiers.ParentFieldResult))) - return false; - - return sourceType == this.DeclaredArgumentType; - } - - return false; - } - - /// - /// Determines whether this argument is (or can be) the cancellation token for this - /// argument's parent field. - /// - /// true if this argument is the dedicated cancellation token; otherwise, false. - protected virtual bool IsCancellationTokenArgument() - { - if (this.ObjectType == typeof(CancellationToken)) - { - if (this.Parent.Arguments.All(x => !x.ArgumentModifiers.IsCancellationToken())) - 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()) - { - // internal parameters should not be injected into the object graph - // so they have no dependents - return Enumerable.Empty(); - } - - var types = new List(); - var expectedTypeKind = GraphValidation.ResolveTypeKind(this.ObjectType, TypeKind.INPUT_OBJECT); - types.Add(new DependentType(this.ObjectType, expectedTypeKind)); - - if (this.AppliedDirectives != null) - { - var directiveTypes = this.AppliedDirectives - .Where(x => x.DirectiveType != null) - .Select(x => new DependentType(x.DirectiveType, TypeKind.DIRECTIVE)); - - types.AddRange(directiveTypes); - } - - return types; - } - - /// - public void ValidateOrThrow(bool validateChildren = true) - { - GraphValidation.EnsureGraphNameOrThrow(this.InternalFullName, this.Name); - - if (_invalidTypeExpression) - { - throw new GraphTypeDeclarationException( - $"The item '{this.Parent.InternalFullName}' declares an argument '{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."); - } - - // if the user declared a custom type expression it must be compatiable with the - // actual expected type expression of the C# code provided - var actualTypeExpression = GraphTypeExpression - .FromType(this.DeclaredArgumentType) - .CloneTo(GraphTypeNames.ParseName(this.ObjectType, TypeKind.INPUT_OBJECT)); - - if (!GraphTypeExpression.AreTypesCompatiable(actualTypeExpression, this.TypeExpression)) - { - throw new GraphTypeDeclarationException( - $"The item '{this.Parent.InternalFullName}' declares an argument '{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 (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."); - } - - if (!GraphValidation.IsValidGraphType(this.ObjectType)) - { - throw new GraphTypeDeclarationException( - $"The item '{this.Parent.InternalFullName}' declares an argument '{this.Name}' of type '{this.ObjectType.FriendlyName()}' " + - $"which is not a valid graph type."); - } - } - - foreach (var directive in this.AppliedDirectives) - directive.ValidateOrThrow(); - } - - /// - public string Name => this.Route.Name; - - /// - /// Gets the reflected parameter data that defines this template. - /// - /// The parameter. - public ParameterInfo Parameter { get; } - - /// - public Type DeclaredArgumentType { get; private set; } - - /// - public string InternalFullName => $"{this.Parent?.InternalFullName}.{this.Parameter.Name}"; - - /// - public string InternalName => this.Parameter.Name; - - /// - public bool IsExplicitDeclaration => true; - - /// - public IGraphFieldTemplateBase Parent { get; } - - /// - public string Description { get; private set; } - - /// - public SchemaItemPath Route { get; private set; } - - /// - public object DefaultValue { get; private set; } - - /// - public GraphTypeExpression TypeExpression { get; private set; } - - /// - public Type ObjectType { get; private set; } - - /// - public GraphArgumentModifiers ArgumentModifiers { get; protected set; } - - /// - public string DeclaredArgumentName => this.Parameter.Name; - - /// - public MetaGraphTypes[] DeclaredTypeWrappers { get; private set; } - - /// - public ICustomAttributeProvider AttributeProvider => this.Parameter; - - /// - public IEnumerable AppliedDirectives { get; private set; } - - /// - public bool HasDefaultValue { get; private set; } - - /// - /// Gets a string representing the name of the parameter's concrete type. - /// This is an an internal helper property for helpful debugging information only. - /// - /// The name of the parameter type friendly. - public string FriendlyObjectTypeName => this.Parameter.ParameterType.FriendlyName(); - } -} \ 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/Logging/GeneralEvents/FieldResolutionCompletedLogEntry.cs b/src/graphql-aspnet/Logging/GeneralEvents/FieldResolutionCompletedLogEntry.cs index f6c594e1d..e10577f6f 100644 --- a/src/graphql-aspnet/Logging/GeneralEvents/FieldResolutionCompletedLogEntry.cs +++ b/src/graphql-aspnet/Logging/GeneralEvents/FieldResolutionCompletedLogEntry.cs @@ -25,7 +25,7 @@ public FieldResolutionCompletedLogEntry(FieldResolutionContext context) : base(LogEventIds.FieldResolutionCompleted) { this.PipelineRequestId = context?.Request?.Id.ToString(); - this.FieldPath = context?.Request?.InvocationContext?.Field?.Route?.Path; + this.FieldPath = context?.Request?.InvocationContext?.Field?.ItemPath?.Path; this.TypeExpression = context?.Request?.InvocationContext?.Field?.TypeExpression?.ToString(); this.HasData = context == null ? null : context.Result != null; this.ResultIsValid = context?.Messages?.IsSucessful; diff --git a/src/graphql-aspnet/Logging/GeneralEvents/FieldResolutionStartedLogEntry.cs b/src/graphql-aspnet/Logging/GeneralEvents/FieldResolutionStartedLogEntry.cs index 02d772adb..ef623e2ac 100644 --- a/src/graphql-aspnet/Logging/GeneralEvents/FieldResolutionStartedLogEntry.cs +++ b/src/graphql-aspnet/Logging/GeneralEvents/FieldResolutionStartedLogEntry.cs @@ -27,7 +27,7 @@ public FieldResolutionStartedLogEntry(FieldResolutionContext context) { this.PipelineRequestId = context?.Request?.Id.ToString(); this.FieldExecutionMode = context?.Request?.Field?.Mode.ToString(); - this.FieldPath = context?.Request?.Field?.Route?.Path; + this.FieldPath = context?.Request?.Field?.ItemPath?.Path; } /// diff --git a/src/graphql-aspnet/Logging/GeneralEvents/SchemaItemAuthenticationCompletedLogEntry.cs b/src/graphql-aspnet/Logging/GeneralEvents/SchemaItemAuthenticationCompletedLogEntry.cs index ee9a60909..6ee28964a 100644 --- a/src/graphql-aspnet/Logging/GeneralEvents/SchemaItemAuthenticationCompletedLogEntry.cs +++ b/src/graphql-aspnet/Logging/GeneralEvents/SchemaItemAuthenticationCompletedLogEntry.cs @@ -28,7 +28,7 @@ public SchemaItemAuthenticationCompletedLogEntry(SchemaItemSecurityChallengeCont : base(LogEventIds.SchemaItemAuthenticationCompleted) { this.PipelineRequestId = securityContext?.Request?.Id.ToString(); - this.SchemaItemPath = securityContext?.Request?.SecureSchemaItem?.Route?.Path; + this.SchemaItemPath = securityContext?.Request?.SecureSchemaItem?.ItemPath?.Path; this.Username = authResult?.User?.RetrieveUsername(); this.AuthenticationScheme = authResult?.AuthenticationScheme; this.AuthethenticationSuccess = authResult?.Suceeded; diff --git a/src/graphql-aspnet/Logging/GeneralEvents/SchemaItemAuthenticationStartedLogEntry.cs b/src/graphql-aspnet/Logging/GeneralEvents/SchemaItemAuthenticationStartedLogEntry.cs index 61c1793d0..571f37b46 100644 --- a/src/graphql-aspnet/Logging/GeneralEvents/SchemaItemAuthenticationStartedLogEntry.cs +++ b/src/graphql-aspnet/Logging/GeneralEvents/SchemaItemAuthenticationStartedLogEntry.cs @@ -26,7 +26,7 @@ public SchemaItemAuthenticationStartedLogEntry(SchemaItemSecurityChallengeContex : base(LogEventIds.SchemaItemAuthenticationStarted) { this.PipelineRequestId = context?.Request?.Id.ToString(); - this.SchemaItemPath = context?.SecureSchemaItem?.Route?.Path; + this.SchemaItemPath = context?.SecureSchemaItem?.ItemPath?.Path; } /// diff --git a/src/graphql-aspnet/Logging/GeneralEvents/SchemaItemAuthorizationCompletedLogEntry.cs b/src/graphql-aspnet/Logging/GeneralEvents/SchemaItemAuthorizationCompletedLogEntry.cs index 1741e82a5..29cb98f09 100644 --- a/src/graphql-aspnet/Logging/GeneralEvents/SchemaItemAuthorizationCompletedLogEntry.cs +++ b/src/graphql-aspnet/Logging/GeneralEvents/SchemaItemAuthorizationCompletedLogEntry.cs @@ -27,7 +27,7 @@ public SchemaItemAuthorizationCompletedLogEntry(SchemaItemSecurityChallengeConte : base(LogEventIds.SchemaItemAuthorizationCompleted) { this.PipelineRequestId = context?.Request.Id.ToString(); - this.SchemaItemPath = context?.Request?.SecureSchemaItem?.Route?.Path; + this.SchemaItemPath = context?.Request?.SecureSchemaItem?.ItemPath?.Path; this.Username = context?.AuthenticatedUser?.RetrieveUsername(); this.AuthorizationStatus = context?.Result?.Status.ToString(); this.LogMessage = context?.Result?.LogMessage; diff --git a/src/graphql-aspnet/Logging/GeneralEvents/SchemaItemAuthorizationStartedLogEntry.cs b/src/graphql-aspnet/Logging/GeneralEvents/SchemaItemAuthorizationStartedLogEntry.cs index 55ec1af23..72a663c0e 100644 --- a/src/graphql-aspnet/Logging/GeneralEvents/SchemaItemAuthorizationStartedLogEntry.cs +++ b/src/graphql-aspnet/Logging/GeneralEvents/SchemaItemAuthorizationStartedLogEntry.cs @@ -27,7 +27,7 @@ public SchemaItemAuthorizationStartedLogEntry(SchemaItemSecurityChallengeContext : base(LogEventIds.SchemaItemAuthorizationStarted) { this.PipelineRequestId = context?.Request?.Id.ToString(); - this.SchemaItemPath = context?.SecureSchemaItem?.Route?.Path; + this.SchemaItemPath = context?.SecureSchemaItem?.ItemPath?.Path; this.Username = context?.AuthenticatedUser?.RetrieveUsername(); } diff --git a/src/graphql-aspnet/Logging/GeneralEvents/SchemaRouteRegisteredLogEntry.cs b/src/graphql-aspnet/Logging/GeneralEvents/SchemaRouteRegisteredLogEntry.cs index 4075299a4..e534b2d73 100644 --- a/src/graphql-aspnet/Logging/GeneralEvents/SchemaRouteRegisteredLogEntry.cs +++ b/src/graphql-aspnet/Logging/GeneralEvents/SchemaRouteRegisteredLogEntry.cs @@ -28,11 +28,11 @@ public class SchemaRouteRegisteredLogEntry : GraphLogEntry /// /// The route string (e.g. '/graphql'). public SchemaRouteRegisteredLogEntry(string routePath) - : base(LogEventIds.SchemaRouteRegistered) + : base(LogEventIds.SchemaUrlRouteRegistered) { _schemaTypeShortName = typeof(TSchema).FriendlyName(); this.SchemaTypeName = typeof(TSchema).FriendlyName(true); - this.SchemaRoutePath = routePath; + this.SchemaItemPath = routePath; } /// @@ -49,10 +49,10 @@ public string SchemaTypeName /// Gets the relative url registered for this schema type to listen on. /// /// The name of the schema route. - public string SchemaRoutePath + public string SchemaItemPath { - get => this.GetProperty(LogPropertyNames.SCHEMA_ROUTE_PATH); - private set => this.SetProperty(LogPropertyNames.SCHEMA_ROUTE_PATH, value); + get => this.GetProperty(LogPropertyNames.SCHEMA_URL_ROUTE_PATH); + private set => this.SetProperty(LogPropertyNames.SCHEMA_URL_ROUTE_PATH, value); } /// @@ -61,7 +61,7 @@ public string SchemaRoutePath /// A that represents this instance. public override string ToString() { - return $"Schema Route Registered | Schema Type: '{_schemaTypeShortName}', Route: '{this.SchemaRoutePath}' "; + return $"Schema Route Registered | Schema Type: '{_schemaTypeShortName}', Route: '{this.SchemaItemPath}' "; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Logging/GeneralEvents/TypeSystemDirectiveAppliedLogEntry.cs b/src/graphql-aspnet/Logging/GeneralEvents/TypeSystemDirectiveAppliedLogEntry.cs index c05cda001..2f0b61c86 100644 --- a/src/graphql-aspnet/Logging/GeneralEvents/TypeSystemDirectiveAppliedLogEntry.cs +++ b/src/graphql-aspnet/Logging/GeneralEvents/TypeSystemDirectiveAppliedLogEntry.cs @@ -29,7 +29,7 @@ public TypeSystemDirectiveAppliedLogEntry(IDirective directiveApplied, ISchemaIt : base(LogEventIds.TypeSystemDirectiveApplied) { this.SchemaTypeName = typeof(TSchema).FriendlyName(true); - this.SchemaItemPath = appliedTo?.Route?.Path; + this.SchemaItemPath = appliedTo?.ItemPath?.Path; this.DirectiveName = directiveApplied?.Name; this.DirectiveInternalName = directiveApplied?.InternalName; this.DirectiveLocation = appliedTo?.AsDirectiveLocation().ToString() ?? "-unknown-"; diff --git a/src/graphql-aspnet/Logging/LogEventIds.cs b/src/graphql-aspnet/Logging/LogEventIds.cs index 7527ebe61..1a55f1bae 100644 --- a/src/graphql-aspnet/Logging/LogEventIds.cs +++ b/src/graphql-aspnet/Logging/LogEventIds.cs @@ -58,7 +58,7 @@ public static class LogEventIds /// /// A log event indicating the runtime successfully registered an ASP.NET route to serve requests for a graph schema. /// - public static EventId SchemaRouteRegistered = new EventId(ROOT_GENERAL_EVENT_ID + 120, "GraphQL Schema Route Registered"); + public static EventId SchemaUrlRouteRegistered = new EventId(ROOT_GENERAL_EVENT_ID + 120, "GraphQL Schema Route Registered"); /// /// A log event indicating an unhandled exception was logged out of context of any other diff --git a/src/graphql-aspnet/Logging/LogPropertyNames.cs b/src/graphql-aspnet/Logging/LogPropertyNames.cs index 046947482..12a6d86ea 100644 --- a/src/graphql-aspnet/Logging/LogPropertyNames.cs +++ b/src/graphql-aspnet/Logging/LogPropertyNames.cs @@ -250,7 +250,7 @@ public static class LogPropertyNames /// /// The route assigned to a schema type when it was registered with ASP.NET. /// - public const string SCHEMA_ROUTE_PATH = "route"; + public const string SCHEMA_URL_ROUTE_PATH = "route"; /// /// The unique path identifying the item, within the schema, the directive was applied to. diff --git a/src/graphql-aspnet/Middleware/DirectiveExecution/Components/AuthorizeDirectiveMiddleware.cs b/src/graphql-aspnet/Middleware/DirectiveExecution/Components/AuthorizeDirectiveMiddleware.cs index da41a445c..22e264e9f 100644 --- a/src/graphql-aspnet/Middleware/DirectiveExecution/Components/AuthorizeDirectiveMiddleware.cs +++ b/src/graphql-aspnet/Middleware/DirectiveExecution/Components/AuthorizeDirectiveMiddleware.cs @@ -59,7 +59,7 @@ public async Task InvokeAsync(GraphDirectiveExecutionContext context, GraphMiddl else { context.Messages.Critical( - $"Access Denied to directive {context.Directive.Route.Path}", + $"Access Denied to directive {context.Directive.ItemPath.Path}", Constants.ErrorCodes.ACCESS_DENIED, context.Request.Origin, new UnauthorizedAccessException(result.LogMessage)); 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/AuthorizeFieldMiddleware.cs b/src/graphql-aspnet/Middleware/FieldExecution/Components/AuthorizeFieldMiddleware.cs index fd6a9ef0b..a714f06a9 100644 --- a/src/graphql-aspnet/Middleware/FieldExecution/Components/AuthorizeFieldMiddleware.cs +++ b/src/graphql-aspnet/Middleware/FieldExecution/Components/AuthorizeFieldMiddleware.cs @@ -64,7 +64,7 @@ public async Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewar else { context.Messages.Critical( - $"Access Denied to field {context.Field.Route.Path}", + $"Access Denied to field {context.Field.ItemPath.Path}", Constants.ErrorCodes.ACCESS_DENIED, context.Request.Origin); } diff --git a/src/graphql-aspnet/Middleware/FieldExecution/Components/InvokeFieldResolverMiddleware.cs b/src/graphql-aspnet/Middleware/FieldExecution/Components/InvokeFieldResolverMiddleware.cs index ec400e931..7e6bf6e51 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); @@ -179,10 +183,10 @@ private void AssignResults(GraphFieldExecutionContext executionContext, FieldRes } throw new GraphExecutionException( - $"When attempting to resolve the field '{executionContext.Field.Route.Path}' an unexpected error occured and the request was teriminated.", + $"When attempting to resolve the field '{executionContext.Field.ItemPath.Path}' an unexpected error occured and the request was teriminated.", executionContext.Request.Origin, new InvalidOperationException( - $"The field '{executionContext.Field.Route.Parent}' has a resolution mode of '{nameof(FieldResolutionMode.PerSourceItem)}' " + + $"The field '{executionContext.Field.ItemPath.Parent}' has a resolution mode of '{nameof(FieldResolutionMode.PerSourceItem)}' " + $"but the execution context contains {executionContext.Request.Data.Items.Count} source items. The runtime is unable to determine which " + "item to assign the resultant value to.")); } @@ -201,7 +205,7 @@ private void AssignResults(GraphFieldExecutionContext executionContext, FieldRes throw new ArgumentOutOfRangeException( nameof(executionContext.Field.Mode), - $"The execution mode for field '{executionContext.Field.Route.Path}' cannot be resolved " + + $"The execution mode for field '{executionContext.Field.ItemPath.Path}' cannot be resolved " + $"by {nameof(InvokeFieldResolverMiddleware)}. (Mode: {executionContext.Field.Mode.ToString()})"); } } diff --git a/src/graphql-aspnet/Middleware/FieldExecution/Components/ProcessChildFieldsMiddleware.cs b/src/graphql-aspnet/Middleware/FieldExecution/Components/ProcessChildFieldsMiddleware.cs index ac2127eac..dd514a1a1 100644 --- a/src/graphql-aspnet/Middleware/FieldExecution/Components/ProcessChildFieldsMiddleware.cs +++ b/src/graphql-aspnet/Middleware/FieldExecution/Components/ProcessChildFieldsMiddleware.cs @@ -95,7 +95,7 @@ private async Task ProcessDownStreamFieldContextsAsync(GraphFieldExecutionContex // theoretically it can't not be found, but you never know if (graphType == null) { - var msg = $"Internal Server Error. When processing the results of '{context.Field.Route.Path}' no graph type on the target schema " + + var msg = $"Internal Server Error. When processing the results of '{context.Field.ItemPath.Path}' no graph type on the target schema " + $"could be found for the type name '{context.Field.TypeExpression.TypeName}'. " + $"Unable to process the {allSourceItems.Count} item(s) generated."; @@ -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 @@ -224,7 +224,7 @@ private void CaptureChildFieldExecutionResults( "provide a reason for the failure."); parentContext.Messages.Critical( - $"Processing field '{childContext.Field.Name}' of '{parentContext.Field.Route}' resulted in a critical failure. See exception for details.", + $"Processing field '{childContext.Field.Name}' of '{parentContext.Field.ItemPath}' resulted in a critical failure. See exception for details.", Constants.ErrorCodes.EXECUTION_ERROR, childContext.InvocationContext.Origin, exception); @@ -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; @@ -374,7 +376,7 @@ private IEnumerable CreateChildExecutionContexts( { throw new ArgumentOutOfRangeException( nameof(childInvocationContext.Field.Mode), - $"The execution mode for field '{childInvocationContext.Field.Route.Path}' cannot be processed " + + $"The execution mode for field '{childInvocationContext.Field.ItemPath.Path}' cannot be processed " + $"by {nameof(ProcessChildFieldsMiddleware)}. (Mode: {childInvocationContext.Field.Mode.ToString()})"); } } diff --git a/src/graphql-aspnet/Middleware/FieldExecution/Components/ValidateFieldExecutionMiddleware.cs b/src/graphql-aspnet/Middleware/FieldExecution/Components/ValidateFieldExecutionMiddleware.cs index b72efd68d..02dd5ebac 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; /// @@ -78,7 +78,7 @@ public Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvoc "at least 1"; throw new GraphExecutionException( - $"Operation failed. The field execution context for '{field.Route.Path}' was passed " + + $"Operation failed. The field execution context for '{field.ItemPath.Path}' was passed " + $"0 items but expected {expected}. (Field Mode: {field.Mode.ToString()})"); } @@ -86,7 +86,7 @@ public Task InvokeAsync(GraphFieldExecutionContext context, GraphMiddlewareInvoc && dataSource.Items.Count != 1) { throw new GraphExecutionException( - $"Operation failed. The field execution context for '{field.Route.Path}' was passed " + + $"Operation failed. The field execution context for '{field.ItemPath.Path}' was passed " + $"{dataSource.Items.Count} items to resolve but expected 1. (Field Mode: {field.Mode.ToString()})"); } @@ -107,7 +107,7 @@ private void ValidateDataSourceItemAgainstExpectedType( if (!analysis.ExactMatchFound) { throw new GraphExecutionException( - $"Operation failed. The field execution context for '{field.Route.Path}' was passed " + + $"Operation failed. The field execution context for '{field.ItemPath.Path}' was passed " + $"a source item of type '{value.GetType().FriendlyName()}' which could not be coerced " + $"to '{expectedSourceType}' as requested by the target graph type '{fieldType.Name}'."); } @@ -115,7 +115,7 @@ private void ValidateDataSourceItemAgainstExpectedType( if (field.Mode == FieldResolutionMode.Batch && !(value.GetType() is IEnumerable)) { throw new GraphExecutionException( - $"Operation failed. The field execution context for '{field.Route.Path}' was executed in batch mode " + + $"Operation failed. The field execution context for '{field.ItemPath.Path}' was executed in batch mode " + $"but was not passed an {nameof(IEnumerable)} for its source data."); } } diff --git a/src/graphql-aspnet/Middleware/QueryExecution/Components/AuthorizeQueryOperationMiddleware.cs b/src/graphql-aspnet/Middleware/QueryExecution/Components/AuthorizeQueryOperationMiddleware.cs index e320e550f..6d1c0b8a9 100644 --- a/src/graphql-aspnet/Middleware/QueryExecution/Components/AuthorizeQueryOperationMiddleware.cs +++ b/src/graphql-aspnet/Middleware/QueryExecution/Components/AuthorizeQueryOperationMiddleware.cs @@ -61,6 +61,7 @@ private async Task AuthorizeOperationAsync(QueryExecutionContext context, { var authTasks = new List(); bool isAuthorized = true; + for (var i = 0; i < context.Operation.SecureItems.Count; i++) { var securePart = context.Operation.SecureItems[i]; @@ -78,13 +79,13 @@ private async Task AuthorizeOperationAsync(QueryExecutionContext context, { var authResult = authContext.Result ?? SchemaItemSecurityChallengeResult.Default(); - // fake the path elements from the field route. since we don't have a full resolution chain + // fake the path elements from the field item path. since we don't have a full resolution chain // when doing query level authorization (no indexers on potential child fields since // nothing is actually resolved yet) if (!authResult.Status.IsAuthorized()) { context.Messages.Critical( - $"Access Denied to {securePart.SecureItem.Route.Path}", + $"Access Denied to {securePart.SecureItem.ItemPath.Path}", Constants.ErrorCodes.ACCESS_DENIED, securePart.SourceLocation.AsOrigin()); isAuthorized = false; diff --git a/src/graphql-aspnet/Middleware/QueryExecution/Components/PackageQueryResultMiddleware.cs b/src/graphql-aspnet/Middleware/QueryExecution/Components/PackageQueryResultMiddleware.cs index 3e6bc7159..976b79852 100644 --- a/src/graphql-aspnet/Middleware/QueryExecution/Components/PackageQueryResultMiddleware.cs +++ b/src/graphql-aspnet/Middleware/QueryExecution/Components/PackageQueryResultMiddleware.cs @@ -9,7 +9,6 @@ namespace GraphQL.AspNet.Middleware.QueryExecution.Components { - using System; using System.Linq; using System.Threading; using System.Threading.Tasks; diff --git a/src/graphql-aspnet/Middleware/SchemaItemSecurity/Components/SchemItemSecurityRequirementsMiddleware.cs b/src/graphql-aspnet/Middleware/SchemaItemSecurity/Components/SchemItemSecurityRequirementsMiddleware.cs index 625db6a48..5aee2c4e0 100644 --- a/src/graphql-aspnet/Middleware/SchemaItemSecurity/Components/SchemItemSecurityRequirementsMiddleware.cs +++ b/src/graphql-aspnet/Middleware/SchemaItemSecurity/Components/SchemItemSecurityRequirementsMiddleware.cs @@ -122,7 +122,7 @@ private async Task CreateSecurityRequirementsAsync(ISecurabl if (_policyProvider == null) { var result = SchemaItemSecurityChallengeResult.Fail( - $"A named policy '{rule.PolicyName}' has been applied to the schema item '{schemaItem.Route}' but " + + $"A named policy '{rule.PolicyName}' has been applied to the schema item '{schemaItem.ItemPath}' but " + $"no policy provider is configured that can handle it."); return new CachedRequirements( @@ -134,7 +134,7 @@ private async Task CreateSecurityRequirementsAsync(ISecurabl if (policy == null) { var result = SchemaItemSecurityChallengeResult.Fail( - $"The schema item '{schemaItem.Route}' named policy '{rule.PolicyName}' has been applied to the schema item '{schemaItem.Name}' but " + + $"The schema item '{schemaItem.ItemPath}' named policy '{rule.PolicyName}' has been applied to the schema item '{schemaItem.Name}' but " + $"no policy provider is configured that can handle it."); return new CachedRequirements( @@ -180,7 +180,7 @@ private async Task CreateSecurityRequirementsAsync(ISecurabl && !allowDefaultSchemeFallThrough) { var result = SchemaItemSecurityChallengeResult.Fail( - $"The schema item '{schemaItem.Route}' has mismatched required authentication schemes in its applied security groups. It contains " + + $"The schema item '{schemaItem.ItemPath}' has mismatched required authentication schemes in its applied security groups. It contains " + $"no scenarios where an authentication scheme can be used to authenticate a user to all possible required authorizations."); return new CachedRequirements( 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..951c38de7 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/RuntimeSchemaItemDefinitions/RuntimeControllerActionDefinitionBase.cs @@ -0,0 +1,169 @@ +// ************************************************************* +// 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; + 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 that define the future where this field item + /// is being defined. + /// The full item path to use for this schema item. + protected RuntimeControllerActionDefinitionBase( + SchemaOptions options, + ItemPath path) + : this() + { + this.Options = Validation.ThrowIfNullOrReturn(options, nameof(options)); + this.ItemPath = Validation.ThrowIfNullOrReturn(path, nameof(path)); + } + + /// + /// 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, + ItemPathRoots collection, + string pathTemplate) + : this() + { + this.Options = Validation.ThrowIfNullOrReturn(options, nameof(options)); + pathTemplate = pathTemplate?.Trim() ?? string.Empty; + + this.ItemPath = new ItemPath(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.ItemPath = _parentField.ItemPath.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 ItemPath ItemPath { 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..ab2b1dbc0 --- /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("{ItemPath.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, ItemPathRoots.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..10339c4cf --- /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("{ItemPath.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, + ItemPathRoots 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..4c30c2c57 --- /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, + ItemPathRoots 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..0b66420ec --- /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("{ItemPath.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.ItemPath); + + 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 path to use for this item. + protected RuntimeResolvedFieldDefinition( + SchemaOptions schemaOptions, + ItemPath itemPath) + : base(schemaOptions, itemPath) + { + } + + /// + /// 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, + ItemPathRoots 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.ItemPath; + switch (collection) + { + case ItemPathRoots.Query: + return new QueryRootAttribute(path, this.ReturnType) + { + InternalName = this.InternalName, + }; + + case ItemPathRoots.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..611fa145c --- /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("{ItemPath.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, + ItemPathRoots.Types, + ItemPath.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..bbfc0585d --- /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?.ItemPath.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?.ItemPath.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 50% rename from src/graphql-aspnet/Engine/TypeMakers/DirectiveMaker.cs rename to src/graphql-aspnet/Schemas/Generation/TypeMakers/DirectiveMaker.cs index a9e93d7a0..5fc602c80 100644 --- a/src/graphql-aspnet/Engine/TypeMakers/DirectiveMaker.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/DirectiveMaker.cs @@ -7,16 +7,18 @@ // 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.Directives; 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,31 +26,26 @@ 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 securityGroups = new List(); @@ -59,10 +56,11 @@ public GraphTypeCreationResult CreateGraphType(Type concreteType) var result = new GraphTypeCreationResult(); var directive = new Directive( - formatter.FormatFieldName(template.Name), + template.Name, + template.InternalName, template.Locations, template.ObjectType, - template.Route, + template.ItemPath.Clone(), template.IsRepeatable, template.CreateResolver(), securityGroups) @@ -71,19 +69,37 @@ public GraphTypeCreationResult CreateGraphType(Type concreteType) Publish = template.Publish, }; + directive = _schema + .Configuration? + .DeclarationOptions? + .SchemaFormatStrategy? + .ApplySchemaItemRules(_schema.Configuration, directive) ?? directive; + // 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); - result.MergeDependents(argumentResult); + var argument = argumentResult.Argument.Clone(directive); + directive.Arguments.AddArgument(argument); + + 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 57% rename from src/graphql-aspnet/Engine/TypeMakers/EnumGraphTypeMaker.cs rename to src/graphql-aspnet/Schemas/Generation/TypeMakers/EnumGraphTypeMaker.cs index 5fc1d9c83..b8ebf6112 100644 --- a/src/graphql-aspnet/Engine/TypeMakers/EnumGraphTypeMaker.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/EnumGraphTypeMaker.cs @@ -7,12 +7,15 @@ // 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.Exceptions; + 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,47 +27,51 @@ 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, - template.Route, + template.Name, + template.InternalName, + template.ObjectType, + template.ItemPath, enumDirectives) { Description = template.Description, Publish = template.Publish, }; + graphType = _config + .DeclarationOptions? + .SchemaFormatStrategy? + .ApplySchemaItemRules(_config, graphType) ?? graphType; + var result = new GraphTypeCreationResult() { GraphType = graphType, - ConcreteType = concreteType, + ConcreteType = template.ObjectType, }; // account for any potential type system directives @@ -74,6 +81,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,13 +89,25 @@ public GraphTypeCreationResult CreateGraphType(Type concreteType) var valueOption = new EnumValue( graphType, - _schema.Configuration.DeclarationOptions.GraphNamingFormatter.FormatEnumValueName(value.Name), + value.Name, + value.InternalName, value.Description, - value.Route, + value.ItemPath, value.Value, - value.InternalName, + value.DeclaredLabel, valueDirectives); + valueOption = _config + .DeclarationOptions? + .SchemaFormatStrategy? + .ApplySchemaItemRules(_config, valueOption) ?? valueOption; + + if (Constants.QueryLanguage.IsReservedKeyword(valueOption.Name)) + { + throw new GraphTypeDeclarationException($"The enum value '{value.Name}' is invalid for " + + $"graph type '{graphType.Name}' on the target schema. {value.Name} is a reserved keyword."); + } + graphType.AddOption(valueOption); result.AddDependentRange(value.RetrieveRequiredTypes()); 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..d63abe852 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphArgumentMaker.cs @@ -0,0 +1,140 @@ +// ************************************************************* +// 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 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); + } + + var typeExpression = template.TypeExpression.Clone(schemaTypeName); + + var argument = new GraphFieldArgument( + owner, + template.Name, + template.InternalName, + template.ParameterName, + typeExpression, + template.ItemPath, + template.ObjectType, + template.HasDefaultValue, + template.DefaultValue, + template.Description, + directives); + + argument = _config + .DeclarationOptions? + .SchemaFormatStrategy? + .ApplySchemaItemRules(_config, argument) ?? argument; + + 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..a5a473f68 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/GraphFieldMaker.cs @@ -0,0 +1,261 @@ +// ************************************************************* +// 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 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(template, securityGroups); + + field = _config + .DeclarationOptions + .SchemaFormatStrategy? + .ApplySchemaItemRules(_config, field) ?? field; + + 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); + + var argument = argumentResult.Argument.Clone(field); + field.Arguments.AddArgument(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 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( + template.Name, + template.InternalName, + template.TypeExpression.Clone(schemaTypeName), + template.ItemPath, + template.ObjectType, + template.DeclaredName, + template.DeclaredReturnType, + template.IsRequired, + defaultValue, + directives); + + field.Description = template.Description; + + field = _config + .DeclarationOptions + .SchemaFormatStrategy? + .ApplySchemaItemRules(_config, field) ?? field; + + 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 (minus casing) for all but scalars + schemaTypeName = GraphTypeNames.ParseName(template.ObjectType, template.OwnerTypeKind); + } + + 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 or interface) + 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); + } + + return schemaTypeName; + } + + /// + /// Instantiates the graph field according to the data provided. + /// + /// The template to create a field from. + /// The complete set of + /// security groups to apply to the field. + /// MethodGraphField. + protected virtual MethodGraphField InstantiateField( + IGraphFieldTemplate template, + List securityGroups) + { + var directives = template.CreateAppliedDirectives(); + + var schemaTypeName = this.PrepareTypeName(template); + var typeExpression = template.TypeExpression.Clone(schemaTypeName); + + switch (template.FieldSource) + { + case GraphFieldSource.Method: + case GraphFieldSource.Property: + case GraphFieldSource.Action: + return new MethodGraphField( + template.Name, + template.InternalName, + typeExpression, + template.ItemPath, + 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 62% rename from src/graphql-aspnet/Engine/TypeMakers/InputObjectGraphTypeMaker.cs rename to src/graphql-aspnet/Schemas/Generation/TypeMakers/InputObjectGraphTypeMaker.cs index 81ef2ab1b..69d5027aa 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,57 +26,68 @@ 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 result = new GraphTypeCreationResult(); // gather directives var directives = template.CreateAppliedDirectives(); var inputObjectType = new InputObjectGraphType( - formatter.FormatGraphTypeName(template.Name), - concreteType, - template.Route, + template.Name, + template.InternalName, + template.ObjectType, + template.ItemPath, directives) { Description = template.Description, Publish = template.Publish, }; + inputObjectType = _config + .DeclarationOptions? + .SchemaFormatStrategy? + .ApplySchemaItemRules(_config, inputObjectType) ?? inputObjectType; + // account for any potential type system directives result.AddDependentRange(template.RetrieveRequiredTypes()); // 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); - inputObjectType.AddField(fieldResult.Field); + var fieldResult = _fieldMaker.CreateField(fieldTemplate); + + var field = fieldResult.Field.Clone(inputObjectType); + inputObjectType.AddField(field); result.MergeDependents(fieldResult); } @@ -90,7 +102,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 57% rename from src/graphql-aspnet/Engine/TypeMakers/InterfaceGraphTypeMaker.cs rename to src/graphql-aspnet/Schemas/Generation/TypeMakers/InterfaceGraphTypeMaker.cs index b2f56b7e5..94fe0f2d9 100644 --- a/src/graphql-aspnet/Engine/TypeMakers/InterfaceGraphTypeMaker.cs +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/InterfaceGraphTypeMaker.cs @@ -7,68 +7,80 @@ // 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 directives = template.CreateAppliedDirectives(); var interfaceType = new InterfaceGraphType( - formatter.FormatGraphTypeName(template.Name), + template.Name, + template.InternalName, template.ObjectType, - template.Route, + template.ItemPath, directives) { Description = template.Description, Publish = template.Publish, }; + // add in declared interfaces by name + foreach (var iface in template.DeclaredInterfaces) + interfaceType.InterfaceNames.Add(GraphTypeNames.ParseName(iface, TypeKind.INTERFACE)); + + interfaceType = _config + .DeclarationOptions? + .SchemaFormatStrategy? + .ApplySchemaItemRules(_config, interfaceType) ?? interfaceType; + // 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); @@ -84,14 +96,8 @@ public GraphTypeCreationResult CreateGraphType(Type concreteType) template.ObjectType); } - // add in declared interfaces by name - foreach (var iface in template.DeclaredInterfaces) - { - interfaceType.InterfaceNames.Add(formatter.FormatGraphTypeName(GraphTypeNames.ParseName(iface, TypeKind.INTERFACE))); - } - 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..ea736f170 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/ObjectGraphTypeMaker.cs @@ -0,0 +1,109 @@ +// ************************************************************* +// 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 directives = template.CreateAppliedDirectives(); + + var objectType = new ObjectGraphType( + template.Name, + template.InternalName, + template.ObjectType, + template.ItemPath, + directives) + { + Description = template.Description, + Publish = template.Publish, + }; + + // add in declared interfaces by name + foreach (var iface in template.DeclaredInterfaces) + objectType.InterfaceNames.Add(GraphTypeNames.ParseName(iface, TypeKind.INTERFACE)); + + objectType = _config + .DeclarationOptions? + .SchemaFormatStrategy? + .ApplySchemaItemRules(_config, objectType) ?? objectType; + + 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); + } + + 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..0069409ab --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeMakers/ScalarGraphTypeMaker.cs @@ -0,0 +1,71 @@ +// ************************************************************* +// 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 = _config + .DeclarationOptions? + .SchemaFormatStrategy? + .ApplySchemaItemRules(_config, scalarType) ?? scalarType; + + 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 53% rename from src/graphql-aspnet/Engine/TypeMakers/UnionGraphTypeMaker.cs rename to src/graphql-aspnet/Schemas/Generation/TypeMakers/UnionGraphTypeMaker.cs index 27ad7df53..edafae15d 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,27 +70,27 @@ public GraphTypeCreationResult CreateUnionFromProxy(IGraphUnionProxy proxy) var directives = directiveTemplates.CreateAppliedDirectives(); - var formatter = _schema.Configuration.DeclarationOptions.GraphNamingFormatter; - var name = formatter.FormatGraphTypeName(proxy.Name); var union = new UnionGraphType( - name, + proxy.Name, + proxy.InternalName, (IUnionGraphTypeMapper)proxy, - new SchemaItemPath(SchemaItemCollections.Types, name), + new ItemPath(ItemPathRoots.Types, proxy.Name), directives) { Description = proxy.Description, Publish = proxy.Publish, }; - result.GraphType = union; - // add dependencies to each type included in the union foreach (var type in proxy.Types) - { - union.AddPossibleGraphType( - formatter.FormatGraphTypeName(GraphTypeNames.ParseName(type, TypeKind.OBJECT)), - type); - } + union.AddPossibleGraphType(GraphTypeNames.ParseName(type, TypeKind.OBJECT), type); + + union = _config + .DeclarationOptions? + .SchemaFormatStrategy? + .ApplySchemaItemRules(_config, union) ?? union; + + result.GraphType = union; // add dependencies for the directives declared on the union foreach (var d in directives.Where(x => x.DirectiveType != null)) 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 70% rename from src/graphql-aspnet/Internal/TypeTemplates/ControllerActionGraphFieldTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/ControllerActionGraphFieldTemplate.cs index 98ab22331..b132a4392 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,31 +32,39 @@ 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) { } /// - /// 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. + /// Initializes a new instance of the class. /// - /// GraphRoutePath. - protected override SchemaItemPath GenerateFieldPath() + /// 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) + { + } + + /// + protected override ItemPath GenerateFieldPath() { // Various meta data fields about the method // ------------------------------------------- var graphMethodAttrib = this.AttributeProvider.SingleAttributeOfTypeOrDefault(); - var fieldType = graphMethodAttrib?.FieldType ?? SchemaItemCollections.Unknown; + var fieldType = graphMethodAttrib?.FieldType ?? ItemPathRoots.Unknown; - var routeFragment = graphMethodAttrib?.Template?.Trim() ?? Constants.Routing.ACTION_METHOD_META_NAME; - routeFragment = routeFragment.Replace(Constants.Routing.ACTION_METHOD_META_NAME, this.Method.Name).Trim(); + var pathFragment = graphMethodAttrib?.Template?.Trim() ?? Constants.Routing.ACTION_METHOD_META_NAME; + pathFragment = pathFragment.Replace(Constants.Routing.ACTION_METHOD_META_NAME, this.Method.Name).Trim(); // remove the parent fragment this method should be nested under if this method is marked as a root entry - var parentRouteFragment = (graphMethodAttrib?.IsRootFragment ?? false) ? string.Empty : this.Parent.Route.Path; - return new SchemaItemPath(SchemaItemPath.Join(fieldType, parentRouteFragment, routeFragment)); + var parentRouteFragment = (graphMethodAttrib?.IsRootFragment ?? false) ? string.Empty : this.Parent.ItemPath.Path; + return new ItemPath(ItemPath.Join(fieldType, parentRouteFragment, pathFragment)); } /// @@ -72,7 +80,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 +92,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 94% rename from src/graphql-aspnet/Internal/TypeTemplates/EnumGraphTypeTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/EnumGraphTypeTemplate.cs index 7709950a7..ac51535b3 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; @@ -59,7 +59,7 @@ protected override void ParseTemplateDefinition() _name = GraphTypeNames.ParseName(this.ObjectType, TypeKind.ENUM); this.Description = this.ObjectType.SingleAttributeOrDefault()?.Description?.Trim(); - this.Route = new SchemaItemPath(SchemaItemPath.Join(SchemaItemCollections.Enums, _name)); + this.ItemPath = new ItemPath(ItemPath.Join(ItemPathRoots.Types, _name)); // parse the enum values for later injection var labels = Enum.GetNames(this.ObjectType); @@ -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 80% rename from src/graphql-aspnet/Internal/TypeTemplates/EnumValueTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/EnumValueTemplate.cs index 79edf9202..b5807baf2 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)); + this.ItemPath = new ItemPath(ItemPath.Join(this.Parent.ItemPath.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/Schemas/Generation/TypeTemplates/GraphArgumentTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphArgumentTemplate.cs new file mode 100644 index 000000000..39127d0d7 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphArgumentTemplate.cs @@ -0,0 +1,481 @@ +// ************************************************************* +// 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.ComponentModel; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Threading; + 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.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + + /// + /// A template describing an argument declared a field. + /// + [DebuggerDisplay("{Name} (Type: {FriendlyObjectTypeName})")] + 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. + /// + /// The owner of this argument. + /// The parameter on which this + /// argument template is made. + public GraphArgumentTemplate(IGraphFieldTemplateBase parent, ParameterInfo parameter) + { + Validation.ThrowIfNull(parent, nameof(parent)); + Validation.ThrowIfNull(parameter, nameof(parameter)); + + _foundModifiers = new HashSet(); + + this.Parent = parent; + this.Parameter = parameter; + } + + /// + 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.AttributeProvider.SingleAttributeOrDefault(); + string name = null; + if (_argDeclaration != null) + { + name = _argDeclaration?.ArgumentName?.Trim(); + _foundModifiers.Add(ParameterModifiers.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; + + name = name.Replace(Constants.Routing.PARAMETER_META_NAME, this.Parameter.Name); + this.ItemPath = new GraphArgumentFieldPath(this.Parent.ItemPath, name); + + this.Description = this.AttributeProvider.SingleAttributeOrDefault()?.Description?.Trim(); + + if (_argDeclaration?.TypeExpression == null) + { + this.DeclaredTypeWrappers = null; + this.IsCustomTypeExpression = false; + } + else + { + var expression = GraphTypeExpression.FromDeclaration(_argDeclaration.TypeExpression); + if (!expression.IsValid) + { + _invalidTypeExpression = true; + } + else + { + this.DeclaredTypeWrappers = expression.Wrappers; + this.IsCustomTypeExpression = true; + } + } + + this.HasDefaultValue = this.Parameter.HasDefaultValue; + this.DefaultValue = null; + + if (this.HasDefaultValue) + { + if (this.Parameter.DefaultValue != null) + { + // enums will present their default value as a raw int + // convert it to a labelled value + if (this.ObjectType.IsEnum) + { + this.DefaultValue = Enum.ToObject(this.ObjectType, this.Parameter.DefaultValue); + } + else + { + this.DefaultValue = this.Parameter.DefaultValue; + } + } + } + + // 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.Clone(Constants.Other.DEFAULT_TYPE_EXPRESSION_TYPE_NAME); + + if (this.IsCustomTypeExpression) + this.TypeExpression = this.TypeExpression.ToFixed(); + + // perform any inspections and logic to determine + // how this argument performs within the application. + var fromServicesAttrib = this.Parameter.SingleAttributeOfTypeOrDefault(); + if (fromServicesAttrib != null) + _foundModifiers.Add(ParameterModifiers.ExplicitInjected); + + if (this.IsSourceDataArgument()) + _foundModifiers.Add(ParameterModifiers.ParentFieldResult); + + if (this.IsCancellationTokenArgument()) + _foundModifiers.Add(ParameterModifiers.CancellationToken); + + if (this.IsResolutionContext()) + _foundModifiers.Add(ParameterModifiers.ResolutionContext); + + if (this.IsHttpContext()) + _foundModifiers.Add(ParameterModifiers.HttpContext); + + if (this.MustBeInjected() && _foundModifiers.Count == 0) + _foundModifiers.Add(ParameterModifiers.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; + } + + /// + /// Determines whether this instance represents a parameter that should be marked as the "source data" + /// for the field its attached to. + /// + /// 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; + if (this.TypeExpression.IsListOfItems) + { + sourceType = typeof(IEnumerable<>).MakeGenericType(sourceType); + } + + return sourceType == this.DeclaredArgumentType; + } + + return false; + } + + /// + /// Determines whether this argument is (or can be) the cancellation token for this + /// argument's parent field. + /// + /// true if this argument is the dedicated cancellation token; otherwise, false. + protected virtual bool IsCancellationTokenArgument() + { + if (this.ObjectType == typeof(CancellationToken)) + { + // 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.ArgumentModifier.CouldBePartOfTheSchema()) + { + // internal parameters should not be injected into the object graph + // so they have no dependents + return Enumerable.Empty(); + } + + var types = new List(); + var expectedTypeKind = GraphValidation.ResolveTypeKind(this.ObjectType, TypeKind.INPUT_OBJECT); + types.Add(new DependentType(this.ObjectType, expectedTypeKind)); + + if (this.AppliedDirectives != null) + { + var directiveTypes = this.AppliedDirectives + .Where(x => x.DirectiveType != null) + .Select(x => new DependentType(x.DirectiveType, TypeKind.DIRECTIVE)); + + types.AddRange(directiveTypes); + } + + return types; + } + + /// + public void ValidateOrThrow(bool validateChildren = true) + { + 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.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."); + } + + // if the user declared a custom type expression it must be compatiable with the + // actual expected type expression of the C# code provided + var actualTypeExpression = GraphTypeExpression + .FromType(this.DeclaredArgumentType) + .Clone(GraphTypeNames.ParseName(this.ObjectType, TypeKind.INPUT_OBJECT)); + + if (!GraphTypeExpression.AreTypesCompatiable(actualTypeExpression, this.TypeExpression, false)) + { + throw new GraphTypeDeclarationException( + $"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 (_foundModifiers.Contains(ParameterModifiers.ExplicitSchemaItem)) + { + if (this.ObjectType.IsInterface) + { + // special error message for trying to use an interface in an argument + throw new GraphTypeDeclarationException( + $"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.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(ParameterModifiers.ExplicitInjected) + && _foundModifiers.Contains(ParameterModifiers.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(ParameterModifiers.ImplicitInjected) + && _foundModifiers.Contains(ParameterModifiers.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.ItemPath.Name; + + /// + /// Gets the reflected parameter data that defines this template. + /// + /// The parameter. + public ParameterInfo Parameter { get; } + + /// + public Type DeclaredArgumentType { get; private set; } + + /// + public string InternalName { get; private set; } + + /// + public bool IsExplicitDeclaration => true; + + /// + public IGraphFieldTemplateBase Parent { get; } + + /// + public string Description { get; private set; } + + /// + public ItemPath ItemPath { get; private set; } + + /// + public object DefaultValue { get; private set; } + + /// + public GraphTypeExpression TypeExpression { get; private set; } + + /// + public bool IsCustomTypeExpression { get; protected set; } + + /// + public Type ObjectType { get; private set; } + + /// + public ParameterModifiers ArgumentModifier { get; protected set; } + + /// + public string ParameterName => this.Parameter.Name; + + /// + public MetaGraphTypes[] DeclaredTypeWrappers { get; private set; } + + /// + public ICustomAttributeProvider AttributeProvider => this.Parameter; + + /// + public IEnumerable AppliedDirectives { get; private set; } + + /// + public bool HasDefaultValue { get; private set; } + + /// + /// Gets a string representing the name of the parameter's concrete type. + /// This is an an internal helper property for helpful debugging information only. + /// + /// The name of the parameter type friendly. + public string FriendlyObjectTypeName => this.Parameter.ParameterType.FriendlyName(); + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Internal/TypeTemplates/GraphControllerTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphControllerTemplate.cs similarity index 71% rename from src/graphql-aspnet/Internal/TypeTemplates/GraphControllerTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphControllerTemplate.cs index 76a6420ac..e7a357f3e 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; @@ -37,45 +37,46 @@ public class GraphControllerTemplate : NonLeafGraphTypeTemplateBase, IGraphContr public GraphControllerTemplate(Type controllerType) : base(controllerType) { - this.AllowedSchemaItemCollections = new HashSet + this.AllowedSchemaItemCollections = new HashSet { - SchemaItemCollections.Query, - SchemaItemCollections.Mutation, - SchemaItemCollections.Subscription, - SchemaItemCollections.Types, + ItemPathRoots.Query, + ItemPathRoots.Mutation, + ItemPathRoots.Subscription, + ItemPathRoots.Types, }; } /// - 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)); } /// - protected override SchemaItemPath GenerateFieldPath() + protected override ItemPath GenerateFieldPath() { var skipControllerLevelField = this.ObjectType.SingleAttributeOrDefault(); if (skipControllerLevelField != null) - return SchemaItemPath.Empty; + return ItemPath.Empty; string template = null; - var graphRoute = this.ObjectType.SingleAttributeOrDefault(); + var itemPath = this.ObjectType.SingleAttributeOrDefault(); var symanticName = this.ObjectType.Name.ReplaceLastInstanceOfCaseInvariant(Constants.CommonSuffix.CONTROLLER_SUFFIX, string.Empty); - // pull the route fragment from the controller level attribute, replacing any meta tags as appropriate + // pull the path fragment from the controller level attribute, replacing any meta tags as appropriate // if not found, default the name to the symantic name of the controller type itself (e.g. 'HumansController' becomes 'Humans') - if (graphRoute != null) + if (itemPath != null) { - template = graphRoute.Template?.Trim() ?? string.Empty; + template = itemPath.Template?.Trim() ?? string.Empty; template = template.Replace(Constants.Routing.CONTOLLER_META_NAME, symanticName); } @@ -84,7 +85,7 @@ protected override SchemaItemPath GenerateFieldPath() template = symanticName; } - return new SchemaItemPath(template); + return new ItemPath(template); } /// @@ -98,10 +99,10 @@ public override void ValidateOrThrow(bool validateChildren = true) $"is reserved for object types. To alter the naming scheme of a controller use '{nameof(GraphRouteAttribute)}' instead."); } - // ensure that root and a route path aren't defined (its one or the other) + // ensure that root and an item path aren't defined (its one or the other) var skipControllerLevelField = this.AttributeProvider.SingleAttributeOrDefault(); - var graphRoute = this.AttributeProvider.SingleAttributeOrDefault(); - if (skipControllerLevelField != null && graphRoute != null) + var itemPath = this.AttributeProvider.SingleAttributeOrDefault(); + if (skipControllerLevelField != null && itemPath != null) { throw new GraphTypeDeclarationException( $"The graph controller '{this.ObjectType.FriendlyName()}' defines a '{typeof(GraphRootAttribute).FriendlyName()}' and " + @@ -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); } /// @@ -155,7 +150,7 @@ protected override IGraphFieldTemplate CreatePropertyFieldTemplate(PropertyInfo /// Gets operation types to which this object can declare a field. /// /// The allowed operation types. - protected override HashSet AllowedSchemaItemCollections { get; } + protected override HashSet AllowedSchemaItemCollections { get; } /// /// Gets an enumeration of the extension methods this controller defines. diff --git a/src/graphql-aspnet/Internal/TypeTemplates/GraphDirectiveMethodTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphDirectiveMethodTemplate.cs similarity index 69% rename from src/graphql-aspnet/Internal/TypeTemplates/GraphDirectiveMethodTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphDirectiveMethodTemplate.cs index d2176a467..37b5eca5f 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,10 @@ 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.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 +32,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 +41,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 +68,21 @@ 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; } @@ -87,7 +105,7 @@ public virtual void Parse() true, false); - this.Route = new SchemaItemPath(SchemaItemPath.Join(this.Parent.Route.Path, this.Name)); + this.ItemPath = new ItemPath(ItemPath.Join(this.Parent.ItemPath.Path, this.Name)); // parse all input parameters into the method foreach (var parameter in this.Method.GetParameters()) @@ -129,14 +147,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 +167,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 +177,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 +201,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 +216,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 +244,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}). /// @@ -220,15 +265,15 @@ public IEnumerable RetrieveRequiredTypes() public string Description { get; private set; } /// - public SchemaItemPath Route { get; private set; } + public ItemPath ItemPath { get; private set; } /// public IReadOnlyList Arguments => _arguments; - /// + /// public Type ExpectedReturnType { get; protected set; } - /// + /// public bool IsAsyncField { get; protected set; } /// @@ -243,28 +288,31 @@ 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 { get; protected set; } /// - public string InternalName => this.Method.Name; + public ICustomAttributeProvider AttributeProvider { get; } /// - public ICustomAttributeProvider AttributeProvider => this.Method; + public IEnumerable AppliedDirectives { get; private set; } /// - public IEnumerable AppliedDirectives { get; private set; } + public bool IsCustomTypeExpression => false; } } \ No newline at end of file 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 59% rename from src/graphql-aspnet/Internal/TypeTemplates/GraphDirectiveTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphDirectiveTemplate.cs index 929d2d681..0a6a1f363 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); - this.Route = new SchemaItemPath(SchemaItemPath.Join(SchemaItemCollections.Directives, routeName)); - - foreach (var methodInfo in this.ObjectType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + var pathName = this.DetermineDirectiveName(); + this.ItemPath = new ItemPath(ItemPath.Join(ItemPathRoots.Directives, pathName)); + 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 54% rename from src/graphql-aspnet/Internal/TypeTemplates/GraphFieldTemplateBase.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphFieldTemplateBase.cs index 76011b2fd..354492d69 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,19 +58,48 @@ 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. // ------------------------------------ - this.Route = this.GenerateFieldPath(); + 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.ItemPath = 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; + this.IsCustomTypeExpression = false; } else { @@ -79,93 +110,74 @@ protected override void ParseTemplateDefinition() } else { + this.IsCustomTypeExpression = true; 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) + .Clone(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) + .Clone(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; } } + if (this.IsCustomTypeExpression) + typeExpression = typeExpression.ToFixed(); + 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 +198,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 +214,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 +264,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 +318,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 +345,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."); } @@ -331,8 +356,8 @@ public override void ValidateOrThrow(bool validateChildren = true) /// When overridden in a child class, this method builds the unique field path that will be assigned to this instance /// using the implementation rules of the concrete type. /// - /// GraphRoutePath. - protected abstract SchemaItemPath GenerateFieldPath(); + /// ItemPath. + protected abstract ItemPath GenerateFieldPath(); /// /// Type extensions used as batch methods required a speceial input and output signature for the runtime @@ -350,13 +375,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 +393,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 +403,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 +421,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 +447,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; + } + } - if (fieldAttribute.Types.Count == 1) + // extract types from [PossibleTypes] + var possibleTypesAttribute = this.AttributeProvider.SingleAttributeOfTypeOrDefault(); + if (possibleTypesAttribute?.PossibleTypes != null) { - var proxyType = fieldAttribute.Types.FirstOrDefault(); - if (proxyType != null) - proxy = GraphQLProviders.GraphTypeMakerProvider.CreateUnionProxyFromType(proxyType); + 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; + } + + // extract union name from [Union] + var unionAttribute = this.AttributeProvider.SingleAttributeOfTypeOrDefault(); + if (unionAttribute != null) + { + 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; } /// @@ -481,18 +581,23 @@ private void BuildUnionProxyInstance() public GraphTypeExpression TypeExpression { get; protected set; } /// - public virtual IGraphUnionProxy UnionProxy { get; protected set; } + public bool IsCustomTypeExpression { get; protected set; } - /// - /// Gets the possible types that can be returned by this field. - /// - /// The possible types. - protected List PossibleTypes { get; private set; } + /// + public virtual IGraphUnionProxy UnionProxy { get; protected 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 54% rename from src/graphql-aspnet/Internal/TypeTemplates/GraphTypeExtensionFieldTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/GraphTypeExtensionFieldTemplate.cs index 96544e01a..ab1002866 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) + .Clone(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,32 +104,42 @@ 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.ItemPath == null || !this.ItemPath.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 item path must conform to standard graphql naming rules. (Regex: {Constants.RegExPatterns.NameRegex} )", + this.ObjectType); } base.ValidateOrThrow(validateChildren); } /// - protected override SchemaItemPath GenerateFieldPath() + protected override ItemPath GenerateFieldPath() { // extract the parent name from the global meta data about the type being extended var parentName = GraphTypeNames.ParseName(_typeAttrib.TypeToExtend, TypeKind.OBJECT); - // an object method cannot contain any route pathing or nesting like controller methods can - // before creating hte route, ensure that the declared name, by itself, is valid for graphql + // an object method cannot contain any pathing or nesting like controller methods can + // before creating the item path, ensure that the declared name, by itself, is valid for graphql var graphName = _typeAttrib.Template?.Trim() ?? Constants.Routing.ACTION_METHOD_META_NAME; graphName = graphName.Replace(Constants.Routing.ACTION_METHOD_META_NAME, this.Method.Name).Trim(); - return new SchemaItemPath(SchemaItemPath.Join(SchemaItemCollections.Types, parentName, graphName)); + return new ItemPath(ItemPath.Join(ItemPathRoots.Types, parentName, graphName)); } /// 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 79% rename from src/graphql-aspnet/Internal/TypeTemplates/InputGraphFieldTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/InputGraphFieldTemplate.cs index c96028930..5832c7801 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,27 @@ protected override void ParseTemplateDefinition() { base.ParseTemplateDefinition(); + var objectType = GraphValidation.EliminateWrappersFromCoreType(this.DeclaredReturnType); + this.ObjectType = objectType; + _fieldDeclaration = this.AttributeProvider.SingleAttributeOfTypeOrDefault(); - this.Route = this.GenerateFieldPath(); + this.ItemPath = 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.Clone(Constants.Other.DEFAULT_TYPE_EXPRESSION_TYPE_NAME); + + if (this.IsCustomTypeExpression) + typeExpression = typeExpression.ToFixed(); 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}"; } /// @@ -78,12 +85,12 @@ public override IEnumerable RetrieveRequiredTypes() return list; } - private SchemaItemPath GenerateFieldPath() + private ItemPath 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)); + return new ItemPath(ItemPath.Join(this.Parent.ItemPath.Path, graphName)); } /// @@ -94,28 +101,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)}."); } } @@ -142,13 +149,10 @@ public override void ValidateOrThrow(bool validateChildren = true) public GraphTypeExpression TypeExpression { get; private set; } /// - public TypeKind OwnerTypeKind => TypeKind.INPUT_OBJECT; - - /// - public override string InternalFullName => $"{this.Parent.InternalFullName}.{this.Property.Name}"; + public bool IsCustomTypeExpression => _fieldDeclaration?.TypeExpression != null; /// - public override string InternalName => this.Property.Name; + public TypeKind OwnerTypeKind => TypeKind.INPUT_OBJECT; /// public MetaGraphTypes[] DeclaredTypeWrappers diff --git a/src/graphql-aspnet/Internal/TypeTemplates/InputObjectGraphTypeTemplate.cs b/src/graphql-aspnet/Schemas/Generation/TypeTemplates/InputObjectGraphTypeTemplate.cs similarity index 87% rename from src/graphql-aspnet/Internal/TypeTemplates/InputObjectGraphTypeTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/InputObjectGraphTypeTemplate.cs index 58703e44b..46d98c2bd 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)) { @@ -107,8 +107,8 @@ protected override void ParseTemplateDefinition() // ------------------------------------ // Common Metadata // ------------------------------------ - this.Route = new SchemaItemPath(SchemaItemPath.Join( - SchemaItemCollections.Types, + this.ItemPath = new ItemPath(ItemPath.Join( + ItemPathRoots.Types, GraphTypeNames.ParseName(this.ObjectType, TypeKind.INPUT_OBJECT))); this.Description = this.AttributeProvider.SingleAttributeOfTypeOrDefault()?.Description; @@ -127,7 +127,7 @@ protected override void ParseTemplateDefinition() var parsedTemplate = new InputGraphFieldTemplate(this, propInfo); parsedTemplate?.Parse(); - if (parsedTemplate?.Route == null || parsedTemplate.Route.RootCollection != SchemaItemCollections.Types) + if (parsedTemplate?.ItemPath == null || parsedTemplate.ItemPath.Root != ItemPathRoots.Types) { _invalidFields = _invalidFields ?? new List(); _invalidFields.Add(parsedTemplate); @@ -139,14 +139,14 @@ protected override void ParseTemplateDefinition() } // ensure no duplicates are possible - _duplicateNames = parsedItems.Select(x => x.Route.Path) + _duplicateNames = parsedItems.Select(x => x.ItemPath.Path) .GroupBy(x => x) .Where(x => x.Count() > 1) .Select(x => x.Key); - foreach (var field in parsedItems.Where(x => !_duplicateNames.Contains(x.Route.Path))) + foreach (var field in parsedItems.Where(x => !_duplicateNames.Contains(x.ItemPath.Path))) { - _fields.Add(field.Route.Path, field); + _fields.Add(field.ItemPath.Path, field); } } @@ -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.ItemPath.Root.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(); + private IEnumerable AllowedGraphCollectionTypes => ItemPathRoots.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 67% rename from src/graphql-aspnet/Internal/TypeTemplates/MethodGraphFieldTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/MethodGraphFieldTemplate.cs index 69b7d3447..51f2db37a 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,16 +35,30 @@ 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() + protected override ItemPath GenerateFieldPath() { - // an object method cannot contain any route pathing or nesting like controller methods can - // before creating hte route, ensure that the declared name, by itself, is valid for graphql + // an object method cannot contain any pathing or nesting like controller methods can + // before creating the field path, ensure that the declared name, by itself, is valid for graphql 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); - return new SchemaItemPath(SchemaItemPath.Join(this.Parent.Route.Path, graphName)); + GraphValidation.EnsureGraphNameOrThrow(this.InternalName, graphName); + return new ItemPath(ItemPath.Join(this.Parent.ItemPath.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..d52b051ee 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 + [DebuggerDisplay("Path: {ItemPath.Path}")] + 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 71% rename from src/graphql-aspnet/Internal/TypeTemplates/NonLeafGraphTypeTemplateBase.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/NonLeafGraphTypeTemplateBase.cs index a83aa387b..3b90d991a 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; @@ -52,9 +52,9 @@ internal NonLeafGraphTypeTemplateBase(Type objectType) _interfaces = new HashSet(); _securityPolicies = AppliedSecurityPolicyGroup.Empty; - this.AllowedSchemaItemCollections = new HashSet() + this.AllowedSchemaItemCollections = new HashSet() { - SchemaItemCollections.Types, + ItemPathRoots.Types, }; // customize the error message on the thrown exception for some helpful hints. @@ -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)) { @@ -92,7 +92,7 @@ protected override void ParseTemplateDefinition() // ------------------------------------ // Common Metadata // ------------------------------------ - this.Route = this.GenerateFieldPath(); + this.ItemPath = this.GenerateFieldPath(); this.Description = this.AttributeProvider.SingleAttributeOfTypeOrDefault()?.Description; // ------------------------------------ @@ -105,7 +105,7 @@ protected override void ParseTemplateDefinition() // ------------------------------------ _fields.Clear(); - var templateMembers = this.GatherPossibleTemplateMembers(); + var templateMembers = this.GatherPossibleFieldTemplates(); foreach (var member in templateMembers) { @@ -121,8 +121,8 @@ protected override void ParseTemplateDefinition() // but controllers are allowed to attach to the operation root collections // // this is used as a check against POCOs declaring [Query] fields for example - if (parsedTemplate?.Route == null - || !this.AllowedSchemaItemCollections.Contains(parsedTemplate.Route.RootCollection)) + if (parsedTemplate?.ItemPath == null + || !this.AllowedSchemaItemCollections.Contains(parsedTemplate.ItemPath.Root)) { _invalidFields = _invalidFields ?? new List(); _invalidFields.Add(parsedTemplate); @@ -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,16 +231,16 @@ 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 path that will be assigned to this item /// using the implementation rules of the concrete type. /// - /// GraphRoutePath. - protected virtual SchemaItemPath GenerateFieldPath() + /// ItemPath. + protected virtual ItemPath GenerateFieldPath() { - // a standard graph object cannot contain any route pathing or nesting like controllers can - // before creating hte route, ensure that the declared name, by itself, is valid for graphql + // a standard graph object cannot contain any pathing or nesting like controllers can + // before creating the path, ensure that the declared name, by itself, is valid for graphql var graphName = GraphTypeNames.ParseName(this.ObjectType, TypeKind.OBJECT); - return new SchemaItemPath(SchemaItemPath.Join(SchemaItemCollections.Types, graphName)); + return new ItemPath(ItemPath.Join(ItemPathRoots.Types, graphName)); } /// @@ -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.ItemPath.Root.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,17 +275,11 @@ 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. /// /// The allowed schema item collections. - protected virtual HashSet AllowedSchemaItemCollections { get; } + protected virtual HashSet AllowedSchemaItemCollections { get; } /// /// Gets the declared interfaces on this item. 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 53% rename from src/graphql-aspnet/Internal/TypeTemplates/PropertyGraphFieldTemplate.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/PropertyGraphFieldTemplate.cs index bfbdc7e9a..1d2798dc2 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; @@ -28,14 +28,31 @@ namespace GraphQL.AspNet.Internal.TypeTemplates /// An template describing a field on an OBJECT or INTERFACE graph type that /// is created from a C# object property. /// - [DebuggerDisplay("Route: {Route.Path}")] - public class PropertyGraphFieldTemplate : GraphFieldTemplateBase, IGraphFieldResolverMethod + [DebuggerDisplay("Path: {ItemPath.Path}")] + 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) @@ -47,36 +64,36 @@ public PropertyGraphFieldTemplate(IGraphTypeTemplate parent, PropertyInfo propIn } /// - protected override SchemaItemPath GenerateFieldPath() + protected override ItemPath GenerateFieldPath() { - // A class property cannot contain any route pathing or nesting like controllers or actions. - // Before creating hte route, ensure that the declared name, by itself, is valid for graphql such that resultant + // A class property cannot contain any pathing or nesting like controllers or actions. + // Before creating the item path, ensure that the declared name, by itself, is valid for graphql such that resultant // global path for this property will also be correct. var graphName = this.AttributeProvider.SingleAttributeOfTypeOrDefault()?.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)); + return new ItemPath(ItemPath.Join(this.Parent.ItemPath.Path, graphName)); } /// 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..20a98daa3 --- /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?.ItemPath.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..eb3bf5093 --- /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.Directives; + 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("{ItemPath.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?.ItemPath.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?.ItemPath.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..918769840 --- /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.ItemPath = new ItemPath(ItemPath.Join(ItemPathRoots.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 80% rename from src/graphql-aspnet/Internal/TypeTemplates/SchemaItemTemplateBase.cs rename to src/graphql-aspnet/Schemas/Generation/TypeTemplates/SchemaItemTemplateBase.cs index ef4ae37e6..698af1c34 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,26 +87,33 @@ 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); } - if (this.Route == null || !this.Route.IsValid) + if (this.ItemPath == null || !this.ItemPath.IsValid) { throw new GraphTypeDeclarationException( - $"The template item '{this.InternalFullName}' declares an invalid route of '{this.Route?.Path ?? ""}'. " + - $"Each segment of the route must conform to standard graphql naming rules. (Regex: {Constants.RegExPatterns.NameRegex} )", + $"The schema template item '{this.InternalName}' declares an invalid path of '{this.ItemPath?.Raw ?? ""}'. " + + $"Each segment of the item path 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(); } @@ -118,16 +125,13 @@ public virtual void ValidateOrThrow(bool validateChildren = true) public virtual string Description { get; protected set; } /// - public virtual string Name => this.Route?.Name; - - /// - public SchemaItemPath Route { get; protected set; } + public virtual string Name => this.ItemPath?.Name; /// - public abstract string InternalFullName { get; } + public ItemPath ItemPath { get; protected set; } /// - 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..84f358b4b --- /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.ItemPath = new ItemPath(ItemPath.Join(ItemPathRoots.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..ff0f3746c 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; @@ -59,8 +58,9 @@ public GraphSchema() $"special characters (such as carrots for generics) and does not start with an underscore."); } - this.Route = new SchemaItemPath(SchemaItemCollections.Schemas, graphName); + this.ItemPath = new ItemPath(ItemPathRoots.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; } @@ -86,6 +89,6 @@ public GraphSchema() public IAppliedDirectiveCollection AppliedDirectives { get; } /// - public SchemaItemPath Route { get; } + public ItemPath ItemPath { get; } } } \ No newline at end of file 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.cs b/src/graphql-aspnet/Schemas/GraphTypeExpression.cs index dda438628..73b469fdc 100644 --- a/src/graphql-aspnet/Schemas/GraphTypeExpression.cs +++ b/src/graphql-aspnet/Schemas/GraphTypeExpression.cs @@ -14,6 +14,7 @@ namespace GraphQL.AspNet.Schemas using System.Collections.Generic; using System.Linq; using System.Text; + using GraphQL.AspNet.Common; using GraphQL.AspNet.Execution.Parsing.Lexing.Tokens; using GraphQL.AspNet.Schemas.TypeSystem; @@ -54,6 +55,13 @@ public GraphTypeExpression(string typeName, params MetaGraphTypes[] wrappers) /// The new type expression wrapped in the provided value. public GraphTypeExpression WrapExpression(MetaGraphTypes wrapper) { + if (this.IsFixed) + { + throw new InvalidOperationException( + "Fixed expressions are immutable " + + "and cannot be altered"); + } + var newArray = new MetaGraphTypes[Wrappers.Length + 1]; this.Wrappers.CopyTo(newArray, 1); newArray[0] = wrapper; @@ -139,13 +147,51 @@ private bool MatchAndExtract(object value, Span modifiers, Func< } /// - /// Clones this expression but with a new, core graph type name. + /// + /// Inspects the supplied type expression and determines that if the structure of the + /// type expression is compatiable with this instance. + /// + /// + /// Two type expressions are considered structurally compatiable if their type names are the same + /// and their list modifiers are the same. Whether a list or type is nullable is not + /// considered. + /// + /// + /// Example:
+ /// [[Type]] and [[Type!]!] ARE structurally compatiable.
+ /// [[Type]] and [Type] ARE NOT structurally compatiable.
+ ///
///
- /// The new graph type name. - /// GraphTypeExpression. - public GraphTypeExpression CloneTo(string graphTypeName) + /// The updated type expression. + /// true if [is structrual match] [the specified updated type expression]; otherwise, false. + public bool IsStructruallyCompatiable(GraphTypeExpression typeExpression) { - return new GraphTypeExpression(graphTypeName, this.Wrappers); + Validation.ThrowIfNull(typeExpression, nameof(typeExpression)); + + if (typeExpression.TypeName != this.TypeName) + return false; + + var left = this; + var right = typeExpression; + + while (left.IsNonNullable) + left = left.UnWrapExpression(); + + while (right.IsNonNullable) + right = right.UnWrapExpression(); + + if (left.IsListOfItems && right.IsListOfItems) + { + left = left.UnWrapExpression(); + right = right.UnWrapExpression(); + + return left.IsStructruallyCompatiable(right); + } + + if (left.IsListOfItems || right.IsListOfItems) + return false; + + return true; } /// @@ -154,7 +200,90 @@ public GraphTypeExpression CloneTo(string graphTypeName) /// GraphTypeExpression. public GraphTypeExpression Clone() { - return new GraphTypeExpression(this.TypeName, this.Wrappers); + var instance = new GraphTypeExpression(this.TypeName, this.Wrappers); + instance.IsFixed = this.IsFixed; + + return instance; + } + + /// + /// Clones this instance into a new copy of itself. + /// + /// + /// Applying a new nullability strategy on a fixed type expression may cause the + /// clone to become unfixed. + /// + /// The nullability strategy to apply to the + /// cloned instance. If no changes are applied to the expression, and the expression + /// is fixed, it will retain its fixed flag. + /// GraphTypeExpression. + public GraphTypeExpression Clone(GraphTypeExpressionNullabilityStrategies nullabilityStrategy) + { + return this.Clone(this.TypeName, nullabilityStrategy); + } + + /// + /// Clones this expression but with a new, core graph type name. + /// + /// + /// Applying a new nullability strategy on a fixed type expression may cause the + /// clone to become unfixed. + /// + /// A new graph type name to apply to the cloned instance. + /// The nullability strategy to apply to the + /// cloned instance. If no changes are applied to the expression, and the expression + /// is fixed, it will retain its fixed flag. + /// GraphTypeExpression. + public GraphTypeExpression Clone(string graphTypeName, GraphTypeExpressionNullabilityStrategies nullabilityStrategy = GraphTypeExpressionNullabilityStrategies.None) + { + graphTypeName = Validation.ThrowIfNullWhiteSpaceOrReturn(graphTypeName, nameof(graphTypeName)); + + var wrappers = this.Wrappers.ToList(); + + if (nullabilityStrategy.HasFlag(GraphTypeExpressionNullabilityStrategies.NonNullLists)) + { + if (wrappers.Count > 0) + { + var wrappersNew = new List(); + if (wrappers[0] == MetaGraphTypes.IsList) + wrappersNew.Add(MetaGraphTypes.IsNotNull); + + wrappersNew.Add(wrappers[0]); + + for (var i = 1; i < wrappers.Count; i++) + { + var prevWrapper = wrappers[i - 1]; + var thisWrapper = wrappers[i]; + + // ensure every list is prefixed with a not-null + if (thisWrapper == MetaGraphTypes.IsList + && prevWrapper != MetaGraphTypes.IsNotNull) + { + wrappersNew.Add(MetaGraphTypes.IsNotNull); + } + + wrappersNew.Add(thisWrapper); + } + + wrappers = wrappersNew; + } + } + + if (nullabilityStrategy.HasFlag(GraphTypeExpressionNullabilityStrategies.NonNullType)) + { + if (wrappers.Count == 0 + || wrappers[wrappers.Count - 1] != MetaGraphTypes.IsNotNull) + { + wrappers.Add(MetaGraphTypes.IsNotNull); + } + } + + // the cloned instance is only fixed if this instance is fixed + // and no structural changes were made + var clone = new GraphTypeExpression(graphTypeName, wrappers); + clone.IsFixed = this.IsFixed && wrappers.SequenceEqual(this.Wrappers); + + return clone; } /// @@ -225,6 +354,20 @@ public GraphTypeExpression UnWrapExpression(MetaGraphTypes? wrapperToRemove = nu public bool IsListOfItems => (this.Wrappers.Length > 0 && this.Wrappers[0] == MetaGraphTypes.IsList) || (this.Wrappers.Length > 1 && this.Wrappers[1] == MetaGraphTypes.IsList); + /// + /// + /// Gets a value indicating whether this instance is fixed in place and + /// defined by developer developer code. Fixed instances cannot be further wrapped + /// or have their nullability settings changed. + /// + /// + /// Non-Fixed instances represent type expressions interpreted + /// from source code. Fixed instances represent those defined via explicit attribution. + /// + /// + /// true if this instance is customized by the developer; otherwise, false. + public bool IsFixed { get; private set; } + /// /// Determines whether the specified is equal to this instance. /// @@ -293,5 +436,16 @@ public override string ToString() return builder.ToString(); } + + /// + /// Converts the type expression to a fixed instance. + /// + /// GraphTypeExpression. + public GraphTypeExpression ToFixed() + { + var expression = this.Clone(); + expression.IsFixed = true; + return expression; + } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Configuration/Formatting/GraphNameFormatStrategy.cs b/src/graphql-aspnet/Schemas/GraphTypeExpressionNullabilityStrategies.cs similarity index 51% rename from src/graphql-aspnet/Configuration/Formatting/GraphNameFormatStrategy.cs rename to src/graphql-aspnet/Schemas/GraphTypeExpressionNullabilityStrategies.cs index 28690dcaf..cb1140b12 100644 --- a/src/graphql-aspnet/Configuration/Formatting/GraphNameFormatStrategy.cs +++ b/src/graphql-aspnet/Schemas/GraphTypeExpressionNullabilityStrategies.cs @@ -7,18 +7,19 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Configuration.Formatting +namespace GraphQL.AspNet.Schemas { + using System; + /// - /// A set of internally supported name formatting options for various names and values created - /// for a schema. + /// A set of available strategy for setting not-null properties are + /// various aspects of a type expression. /// - public enum GraphNameFormatStrategy + [Flags] + public enum GraphTypeExpressionNullabilityStrategies { - ProperCase, - CamelCase, - UpperCase, - LowerCase, - NoChanges, + None = 0, + NonNullType = 1, + NonNullLists = 2, } } \ 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..70ebad864 100644 --- a/src/graphql-aspnet/Schemas/GraphTypeExpression_Statics.cs +++ b/src/graphql-aspnet/Schemas/GraphTypeExpression_Statics.cs @@ -13,9 +13,10 @@ namespace GraphQL.AspNet.Schemas using System.Collections.Generic; using System.Diagnostics; using System.Linq; + using System.Runtime.CompilerServices; 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 +264,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..a52676ca1 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'. /// @@ -366,7 +339,7 @@ public static bool IsValidGraphType(Type type, bool throwOnFailure = false) /// true if not nullable, false otherwise. public static bool IsNotNullable(Type typeToCheck) { - // GraphId is the only "nullable" struct known to the system + Validation.ThrowIfNull(typeToCheck, nameof(typeToCheck)); return typeToCheck.IsValueType && !typeToCheck.IsNullableOfT(); } } 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/GraphArgumentFieldPath.cs b/src/graphql-aspnet/Schemas/Structural/GraphArgumentFieldPath.cs index 58b5e92b6..979fc651c 100644 --- a/src/graphql-aspnet/Schemas/Structural/GraphArgumentFieldPath.cs +++ b/src/graphql-aspnet/Schemas/Structural/GraphArgumentFieldPath.cs @@ -11,21 +11,21 @@ namespace GraphQL.AspNet.Schemas.Structural { using System.Collections.Generic; using GraphQL.AspNet.Common.Extensions; - using RouteConstants = GraphQL.AspNet.Constants.Routing; + using PathConstants = GraphQL.AspNet.Constants.Routing; /// /// An implemention of the schema item path with special considering for input arguments /// to differentiate them from normal field routes. /// - public class GraphArgumentFieldPath : SchemaItemPath + public class GraphArgumentFieldPath : ItemPath { /// /// Initializes a new instance of the class. /// /// The parent path. /// Name of the argument. - public GraphArgumentFieldPath(SchemaItemPath parentPath, string argumentName) - : base(SchemaItemPath.Join(parentPath.Path, argumentName)) + public GraphArgumentFieldPath(ItemPath parentPath, string argumentName) + : base(ItemPath.Join(parentPath.Path, argumentName)) { } @@ -42,8 +42,8 @@ protected override string GeneratePathString(IReadOnlyList pathFragments return base.GeneratePathString(pathFragments); var lastItem = pathFragments[pathFragments.Count - 1]; - return string.Join(RouteConstants.PATH_SEPERATOR, pathFragments.SkipLastN(1)) - + RouteConstants.DELIMITER_ROOT_START + lastItem + RouteConstants.DELIMITER_ROOT_END; + return string.Join(PathConstants.PATH_SEPERATOR, pathFragments.SkipLastN(1)) + + PathConstants.DELIMITER_ROOT_START + lastItem + PathConstants.DELIMITER_ROOT_END; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Structural/GraphFieldArgumentCollection.cs b/src/graphql-aspnet/Schemas/Structural/GraphFieldArgumentCollection.cs index 519cb6692..ffc112e74 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,22 +54,22 @@ 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), + _owner.ItemPath.CreateChild(name), concreteType, false); @@ -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), + _owner.ItemPath.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..736302f97 100644 --- a/src/graphql-aspnet/Schemas/Structural/GraphFieldCollection.cs +++ b/src/graphql-aspnet/Schemas/Structural/GraphFieldCollection.cs @@ -13,14 +13,10 @@ namespace GraphQL.AspNet.Schemas.Structural using System.Collections; using System.Collections.Generic; using System.Diagnostics; - using System.Threading.Tasks; 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; /// /// A collection of owned by a . @@ -28,17 +24,14 @@ namespace GraphQL.AspNet.Schemas.Structural [DebuggerDisplay("Count = {Count}")] public class GraphFieldCollection : IGraphFieldCollection { - private readonly IGraphType _owner; private readonly Dictionary _fields; private readonly List _requiredFields; /// /// Initializes a new instance of the class. /// - /// The graphtype that owns this field collection. - public GraphFieldCollection(IGraphType owner) + public GraphFieldCollection() { - _owner = Validation.ThrowIfNullOrReturn(owner, nameof(owner)); _fields = new Dictionary(StringComparer.Ordinal); _requiredFields = new List(); } @@ -50,20 +43,14 @@ public IGraphField AddField(IGraphField field) if (_fields.ContainsKey(field.Name)) { - Type ownerType = null; - if (_owner is ITypedSchemaItem tsi) - ownerType = tsi.ObjectType; - throw new GraphTypeDeclarationException( - $"Duplicate field name detected. The graph type '{_owner.Name}' already declares a field named '{field.Name}'. " + + $"Duplicate field name detected. The collection already declares a field named '{field.Name}' (Path: {field.ItemPath}). " + "This may occur if a type extension is added with the same name as an existing field, " + "when an attempt is made to extend an OBJECT type through a direct field extension and an indirect " + "interface field extension with the same name or when a schema attempts to include multiple overloads " + - "of the same method on a class, interface or struct.", - ownerType); + "of the same method on a class, interface or struct."); } - field.AssignParent(_owner); _fields.Add(field.Name, field); if (field.TypeExpression.IsNonNullable) _requiredFields.Add(field); @@ -71,38 +58,6 @@ public IGraphField AddField(IGraphField field) return field; } - /// - /// Creates and adds a new to the growing collection. - /// - /// The expected type of the source data supplied to the resolver. - /// The expected type of data to be returned from this field. - /// The formatted name of the field as it will appear in the object graph. - /// The item representing how this field returns a graph type. - /// The formal route that uniquely identifies this field in the object graph. - /// The resolver used to fulfil requests to this field. - /// The description to assign to the field. - /// IGraphTypeField. - internal IGraphField AddField( - string fieldName, - GraphTypeExpression typeExpression, - SchemaItemPath route, - Func> resolver, - string description = null) - where TSource : class - { - var field = new MethodGraphField( - fieldName, - typeExpression, - route, - GraphValidation.EliminateNextWrapperFromCoreType(typeof(TReturn)), - typeof(TReturn), - FieldResolutionMode.PerSourceItem, - new FunctionGraphFieldResolver(resolver)); - field.Description = description; - - return this.AddField(field); - } - /// public IGraphField FindField(string fieldName) { @@ -143,9 +98,6 @@ public IGraphField this[string fieldName] /// public int Count => _fields.Count; - /// - public IGraphType Owner => _owner; - /// public IReadOnlyList RequiredFields => _requiredFields; diff --git a/src/graphql-aspnet/Schemas/Structural/InputGraphFieldCollection.cs b/src/graphql-aspnet/Schemas/Structural/InputGraphFieldCollection.cs index d34b0d8c9..cef0b3b42 100644 --- a/src/graphql-aspnet/Schemas/Structural/InputGraphFieldCollection.cs +++ b/src/graphql-aspnet/Schemas/Structural/InputGraphFieldCollection.cs @@ -23,7 +23,6 @@ namespace GraphQL.AspNet.Schemas.Structural [DebuggerDisplay("Count = {Count}")] internal class InputGraphFieldCollection : IInputGraphFieldCollection { - private readonly IInputObjectGraphType _owner; private readonly Dictionary _fields; private readonly List _requiredFields; private readonly List _nonNullableFields; @@ -31,10 +30,8 @@ internal class InputGraphFieldCollection : IInputGraphFieldCollection /// /// Initializes a new instance of the class. /// - /// The graphtype that owns this field collection. - public InputGraphFieldCollection(IInputObjectGraphType owner) + public InputGraphFieldCollection() { - _owner = Validation.ThrowIfNullOrReturn(owner, nameof(owner)); _fields = new Dictionary(StringComparer.Ordinal); _requiredFields = new List(); _nonNullableFields = new List(); @@ -52,11 +49,10 @@ public IInputGraphField AddField(IInputGraphField field) if (_fields.ContainsKey(field.Name)) { throw new GraphTypeDeclarationException( - $"Duplicate field name detected. The input object type '{_owner.Name}' already " + - $"declares a field named '{field.Name}'."); + $"Duplicate field name detected. The collection already " + + $"declares a field named '{field.Name}' (Path: {field.ItemPath})."); } - field.AssignParent(_owner); _fields.Add(field.Name, field); if (field.IsRequired) _requiredFields.Add(field); @@ -106,9 +102,6 @@ public IInputGraphField this[string fieldName] /// public int Count => _fields.Count; - /// - public IInputObjectGraphType Owner => _owner; - /// public IReadOnlyList RequiredFields => _requiredFields; diff --git a/src/graphql-aspnet/Schemas/Structural/GraphIntrospectionFieldPath.cs b/src/graphql-aspnet/Schemas/Structural/IntrospectedFieldPath.cs similarity index 68% rename from src/graphql-aspnet/Schemas/Structural/GraphIntrospectionFieldPath.cs rename to src/graphql-aspnet/Schemas/Structural/IntrospectedFieldPath.cs index 47e467106..63b2d7418 100644 --- a/src/graphql-aspnet/Schemas/Structural/GraphIntrospectionFieldPath.cs +++ b/src/graphql-aspnet/Schemas/Structural/IntrospectedFieldPath.cs @@ -14,14 +14,14 @@ namespace GraphQL.AspNet.Schemas.Structural /// A field path that identifies an introspection item /// in the schema. /// - internal class GraphIntrospectionFieldPath : SchemaItemPath + internal class IntrospectedFieldPath : ItemPath { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Name of the item. - public GraphIntrospectionFieldPath(string itemName) - : base(SchemaItemCollections.Introspection, itemName) + public IntrospectedFieldPath(string itemName) + : base(ItemPathRoots.Introspection, itemName) { } } diff --git a/src/graphql-aspnet/Schemas/Structural/IntrospectedRoutePath.cs b/src/graphql-aspnet/Schemas/Structural/IntrospectedItemPath.cs similarity index 59% rename from src/graphql-aspnet/Schemas/Structural/IntrospectedRoutePath.cs rename to src/graphql-aspnet/Schemas/Structural/IntrospectedItemPath.cs index 7734749f2..394aa01e2 100644 --- a/src/graphql-aspnet/Schemas/Structural/IntrospectedRoutePath.cs +++ b/src/graphql-aspnet/Schemas/Structural/IntrospectedItemPath.cs @@ -10,29 +10,29 @@ namespace GraphQL.AspNet.Schemas.Structural { /// - /// An internal overload of that allows reserved names + /// An internal overload of that allows reserved names /// as part of the path segment. /// - internal sealed class IntrospectedRoutePath : SchemaItemPath + internal sealed class IntrospectedItemPath : ItemPath { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The collection the route belongs to. - /// The individual path segments of the route. - public IntrospectedRoutePath(Execution.SchemaItemCollections collection, params string[] pathSegments) + /// The collection the path belongs to. + /// The individual segments of the path. + public IntrospectedItemPath(Execution.ItemPathRoots collection, params string[] pathSegments) : base(collection, pathSegments) { } /// - /// Validates that the given route fragment is valid and usable. + /// Validates that the given path fragment is valid and usable. /// /// The fragment. /// System.Boolean. protected override bool ValidateFragment(string fragment) { - if (Constants.ReservedNames.IntrospectableRouteNames.Contains(fragment)) + if (Constants.ReservedNames.IntrospectablePathNames.Contains(fragment)) return true; return Constants.RegExPatterns.NameRegex.IsMatch(fragment); diff --git a/src/graphql-aspnet/Schemas/Structural/SchemaItemPath.cs b/src/graphql-aspnet/Schemas/Structural/ItemPath.cs similarity index 61% rename from src/graphql-aspnet/Schemas/Structural/SchemaItemPath.cs rename to src/graphql-aspnet/Schemas/Structural/ItemPath.cs index dafe15709..8fa11cbb2 100644 --- a/src/graphql-aspnet/Schemas/Structural/SchemaItemPath.cs +++ b/src/graphql-aspnet/Schemas/Structural/ItemPath.cs @@ -15,55 +15,56 @@ namespace GraphQL.AspNet.Schemas.Structural using System.Diagnostics; using System.Linq; using GraphQL.AspNet.Execution; - using RouteConstants = GraphQL.AspNet.Constants.Routing; + using PathConstants = GraphQL.AspNet.Constants.Routing; /// - /// A representation of a hierarchical path to a single item (type, field, argument etc.) within a graph schema. + /// A representation of a hierarchical path to a single item tracked (type, field, argument etc.) within a graph schema. /// [DebuggerDisplay("{Path}")] - public partial class SchemaItemPath : IEnumerable + public partial class ItemPath : IEnumerable { /// - /// Gets a special route path used to identify a "root level" fragment that has no path + /// Gets a special path used to identify a "root level" fragment that has no path /// but is considered valid. /// /// The empty. - public static SchemaItemPath Empty { get; } + public static ItemPath Empty { get; } /// - /// Initializes static members of the class. + /// Initializes static members of the class. /// - static SchemaItemPath() + static ItemPath() { - Empty = new SchemaItemPath(string.Empty); + Empty = new ItemPath(string.Empty); Empty._isValid = true; Empty._pathInitialized = true; } private object _lock = new object(); private bool _pathInitialized; - private SchemaItemCollections _rootCollection; + private ItemPathRoots _rootCollection; private string _path; - private SchemaItemPath _parentField; + private string _pathMinusCollection; + private ItemPath _parentField; private string _name; private bool _isTopLevelField; private bool _isValid; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The collection the route belongs to. - /// The individual path segments of the route. - public SchemaItemPath(SchemaItemCollections collection, params string[] pathSegments) - : this(SchemaItemPath.Join(collection, pathSegments)) + /// The path root the instance belongs to. + /// The individual path segments of the path. + public ItemPath(ItemPathRoots collection, params string[] pathSegments) + : this(ItemPath.Join(collection, pathSegments)) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The full path. - public SchemaItemPath(string fullPath) + public ItemPath(string fullPath) { this.Raw = fullPath; @@ -72,9 +73,10 @@ public SchemaItemPath(string fullPath) _parentField = null; _isTopLevelField = false; _path = string.Empty; + _pathMinusCollection = string.Empty; _name = string.Empty; _isValid = false; - _rootCollection = SchemaItemCollections.Unknown; + _rootCollection = ItemPathRoots.Unknown; } private void EnsurePathInitialized() @@ -87,56 +89,43 @@ private void EnsurePathInitialized() if (_pathInitialized) return; - var workingPath = SchemaItemPath.NormalizeFragment(this.Raw); + var workingPath = ItemPath.NormalizeFragment(this.Raw); // split the path into its fragments - List pathFragments = workingPath.Split(new[] { RouteConstants.PATH_SEPERATOR }, StringSplitOptions.None).ToList(); - + List pathFragments = workingPath.Split(new[] { PathConstants.PATH_SEPERATOR }, StringSplitOptions.None).ToList(); switch (pathFragments[0]) { - case RouteConstants.QUERY_ROOT: - _rootCollection = SchemaItemCollections.Query; - break; - - case RouteConstants.MUTATION_ROOT: - _rootCollection = SchemaItemCollections.Mutation; + case PathConstants.QUERY_ROOT: + _rootCollection = ItemPathRoots.Query; break; - case RouteConstants.SCALAR_ROOT: - _rootCollection = SchemaItemCollections.Scalars; + case PathConstants.MUTATION_ROOT: + _rootCollection = ItemPathRoots.Mutation; break; - case RouteConstants.TYPE_ROOT: - _rootCollection = SchemaItemCollections.Types; + case PathConstants.SUBSCRIPTION_ROOT: + _rootCollection = ItemPathRoots.Subscription; break; - case RouteConstants.ENUM_ROOT: - _rootCollection = SchemaItemCollections.Enums; + case PathConstants.TYPE_ROOT: + _rootCollection = ItemPathRoots.Types; break; - case RouteConstants.DIRECTIVE_ROOT: - _rootCollection = SchemaItemCollections.Directives; + case PathConstants.DIRECTIVE_ROOT: + _rootCollection = ItemPathRoots.Directives; break; - case RouteConstants.SUBSCRIPTION_ROOT: - _rootCollection = SchemaItemCollections.Subscription; + case PathConstants.INTROSPECTION_ROOT: + _rootCollection = ItemPathRoots.Introspection; break; - case RouteConstants.INTROSPECTION_ROOT: - _rootCollection = SchemaItemCollections.Introspection; - break; - - case RouteConstants.SCHEMA_ROOT: - _rootCollection = SchemaItemCollections.Schemas; - break; - - case RouteConstants.DOCUMENT_ROOT: - _rootCollection = SchemaItemCollections.Document; + case PathConstants.SCHEMA_ROOT: + _rootCollection = ItemPathRoots.Schemas; break; } // ensure each fragment matches the naming specification - foreach (var fragment in pathFragments.Skip(_rootCollection == SchemaItemCollections.Unknown ? 0 : 1)) + foreach (var fragment in pathFragments.Skip(_rootCollection == ItemPathRoots.Unknown ? 0 : 1)) { if (!this.ValidateFragment(fragment)) return; @@ -144,11 +133,17 @@ private void EnsurePathInitialized() _name = pathFragments[pathFragments.Count - 1]; if (pathFragments.Count > 1) - _parentField = new SchemaItemPath(string.Join(RouteConstants.PATH_SEPERATOR, pathFragments.Take(pathFragments.Count - 1))); + _parentField = new ItemPath(string.Join(PathConstants.PATH_SEPERATOR, pathFragments.Take(pathFragments.Count - 1))); - _isTopLevelField = pathFragments.Count == 1 || (pathFragments.Count == 2 && _rootCollection > SchemaItemCollections.Unknown); // e.g. "[query]/name" + _isTopLevelField = pathFragments.Count == 1 || (pathFragments.Count == 2 && _rootCollection > ItemPathRoots.Unknown); // e.g. "[query]/name" _isValid = _name.Length > 0; _path = this.GeneratePathString(pathFragments); + + if (_rootCollection == ItemPathRoots.Unknown) + _pathMinusCollection = this.GeneratePathString(pathFragments); + else + _pathMinusCollection = $"{PathConstants.PATH_SEPERATOR}{this.GeneratePathString(pathFragments.Skip(1).ToList())}"; + _pathInitialized = true; } } @@ -160,11 +155,11 @@ private void EnsurePathInitialized() /// System.String. protected virtual string GeneratePathString(IReadOnlyList pathFragments) { - return string.Join(RouteConstants.PATH_SEPERATOR, pathFragments); + return string.Join(PathConstants.PATH_SEPERATOR, pathFragments); } /// - /// Validates that the given route fragment is valid and usable. + /// Validates that the given path fragment is valid and usable. /// /// The fragment. /// System.Boolean. @@ -174,23 +169,34 @@ protected virtual bool ValidateFragment(string fragment) } /// - /// Determines whether the given route represents the same path as this route. + /// Determines whether the given path represents the same path as this instance. /// - /// The route. - /// true if the routes point to the same location; otherwise, false. - public bool IsSameRoute(SchemaItemPath route) + /// The other item path to compare against. + /// true if the paths point to the same location; otherwise, false. + public bool IsSamePath(ItemPath otherPath) { - return (route?.Path?.Length ?? 0) > 0 && route.Path == this.Path; + return (otherPath?.Path?.Length ?? 0) > 0 && otherPath.Path == this.Path; } /// /// Determines whether this instance contains, as a child, the given path. /// - /// The path. + /// The other item path to compare against. /// true if this path contains the supplied path; otherwise, false. - public bool HasChildRoute(SchemaItemPath route) + public bool HasChildPath(ItemPath otherPath) { - return (route?.Path?.Length ?? 0) > 0 && route.Path.StartsWith(this.Path); + return (otherPath?.Path?.Length ?? 0) > 0 && otherPath.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 ItemPathRoots pathRoot, out string path) + { + pathRoot = this.Root; + path = _pathMinusCollection; } /// @@ -226,10 +232,10 @@ public bool IsValid } /// - /// Gets the root collection represented by this route represents (e.g. query, mutation, type system etc.). + /// Gets the root path collection this path belongs to(e.g. query, mutation, type system etc.). /// /// The type of the field. - public SchemaItemCollections RootCollection + public ItemPathRoots Root { get { @@ -242,7 +248,7 @@ public SchemaItemCollections RootCollection /// Gets this item's parent path, if any. /// /// The parent. - public SchemaItemPath Parent + public ItemPath Parent { get { @@ -278,6 +284,30 @@ public bool IsTopLevelField } } + /// + /// Gets a value indicating whether the provided item path root represents an + /// path on one of the top level graphql operations. + /// + /// true if the root value represents + /// a path on ; otherwise, false. + public bool IsOperationRoot + { + get + { + this.EnsurePathInitialized(); + switch (this.Root) + { + case ItemPathRoots.Query: + case ItemPathRoots.Mutation: + case ItemPathRoots.Subscription: + return true; + + default: + return false; + } + } + } + /// /// Returns a that represents this instance. /// @@ -312,48 +342,14 @@ 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. /// - /// List<GraphRoutePath>. - public List GenerateParentPathSegments() + /// List<ItemPath>. + public List GenerateParentPathSegments() { if (this.IsTopLevelField || !this.IsValid) - return new List(); + return new List(); var list = this.Parent.GenerateParentPathSegments(); list.Add(this.Parent); @@ -365,9 +361,9 @@ public List GenerateParentPathSegments() /// Clones this instance. /// /// SchemaItemPath. - public SchemaItemPath Clone() + public ItemPath Clone() { - return new SchemaItemPath(this.Path); + return new ItemPath(this.Path); } /// @@ -377,12 +373,12 @@ public SchemaItemPath Clone() /// The path segments to append /// to the current path. /// SchemaItemPath. - public virtual SchemaItemPath CreateChild(params string[] pathSegments) + public virtual ItemPath CreateChild(params string[] pathSegments) { var list = new List(); list.Add(this.Path); list.AddRange(pathSegments); - return new SchemaItemPath(SchemaItemPath.Join(list.ToArray())); + return new ItemPath(ItemPath.Join(list.ToArray())); } /// @@ -393,12 +389,12 @@ public virtual SchemaItemPath CreateChild(params string[] pathSegments) /// The path segments to prepend /// to the current path. /// SchemaItemPath. - public SchemaItemPath ReParent(params string[] pathSegments) + public ItemPath ReParent(params string[] pathSegments) { var list = new List(); list.AddRange(pathSegments); list.Add(this.Path); - return new SchemaItemPath(SchemaItemPath.Join(list.ToArray())); + return new ItemPath(ItemPath.Join(list.ToArray())); } /// @@ -417,7 +413,7 @@ public override int GetHashCode() /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object obj) { - if (obj is SchemaItemPath grp) + if (obj is ItemPath grp) return grp.Path == this.Path; return false; diff --git a/src/graphql-aspnet/Schemas/Structural/SchemaItemPathComparer.cs b/src/graphql-aspnet/Schemas/Structural/ItemPathComparer.cs similarity index 73% rename from src/graphql-aspnet/Schemas/Structural/SchemaItemPathComparer.cs rename to src/graphql-aspnet/Schemas/Structural/ItemPathComparer.cs index 47edcaf72..946cc04da 100644 --- a/src/graphql-aspnet/Schemas/Structural/SchemaItemPathComparer.cs +++ b/src/graphql-aspnet/Schemas/Structural/ItemPathComparer.cs @@ -12,22 +12,22 @@ namespace GraphQL.AspNet.Schemas.Structural using System.Collections.Generic; /// - /// An equality comparer for objects. + /// An equality comparer for objects. /// - public class SchemaItemPathComparer : IEqualityComparer + public class ItemPathComparer : IEqualityComparer { /// /// Gets a singleton instance of the comparer declared with default parameters. /// /// The instance. - public static SchemaItemPathComparer Instance { get; } + public static ItemPathComparer Instance { get; } /// - /// Initializes static members of the class. + /// Initializes static members of the class. /// - static SchemaItemPathComparer() + static ItemPathComparer() { - SchemaItemPathComparer.Instance = new SchemaItemPathComparer(); + ItemPathComparer.Instance = new ItemPathComparer(); } /// @@ -36,7 +36,7 @@ static SchemaItemPathComparer() /// The first object of type T to compare. /// The second object of type T to compare. /// true if the specified objects are equal; otherwise, false. - public bool Equals(SchemaItemPath x, SchemaItemPath y) + public bool Equals(ItemPath x, ItemPath y) { return x == y; } @@ -46,7 +46,7 @@ public bool Equals(SchemaItemPath x, SchemaItemPath y) /// /// The for which a hash code is to be returned. /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - public int GetHashCode(SchemaItemPath obj) + public int GetHashCode(ItemPath obj) { return obj?.GetHashCode() ?? 0; } diff --git a/src/graphql-aspnet/Schemas/Structural/ItemPath_Statics.cs b/src/graphql-aspnet/Schemas/Structural/ItemPath_Statics.cs new file mode 100644 index 000000000..d1fdc0f20 --- /dev/null +++ b/src/graphql-aspnet/Schemas/Structural/ItemPath_Statics.cs @@ -0,0 +1,101 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Schemas.Structural +{ + using System; + using System.Linq; + using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Execution; + using PathConstants = GraphQL.AspNet.Constants.Routing; + + /// + /// A representation of a hierarchical path to a single field in within a graph schema. + /// + public partial class ItemPath + { + /// + /// Joins a parent and child path segments under the top level field type provided. + /// + /// The path segments to join. + /// System.String. + public static string Join(params string[] pathSegments) + { + var fragment = string.Join(PathConstants.PATH_SEPERATOR, pathSegments); + return ItemPath.NormalizeFragment(fragment); + } + + /// + /// Joins a parent and child path segments under the top level field type provided. + /// + /// Type of the field to prepend a root key to the path. + /// The path segments to join. + /// System.String. + public static string Join(ItemPathRoots fieldType, params string[] pathSegments) + { + return ItemPath.Join(fieldType.ToPathRootString().AsEnumerable().Concat(pathSegments).ToArray()); + } + + /// + /// Normalizes a given 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 pathfragment) + { + // ensure a working path + pathfragment = pathfragment?.Trim() ?? string.Empty; + pathfragment = pathfragment.Replace(PathConstants.ALT_PATH_SEPERATOR, PathConstants.PATH_SEPERATOR); + + // doubled up seperators may happen if a 3rd party is joining path fragments (especially if the seperator is a '/') + // trim them down + while (pathfragment.Contains(PathConstants.DOUBLE_PATH_SEPERATOR)) + { + pathfragment = pathfragment.Replace(PathConstants.DOUBLE_PATH_SEPERATOR, PathConstants.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 (pathfragment.EndsWith(PathConstants.PATH_SEPERATOR)) + { + pathfragment = pathfragment.Substring(0, pathfragment.Length - PathConstants.PATH_SEPERATOR.Length); + } + + while (pathfragment.StartsWith(PathConstants.PATH_SEPERATOR)) + { + pathfragment = pathfragment.Substring(pathfragment.IndexOf(PathConstants.PATH_SEPERATOR, StringComparison.Ordinal) + PathConstants.PATH_SEPERATOR.Length); + } + + return pathfragment; + } + + /// + /// Implements the == operator. + /// + /// The left side operand. + /// The right side operand. + /// The result of the operator. + public static bool operator ==(ItemPath left, ItemPath right) + { + return left?.Equals(right) ?? right?.Equals(left) ?? true; + } + + /// + /// Implements the != operator. + /// + /// The left side operand. + /// The right side operand. + /// The result of the operation. + public static bool operator !=(ItemPath left, ItemPath right) + { + return !(left == right); + } + } +} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/Structural/SchemaItemPath_Statics.cs b/src/graphql-aspnet/Schemas/Structural/SchemaItemPath_Statics.cs deleted file mode 100644 index 44dacf668..000000000 --- a/src/graphql-aspnet/Schemas/Structural/SchemaItemPath_Statics.cs +++ /dev/null @@ -1,66 +0,0 @@ -// ************************************************************* -// project: graphql-aspnet -// -- -// repo: https://github.com/graphql-aspnet -// docs: https://graphql-aspnet.github.io -// -- -// License: MIT -// ************************************************************* - -namespace GraphQL.AspNet.Schemas.Structural -{ - using System.Linq; - using GraphQL.AspNet.Common.Extensions; - using GraphQL.AspNet.Execution; - using RouteConstants = GraphQL.AspNet.Constants.Routing; - - /// - /// A representation of a hierarchical path to a single field in within a graph schema. - /// - public partial class SchemaItemPath - { - /// - /// Joins a parent and child route segments under the top level field type provided. - /// - /// The route segments to join. - /// System.String. - public static string Join(params string[] routeSegments) - { - var fragment = string.Join(RouteConstants.PATH_SEPERATOR, routeSegments); - return SchemaItemPath.NormalizeFragment(fragment); - } - - /// - /// Joins a parent and child route segments under the top level field type provided. - /// - /// Type of the field to prepend a root key to the path. - /// The route segments to join. - /// System.String. - public static string Join(SchemaItemCollections fieldType, params string[] routeSegments) - { - return SchemaItemPath.Join(fieldType.ToRouteRoot().AsEnumerable().Concat(routeSegments).ToArray()); - } - - /// - /// Implements the == operator. - /// - /// The left side operand. - /// The right side operand. - /// The result of the operator. - public static bool operator ==(SchemaItemPath left, SchemaItemPath right) - { - return left?.Equals(right) ?? right?.Equals(left) ?? true; - } - - /// - /// Implements the != operator. - /// - /// The left side operand. - /// The right side operand. - /// The result of the operation. - public static bool operator !=(SchemaItemPath left, SchemaItemPath right) - { - return !(left == right); - } - } -} \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/TypeSystem/DefaultValueCloneOptions.cs b/src/graphql-aspnet/Schemas/TypeSystem/DefaultValueCloneOptions.cs new file mode 100644 index 000000000..952e1dc17 --- /dev/null +++ b/src/graphql-aspnet/Schemas/TypeSystem/DefaultValueCloneOptions.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.Schemas.TypeSystem +{ + /// + /// A set of options to instruct how a field clone operation should handle a default value + /// applied to a field argument or input object field. + /// + public enum DefaultValueCloneOptions + { + /// + /// No change to the requirability of the item is made. + /// + None = 0, + + /// + /// The item is marked as required and any assigned default value is removed. + /// + MakeRequired = 1, + + /// + /// The item is marked as not required and the supplied default value is set as the + /// value for the field. + /// + UpdateDefaultValue = 2, + } +} diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Directive.cs b/src/graphql-aspnet/Schemas/TypeSystem/Directive.cs index 4278f19cb..c502798ad 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,9 +29,10 @@ 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. + /// The that identifies this directive within the schema. /// if set to true the directive is repeatable /// at a target location. /// The resolver used to process this instance. @@ -41,9 +40,10 @@ 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, + ItemPath itemPath, bool isRepeatable = false, IGraphDirectiveResolver resolver = null, IEnumerable securityGroups = null) @@ -53,9 +53,9 @@ public Directive( this.Locations = locations; this.Resolver = resolver; this.Publish = true; - this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); + this.ItemPath = Validation.ThrowIfNullOrReturn(itemPath, nameof(itemPath)); 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; @@ -68,6 +68,30 @@ public bool ValidateObject(object item) return item == null || item.GetType() == this.ObjectType; } + /// + public virtual IGraphType Clone(string typeName = null) + { + typeName = typeName?.Trim() ?? this.Name; + + var clonedItem = new Directive( + typeName, + this.InternalName, + this.Locations, + this.ObjectType, + this.ItemPath.Parent.CreateChild(typeName), + this.IsRepeatable, + this.Resolver, + this.SecurityGroups); + + clonedItem.Publish = this.Publish; + clonedItem.Description = this.Description; + + foreach (var argument in this.Arguments) + clonedItem.Arguments.AddArgument(argument.Clone(clonedItem)); + + return clonedItem; + } + /// public string Name { get; set; } @@ -96,7 +120,7 @@ public bool ValidateObject(object item) public virtual IAppliedDirectiveCollection AppliedDirectives { get; } /// - public SchemaItemPath Route { get; } + public ItemPath ItemPath { get; } /// public Type ObjectType { get; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/EnumGraphType.cs b/src/graphql-aspnet/Schemas/TypeSystem/EnumGraphType.cs index cc5074fb3..3b0072557 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 item 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, + ItemPath itemPath, + IAppliedDirectiveCollection directives = null) + : this(name, internalName, enumType, itemPath, 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 item 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, + ItemPath itemPath, 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.ItemPath = Validation.ThrowIfNullOrReturn(itemPath, nameof(itemPath)); this.SourceResolver = Validation.ThrowIfNullOrReturn(resolver, nameof(resolver)); - this.InternalName = this.ObjectType.FriendlyName(); this.Publish = true; this.AppliedDirectives = directives?.Clone(this) ?? new AppliedDirectiveCollection(this); @@ -92,6 +101,27 @@ public virtual bool ValidateObject(object item) return Enum.IsDefined(this.ObjectType, item); } + /// + public virtual IGraphType Clone(string typeName = null) + { + typeName = typeName?.Trim() ?? this.Name; + + var clonedItem = new EnumGraphType( + typeName, + this.InternalName, + this.ObjectType, + this.ItemPath.Parent.CreateChild(typeName), + this.AppliedDirectives); + + clonedItem.Description = this.Description; + clonedItem.Publish = this.Publish; + + foreach (var enumValue in this.Values) + clonedItem.AddOption(enumValue.Value.Clone(clonedItem)); + + return clonedItem; + } + /// public virtual IEnumValueCollection Values => _options; @@ -123,6 +153,6 @@ public virtual bool ValidateObject(object item) public IAppliedDirectiveCollection AppliedDirectives { get; } /// - public SchemaItemPath Route { get; } + public ItemPath ItemPath { get; } } } \ No newline at end of file diff --git a/src/graphql-aspnet/Schemas/TypeSystem/EnumValue.cs b/src/graphql-aspnet/Schemas/TypeSystem/EnumValue.cs index 68d02caf4..ed5aa3688 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/EnumValue.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/EnumValue.cs @@ -26,34 +26,51 @@ 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 item 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, + ItemPath itemPath, object internalValue, - string internalLabel, + string declaredLabel, IAppliedDirectiveCollection directives = null) { this.Parent = Validation.ThrowIfNullOrReturn(parent, nameof(parent)); this.Name = Validation.ThrowIfNullEmptyOrReturn(name, nameof(name)); - this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); + this.ItemPath = Validation.ThrowIfNullOrReturn(itemPath, nameof(itemPath)); 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)); + } + + /// + public virtual IEnumValue Clone(IEnumGraphType parent = null, string valueName = null) + { + parent = parent ?? this.Parent; + valueName = valueName?.Trim() ?? this.Name; - if (Constants.QueryLanguage.IsReservedKeyword(this.Name)) - { - throw new GraphTypeDeclarationException($"The enum value '{this.Name}' is invalid for " + - $"graph type '{this.Parent.Name}'. {this.Name} is a reserved keyword."); - } + var clonedItem = new EnumValue( + parent, + valueName, + this.InternalName, + this.Description, + parent.ItemPath.CreateChild(valueName), + this.DeclaredValue, + this.DeclaredLabel, + this.AppliedDirectives); + + return clonedItem; } /// @@ -72,15 +89,18 @@ public EnumValue( public IAppliedDirectiveCollection AppliedDirectives { get; } /// - public SchemaItemPath Route { get; } + public ItemPath ItemPath { get; } /// public IEnumGraphType Parent { get; } /// - public object InternalValue { get; } + public object DeclaredValue { get; } + + /// + public string DeclaredLabel { get; } /// - public string InternalLabel { 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..31426347b 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 fieldPath = graphType.ItemPath.CreateChild(fieldName); var field = new MethodGraphField( fieldName, + $"GraphQLExtendedField", typeExpression, - fieldRoute, - GraphValidation.EliminateNextWrapperFromCoreType(typeof(TReturn)), + fieldPath, 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..3abf6ce47 --- /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. + /// + /// The type name to look for. + /// true the name can be reformatted, otherwise false. + public static bool CanBeRenamed(string typeName) + { + // meh, its not a built in scalar, doesnt really matter + if (typeName == 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(typeName, 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 deleted file mode 100644 index de90d1639..000000000 --- a/src/graphql-aspnet/Schemas/TypeSystem/GraphArgumentModifiers.cs +++ /dev/null @@ -1,42 +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; - - /// - /// A set of modifiers and flags that can be assigned to individual arguments on graph fields to modify their behavior - /// during execution. - /// - [Flags] - public enum GraphArgumentModifiers - { - /// - /// No special modifications are needed. - /// - None = 0, - - /// - /// This parameter is internal to the server environment and will not be exposed on the object graph. - /// - Internal = 1, - - /// - /// This parameter is declared to contain the result of the resolved parent field. - /// - ParentFieldResult = 2, - - /// - /// 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. - /// - CancellationToken = 4, - } -} \ 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..9f1ed341d 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/GraphArgumentModifiersExtensions.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/GraphArgumentModifiersExtensions.cs @@ -6,67 +6,91 @@ // -- // License: MIT // ************************************************************* + namespace GraphQL.AspNet.Schemas.TypeSystem { - using System.Runtime.CompilerServices; + using GraphQL.AspNet.Execution; /// - /// Extension helper methods for . + /// 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 ParameterModifiers modifiers) { - return modifiers.HasFlag(GraphArgumentModifiers.ParentFieldResult); + return modifiers == ParameterModifiers.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 ParameterModifiers modifier) { - return modifiers.HasFlag(GraphArgumentModifiers.Internal); + return modifier == ParameterModifiers.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 ParameterModifiers modifier) { - return modifiers.HasFlag(GraphArgumentModifiers.CancellationToken); + return modifier == ParameterModifiers.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 ParameterModifiers modifier) { - return !modifiers.IsPartOfTheSchema(); + return modifier == ParameterModifiers.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 ParameterModifiers modifier) + { + return modifier == ParameterModifiers.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 ParameterModifiers modifier) + { + return modifier == ParameterModifiers.ExplicitInjected || + modifier == ParameterModifiers.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 ParameterModifiers modifier) { - return modifiers == GraphArgumentModifiers.None; + return modifier == ParameterModifiers.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..feeb141ce 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 type expression. - /// The route path that identifies this argument. - /// The modifiers. + /// The internal name identifiying this argument. /// Name of the parameter as it is declared in the source code. - /// The fully qualified internal name identifiying this argument. + /// The type expression. + /// The path that identifies this argument in the target schema. /// 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, - GraphTypeExpression typeExpression, - SchemaItemPath route, - GraphArgumentModifiers modifiers, - string parameterName, string internalName, + string parameterName, + GraphTypeExpression typeExpression, + ItemPath itemPath, Type objectType, bool hasDefaultValue, object defaultValue = null, @@ -58,12 +56,11 @@ public GraphFieldArgument( { this.Parent = Validation.ThrowIfNullOrReturn(parent, nameof(parent)); this.Name = Validation.ThrowIfNullWhiteSpaceOrReturn(argumentName, nameof(argumentName)); - this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); + this.ItemPath = Validation.ThrowIfNullOrReturn(itemPath, nameof(itemPath)); this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalName, nameof(internalName)); 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' @@ -76,33 +73,61 @@ public GraphFieldArgument( } /// - public IGraphArgument Clone(ISchemaItem parent) + public IGraphArgument Clone( + ISchemaItem parent = null, + string argumentName = null, + GraphTypeExpression typeExpression = null, + DefaultValueCloneOptions defaultValueOptions = DefaultValueCloneOptions.None, + object newDefaultValue = null) { - Validation.ThrowIfNull(parent, nameof(parent)); - return new GraphFieldArgument( + parent = parent ?? this.Parent; + + argumentName = argumentName?.Trim() ?? this.Name; + + var parentPath = parent?.ItemPath ?? this.ItemPath.Parent; + var itemPath = parentPath.CreateChild(argumentName); + + var hasDefaultValue = this.HasDefaultValue; + var defaultValue = this.DefaultValue; + + switch (defaultValueOptions) + { + case DefaultValueCloneOptions.None: + break; + + case DefaultValueCloneOptions.MakeRequired: + defaultValue = null; + hasDefaultValue = false; + break; + + case DefaultValueCloneOptions.UpdateDefaultValue: + defaultValue = newDefaultValue; + hasDefaultValue = true; + break; + } + + var clonedItem = new GraphFieldArgument( parent, - this.Name, - this.TypeExpression.Clone(), - parent.Route.CreateChild(this.Name), - this.ArgumentModifiers, - this.ParameterName, + argumentName, this.InternalName, + this.ParameterName, + typeExpression ?? this.TypeExpression.Clone(), + itemPath, this.ObjectType, - this.HasDefaultValue, - this.DefaultValue, + hasDefaultValue, + defaultValue, this.Description, this.AppliedDirectives); + + return clonedItem; } /// - public string Name { get; set; } + public string Name { get; } /// public string Description { get; set; } - /// - public GraphArgumentModifiers ArgumentModifiers { get; } - /// public GraphTypeExpression TypeExpression { get; } @@ -125,7 +150,7 @@ public IGraphArgument Clone(ISchemaItem parent) public bool HasDefaultValue { get; } /// - public SchemaItemPath Route { get; } + public ItemPath ItemPath { get; } /// public ISchemaItem Parent { get; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/GraphOperation.cs b/src/graphql-aspnet/Schemas/TypeSystem/GraphOperation.cs index 492a55fc5..24cadf874 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/GraphOperation.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/GraphOperation.cs @@ -35,7 +35,8 @@ public GraphOperation( IAppliedDirectiveCollection directives = null) : base( Constants.ReservedNames.FindOperationTypeNameByType(operationType), - new SchemaItemPath(SchemaItemCollections.Types, Constants.ReservedNames.FindOperationTypeNameByType(operationType)), + $"{nameof(GraphOperation)}.{Constants.ReservedNames.FindOperationTypeNameByType(operationType)}", + new ItemPath(ItemPathRoots.Types, Constants.ReservedNames.FindOperationTypeNameByType(operationType)), directives) { this.OperationType = operationType; @@ -52,21 +53,18 @@ public override bool ValidateObject(object item) } /// - public GraphOperationType OperationType { get; } - - /// - public IGraphField Extend(IGraphField newField) + public override IGraphType Clone(string typeName = null) { - return this.GraphFieldCollection.AddField(newField); + throw new NotSupportedException($"Graph Operation '{this.OperationType}' cannot be cloned"); } /// - public override bool IsVirtual => true; + public GraphOperationType OperationType { get; } /// - public Type ObjectType => typeof(GraphOperation); + public override bool IsVirtual => true; /// - public string InternalName => $"{typeof(GraphOperation).FriendlyName()}.{this.OperationType}"; + public Type ObjectType => typeof(GraphOperation); } } \ 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..18c7e9bfb 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/InputGraphField.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/InputGraphField.cs @@ -19,9 +19,11 @@ namespace GraphQL.AspNet.Schemas.TypeSystem /// A standard field on any INPUT_OBJECT field. /// /// - [DebuggerDisplay("Field: {Route.Path}")] + [DebuggerDisplay("Field: {ItemPath.Path}")] public class InputGraphField : IInputGraphField { + private readonly bool _declaredIsRequired; + // ******************************************* // implementation note: // @@ -36,38 +38,43 @@ 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 formal path to this field in the schema. /// 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, + ItemPath itemPath, 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)); + this.ItemPath = Validation.ThrowIfNullOrReturn(itemPath, nameof(itemPath)); this.ObjectType = Validation.ThrowIfNullOrReturn(objectType, nameof(objectType)); this.DeclaredReturnType = Validation.ThrowIfNullOrReturn(declaredReturnType, nameof(declaredReturnType)); this.AppliedDirectives = directives?.Clone(this) ?? new AppliedDirectiveCollection(this); - this.InternalName = declaredPropertyName; + _declaredIsRequired = isRequired; this.HasDefaultValue = !isRequired; this.IsRequired = isRequired && this.TypeExpression.IsNonNullable; this.DefaultValue = defaultValue; @@ -75,10 +82,53 @@ public InputGraphField( } /// - public void AssignParent(IGraphType parent) + public virtual IInputGraphField Clone( + ISchemaItem parent = null, + string fieldName = null, + GraphTypeExpression typeExpression = null, + DefaultValueCloneOptions defaultValueOptions = DefaultValueCloneOptions.None, + object newDefaultValue = null) { - Validation.ThrowIfNull(parent, nameof(parent)); - this.Parent = parent; + parent = parent ?? this.Parent; + var itemPath = parent?.ItemPath.CreateChild(this.ItemPath.Name) ?? this.ItemPath.Clone(); + + fieldName = fieldName?.Trim() ?? this.Name; + + var declareIsRequired = _declaredIsRequired; + var defaultValue = this.DefaultValue; + + switch (defaultValueOptions) + { + case DefaultValueCloneOptions.None: + break; + + case DefaultValueCloneOptions.MakeRequired: + declareIsRequired = true; + defaultValue = null; + break; + + case DefaultValueCloneOptions.UpdateDefaultValue: + defaultValue = newDefaultValue; + break; + } + + var clonedItem = new InputGraphField( + fieldName, + this.InternalName, + typeExpression ?? this.TypeExpression, + itemPath, + this.ObjectType, + this.DeclaredName, + this.DeclaredReturnType, + declareIsRequired, + defaultValue, + this.AppliedDirectives); + + clonedItem.Parent = parent; + clonedItem.Description = this.Description; + clonedItem.Publish = this.Publish; + + return clonedItem; } /// @@ -94,7 +144,7 @@ public void AssignParent(IGraphType parent) public ISchemaItem Parent { get; private set; } /// - public SchemaItemPath Route { get; } + public ItemPath ItemPath { get; } /// public IAppliedDirectiveCollection AppliedDirectives { get; } @@ -108,13 +158,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..0d79faa4c 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/InputObjectGraphType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/InputObjectGraphType.cs @@ -29,24 +29,48 @@ 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 item 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, + ItemPath itemPath, IAppliedDirectiveCollection directives = null) { this.Name = Validation.ThrowIfNullWhiteSpaceOrReturn(name, nameof(name)); this.ObjectType = Validation.ThrowIfNullOrReturn(objectType, nameof(objectType)); - this.InternalName = this.ObjectType.FriendlyName(); - this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); + this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalName, nameof(internalName)); + this.ItemPath = Validation.ThrowIfNullOrReturn(itemPath, nameof(itemPath)); this.AppliedDirectives = directives?.Clone(this) ?? new AppliedDirectiveCollection(this); this.Publish = true; - _graphFields = new InputGraphFieldCollection(this); + _graphFields = new InputGraphFieldCollection(); + } + + /// + public virtual IGraphType Clone(string typeName = null) + { + typeName = typeName?.Trim() ?? this.Name; + var itemPath = this.ItemPath.Clone().Parent.CreateChild(typeName); + + var clonedItem = new InputObjectGraphType( + typeName, + this.InternalName, + this.ObjectType, + itemPath, + this.AppliedDirectives); + + clonedItem.Description = this.Description; + clonedItem.Publish = this.Publish; + + foreach (var field in this.Fields) + this.AddField(field.Clone(clonedItem)); + + return clonedItem; } /// @@ -99,7 +123,7 @@ public void AddField(IInputGraphField field) public IAppliedDirectiveCollection AppliedDirectives { get; } /// - public SchemaItemPath Route { get; } + public ItemPath ItemPath { get; } /// public TypeKind Kind => TypeKind.INPUT_OBJECT; diff --git a/src/graphql-aspnet/Schemas/TypeSystem/InterfaceGraphType.cs b/src/graphql-aspnet/Schemas/TypeSystem/InterfaceGraphType.cs index 7fd4bb213..af9a0916f 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/InterfaceGraphType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/InterfaceGraphType.cs @@ -12,6 +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.Schema; @@ -30,23 +31,25 @@ 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 item 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, + ItemPath itemPath, IAppliedDirectiveCollection directives = null) { this.Name = Validation.ThrowIfNullOrReturn(name, nameof(name)); - this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); + this.ItemPath = Validation.ThrowIfNullOrReturn(itemPath, nameof(itemPath)); 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); + _fieldSet = new GraphFieldCollection(); this.Publish = true; @@ -55,9 +58,38 @@ public InterfaceGraphType( this.AppliedDirectives = directives?.Clone(this) ?? new AppliedDirectiveCollection(this); } + /// + public virtual IGraphType Clone(string typeName = null) + { + typeName = typeName?.Trim() ?? this.Name; + var itemPath = this.ItemPath.Clone().Parent.CreateChild(typeName); + + var clonedItem = new InterfaceGraphType( + typeName, + this.InternalName, + this.ObjectType, + itemPath, + this.AppliedDirectives); + + clonedItem.Description = this.Description; + clonedItem.Publish = this.Publish; + + foreach (var item in this.InterfaceNames) + clonedItem.InterfaceNames.Add(item); + + foreach (var field in this.Fields.Where(x => !(x is Introspection_TypeNameMetaField))) + clonedItem.Extend(field); + + return clonedItem; + } + /// public IGraphField Extend(IGraphField newField) { + Validation.ThrowIfNull(newField, nameof(newField)); + if (newField.Parent != this) + newField = newField.Clone(parent: this); + return _fieldSet.AddField(newField); } @@ -96,7 +128,7 @@ public bool ValidateObject(object item) public IAppliedDirectiveCollection AppliedDirectives { get; } /// - public SchemaItemPath Route { get; } + public ItemPath ItemPath { get; } /// public Type ObjectType { get; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/BaseIntrospectionObjectType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/BaseIntrospectionObjectType.cs index 6aad89827..fd1e9b17f 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/BaseIntrospectionObjectType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/BaseIntrospectionObjectType.cs @@ -10,8 +10,10 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.Introspection { using System; - using GraphQL.AspNet.Common.Extensions; + using System.Threading.Tasks; + using GraphQL.AspNet.Execution; using GraphQL.AspNet.Execution.Exceptions; + using GraphQL.AspNet.Execution.Resolvers; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; @@ -23,14 +25,52 @@ 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 IntrospectedFieldPath(name)) { } + /// + /// Creates and adds a new to the growing collection. + /// + /// The expected type of the source data supplied to the resolver. + /// The expected type of data to be returned from this field. + /// The formatted name of the field as it will appear in the object graph. + /// The internal name of the field as its represented in source code. + /// The item representing how this field returns a graph type. + /// The formal path that uniquely identifies this field in the object graph. + /// The resolver used to fulfil requests to this field. + /// The description to assign to the field. + /// IGraphTypeField. + protected IGraphField AddField( + string fieldName, + string internalName, + GraphTypeExpression typeExpression, + ItemPath itemPath, + Func> resolver, + string description = null) + where TSource : class + { + IGraphField field = new MethodGraphField( + fieldName, + internalName, + typeExpression, + itemPath, + GraphValidation.EliminateNextWrapperFromCoreType(typeof(TReturn)), + typeof(TReturn), + FieldResolutionMode.PerSourceItem, + new FunctionGraphFieldResolver(resolver)); + + field.Description = description; + field = field.Clone(parent: this); + + return this.GraphFieldCollection.AddField(field); + } + /// public override bool ValidateObject(object item) { @@ -38,15 +78,18 @@ public override bool ValidateObject(object item) } /// - public IGraphField Extend(IGraphField newField) + public override IGraphField Extend(IGraphField newField) { throw new GraphTypeDeclarationException($"Introspection type '{this.Name}' cannot be extended"); } /// - public virtual Type ObjectType => this.GetType(); + public override IGraphType Clone(string typeName = null) + { + throw new InvalidOperationException($"Introspection type '{this.Name}' cannot be cloned"); + } /// - public virtual string InternalName => this.ObjectType.FriendlyName(); + public virtual Type ObjectType => this.GetType(); } } \ 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..81570e2a7 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; @@ -28,7 +28,7 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.Introspection.Fields [DebuggerDisplay("Field: {Name}")] internal class Introspection_SchemaField : MethodGraphField { - private static readonly SchemaItemPath FIELD_PATH = new SchemaItemPath(SchemaItemCollections.Query, Constants.ReservedNames.SCHEMA_FIELD); + private static readonly ItemPath FIELD_PATH = new ItemPath(ItemPathRoots.Query, Constants.ReservedNames.SCHEMA_FIELD); /// /// Initializes a new instance of the class. @@ -37,17 +37,30 @@ 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)); } /// - public override IGraphField Clone(IGraphType parent) + public override IGraphField Clone(ISchemaItem parent = null, string fieldName = null, GraphTypeExpression typeExpression = null) { - throw new NotImplementedException("Introspection related fields cannot be cloned."); + if (fieldName != null) + throw new NotSupportedException($"{nameof(Introspection_SchemaField)} does not support field name changes."); + if (typeExpression != null) + throw new NotSupportedException($"{nameof(Introspection_SchemaField)} does not support type expression changes."); + + var item = new Introspection_SchemaField(this.IntrospectedSchema); + + item.Parent = parent ?? this.Parent; + item.Description = this.Description; + + return item; } /// 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..570d45407 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Fields/Introspection_TypeGraphField.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Fields/Introspection_TypeGraphField.cs @@ -11,9 +11,10 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.Introspection.Fields { using System; using System.Diagnostics; + using GraphQL.AspNet.Common; 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; @@ -26,7 +27,8 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.Introspection.Fields [DebuggerDisplay("Meta Field: " + Constants.ReservedNames.TYPE_FIELD)] internal class Introspection_TypeGraphField : MethodGraphField { - private static readonly SchemaItemPath FIELD_PATH = new SchemaItemPath(SchemaItemCollections.Query, Constants.ReservedNames.TYPE_FIELD); + private static readonly ItemPath FIELD_PATH = new ItemPath(ItemPathRoots.Query, Constants.ReservedNames.TYPE_FIELD); + private readonly IntrospectedSchema _schema; /// /// Initializes a new instance of the class. @@ -35,11 +37,15 @@ 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)) { + _schema = Validation.ThrowIfNullOrReturn(schema, nameof(schema)); this.Arguments.AddArgument( "name", "name", @@ -48,9 +54,18 @@ public Introspection_TypeGraphField(IntrospectedSchema schema) } /// - public override IGraphField Clone(IGraphType parent) + public override IGraphField Clone(ISchemaItem parent = null, string fieldName = null, GraphTypeExpression typeExpression = null) { - throw new NotImplementedException("Introspection related fields cannot be cloned."); + if (fieldName != null) + throw new NotSupportedException($"{nameof(Introspection_TypeGraphField)} does not support field name changes."); + if (typeExpression != null) + throw new NotSupportedException($"{nameof(Introspection_TypeGraphField)} does not support type expression changes."); + + var item = new Introspection_TypeGraphField(_schema); + item.Parent = parent ?? this.Parent; + item.Description = this.Description; + + return item; } /// 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..524137f67 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; @@ -28,7 +28,8 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.Introspection.Fields [DebuggerDisplay("Meta Field: " + Constants.ReservedNames.TYPENAME_FIELD)] public class Introspection_TypeNameMetaField : MethodGraphField { - private static readonly SchemaItemPath FIELD_PATH = new SchemaItemPath(SchemaItemCollections.Query, Constants.ReservedNames.TYPENAME_FIELD); + private static readonly ItemPath FIELD_PATH = new ItemPath(ItemPathRoots.Query, Constants.ReservedNames.TYPENAME_FIELD); + private readonly string _graphTypeName; /// /// Initializes a new instance of the class. @@ -37,17 +38,30 @@ 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)); + _graphTypeName = Validation.ThrowIfNullWhiteSpaceOrReturn(graphTypeName, nameof(graphTypeName)); this.UpdateResolver(new FunctionGraphFieldResolver((obj) => graphTypeName.AsCompletedTask()), FieldResolutionMode.PerSourceItem); } /// - public override IGraphField Clone(IGraphType parent) + public override IGraphField Clone(ISchemaItem parent = null, string fieldName = null, GraphTypeExpression typeExpression = null) { - throw new NotImplementedException("Introspection related fields cannot be cloned."); + if (fieldName != null) + throw new NotSupportedException($"{nameof(Introspection_TypeNameMetaField)} does not support field name changes."); + if (typeExpression != null) + throw new NotSupportedException($"{nameof(Introspection_TypeNameMetaField)} does not support type expression changes."); + + var item = new Introspection_TypeNameMetaField(_graphTypeName); + item.Parent = parent ?? this.Parent; + item.Description = this.Description; + + return item; } /// diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_DirectiveLocationType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_DirectiveLocationType.cs index e52702577..ccc9cacc7 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_DirectiveLocationType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_DirectiveLocationType.cs @@ -30,8 +30,9 @@ 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)) + new IntrospectedFieldPath(Constants.ReservedNames.DIRECTIVE_LOCATION_ENUM)) { foreach (var value in Enum.GetValues(this.ObjectType)) { @@ -44,10 +45,11 @@ public Introspection_DirectiveLocationType() var option = new EnumValue( this, name, + name, description, - this.Route.CreateChild(name), + this.ItemPath.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..d8cc8228c 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_DirectiveType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_DirectiveType.cs @@ -27,51 +27,56 @@ 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 // ------------------------------------------------------------------------- - this.GraphFieldCollection.AddField( + this.AddField( "name", + $"{this.InternalName}.{nameof(Directive.Name)}", new GraphTypeExpression( Constants.ScalarNames.STRING, GraphTypeExpression.RequiredSingleItem), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "name"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "name"), (directive) => directive.Name.AsCompletedTask(), "The case-sensitive name of this directive as it should appear in a query."); - this.GraphFieldCollection.AddField( + this.AddField( "description", + $"{this.InternalName}.{nameof(Directive.Description)}", new GraphTypeExpression(Constants.ScalarNames.STRING), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "description"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "description"), (directive) => directive.Description.AsCompletedTask(), "A human-friendly description of the directive and how it functions."); - this.GraphFieldCollection.AddField>( + this.AddField>( "locations", + $"{this.InternalName}.{nameof(Directive.Locations)}", new GraphTypeExpression( Constants.ReservedNames.DIRECTIVE_LOCATION_ENUM, GraphTypeExpression.RequiredListRequiredItem), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "locations"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "locations"), (directive) => directive.Locations.AsCompletedTask(), "A collection of locations where this directive can be used."); - this.GraphFieldCollection.AddField>( + this.AddField>( "args", + $"{this.InternalName}.{nameof(Directive.Arguments)}", new GraphTypeExpression( Constants.ReservedNames.INPUT_VALUE_TYPE, GraphTypeExpression.RequiredListRequiredItem), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "args"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "args"), (directive) => directive.Arguments.AsCompletedTask(), "A collection of input values provided to the directive in order to properly invoke it."); - this.GraphFieldCollection.AddField( + this.AddField( "isRepeatable", + $"{this.InternalName}.{nameof(Directive.IsRepeatable)}", new GraphTypeExpression( Constants.ScalarNames.BOOLEAN, GraphTypeExpression.RequiredSingleItem), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "isRepeatable"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "isRepeatable"), (directive) => directive.IsRepeatable.AsCompletedTask(), "A value indicating if the directive is repeatable on its target entity."); } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_EnumValueType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_EnumValueType.cs index 82d36c441..141859a74 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_EnumValueType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_EnumValueType.cs @@ -26,37 +26,41 @@ 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 // ------------------------------------------------------------------------- - this.GraphFieldCollection.AddField( + this.AddField( "name", + $"{this.InternalName}.{nameof(EnumValue.Name)}", new GraphTypeExpression(Constants.ScalarNames.STRING, MetaGraphTypes.IsNotNull), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "name"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "name"), (ev) => ev.Name.AsCompletedTask(), "The case-sensitive name of this value as it should be used in a query."); - this.GraphFieldCollection.AddField( + this.AddField( "description", + $"{this.InternalName}.{nameof(EnumValue.Description)}", new GraphTypeExpression(Constants.ScalarNames.STRING), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "description"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "description"), (ev) => ev.Description.AsCompletedTask(), "A human-friendly description of the value and what it means."); - this.GraphFieldCollection.AddField( + this.AddField( "isDeprecated", + $"{this.InternalName}.{nameof(EnumValue.IsDeprecated)}", new GraphTypeExpression(Constants.ScalarNames.BOOLEAN, MetaGraphTypes.IsNotNull), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "isDeprecatedame"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "isDeprecatedame"), (ev) => ev.IsDeprecated.AsCompletedTask(), "Indiates if this value is deprecated. Any deprecated value should not be used and " + "may be removed at a future date."); - this.GraphFieldCollection.AddField( + this.AddField( "deprecationReason", + $"{this.InternalName}.{nameof(EnumValue.DeprecationReason)}", new GraphTypeExpression(Constants.ScalarNames.STRING), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "deprecationReason"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "deprecationReason"), (ev) => ev.DeprecationReason.AsCompletedTask(), "A human-friendly reason as to why this value has been deprecated."); } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_FieldType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_FieldType.cs index c9bfc15b5..38b22aaa8 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_FieldType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_FieldType.cs @@ -27,52 +27,58 @@ 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 // ------------------------------------------------------------------------- - this.GraphFieldCollection.AddField( + this.AddField( "name", + $"{this.InternalName}.{nameof(IntrospectedField.Name)}", new GraphTypeExpression(Constants.ScalarNames.STRING, MetaGraphTypes.IsNotNull), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "name"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "name"), (field) => field.Name.AsCompletedTask(), "The case-sensitive name of this field as it should be used in a query."); - this.GraphFieldCollection.AddField( + this.AddField( "description", + $"{this.InternalName}.{nameof(IntrospectedField.Description)}", new GraphTypeExpression(Constants.ScalarNames.STRING), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "description"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "description"), (field) => field.Description.AsCompletedTask(), "Indiates if this field is deprecated. Any deprecated field should not be used and " + "may be removed at a future date."); - this.GraphFieldCollection.AddField>( + this.AddField>( "args", + $"{this.InternalName}.{nameof(IntrospectedField.Arguments)}", new GraphTypeExpression(Constants.ReservedNames.INPUT_VALUE_TYPE, GraphTypeExpression.RequiredListRequiredItem), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "args"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "args"), (field) => field.Arguments.AsCompletedTask(), "A collection of input values that can be passed to this field to alter its behavior when used in a query."); - this.GraphFieldCollection.AddField( + this.AddField( "type", + $"{this.InternalName}.{nameof(IntrospectedField.IntrospectedGraphType)}", new GraphTypeExpression(Constants.ReservedNames.TYPE_TYPE, MetaGraphTypes.IsNotNull), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "type"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "type"), (field) => field.IntrospectedGraphType.AsCompletedTask(), "The graph type returned by this field."); - this.GraphFieldCollection.AddField( + this.AddField( "isDeprecated", + $"{this.InternalName}.{nameof(IntrospectedField.IsDeprecated)}", new GraphTypeExpression(Constants.ScalarNames.BOOLEAN, MetaGraphTypes.IsNotNull), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "isDeprecated"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "isDeprecated"), (field) => field.IsDeprecated.AsCompletedTask(), "Indiates if this field is deprecated. Any deprecated field should not be used and " + "may be removed at a future date."); - this.GraphFieldCollection.AddField( + this.AddField( "deprecationReason", + $"{this.InternalName}.{nameof(IntrospectedField.DeprecationReason)}", new GraphTypeExpression(Constants.ScalarNames.STRING), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "deprecationReason"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "deprecationReason"), (field) => field.DeprecationReason.AsCompletedTask(), "A human-friendly reason as to why this field has been deprecated."); } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_InputValueType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_InputValueType.cs index e21cbb08d..54f1cb034 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_InputValueType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_InputValueType.cs @@ -26,38 +26,42 @@ 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 // ------------------------------------------------------------------------- this.Description = "A single argument supplied to a field, a directive or a complex input object."; - this.GraphFieldCollection.AddField( + this.AddField( "name", + $"{this.InternalName}.{nameof(IntrospectedInputValueType.Name)}", new GraphTypeExpression(Constants.ScalarNames.STRING, MetaGraphTypes.IsNotNull), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "name"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "name"), (inputField) => inputField.Name.AsCompletedTask(), "The case-sensitive name of this argument as it should be declared in a queryt."); - this.GraphFieldCollection.AddField( + this.AddField( "description", + $"{this.InternalName}.{nameof(IntrospectedInputValueType.Description)}", new GraphTypeExpression(Constants.ScalarNames.STRING), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "description"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "description"), (inputField) => inputField.Description.AsCompletedTask(), "A human-friendly description of this argument and what it means to the item it is declared on."); - this.GraphFieldCollection.AddField( + this.AddField( "type", + $"{this.InternalName}.{nameof(IntrospectedInputValueType.IntrospectedGraphType)}", new GraphTypeExpression(Constants.ReservedNames.TYPE_TYPE, MetaGraphTypes.IsNotNull), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "type"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "type"), (inputField) => inputField.IntrospectedGraphType.AsCompletedTask(), "The graph type of this argument."); - this.GraphFieldCollection.AddField( + this.AddField( "defaultValue", + $"{this.InternalName}.{nameof(IntrospectedInputValueType.DefaultValue)}", new GraphTypeExpression(Constants.ScalarNames.STRING), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "defaultValue"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "defaultValue"), (inputField) => inputField.DefaultValue.AsCompletedTask(), "(optional) A default value that will be used if this input field is not provided in a query."); } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_SchemaType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_SchemaType.cs index cfd1427f1..7b0ac94df 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_SchemaType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_SchemaType.cs @@ -28,50 +28,56 @@ 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 // ------------------------------------------------------------------------- - this.GraphFieldCollection.AddField( + this.AddField( "description", + $"{this.InternalName}.{nameof(IntrospectedSchema.Description)}", new GraphTypeExpression(Constants.ScalarNames.STRING, GraphTypeExpression.SingleItem), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "description"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "description"), (its) => its.Description.AsCompletedTask(), "A human-readable string describing this schema."); - this.GraphFieldCollection.AddField>( + this.AddField>( "types", + $"{this.InternalName}.{nameof(IntrospectedSchema.KnownTypes)}", new GraphTypeExpression(Constants.ReservedNames.TYPE_TYPE, GraphTypeExpression.RequiredListRequiredItem), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "types"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "types"), (its) => (its?.KnownTypes).Where(x => x.Publish).AsCompletedTask(), "A complete collection of graph types declared by this schema."); - this.GraphFieldCollection.AddField( + this.AddField( "queryType", + $"{this.InternalName}.{nameof(IntrospectedSchema.QueryType)}", new GraphTypeExpression(Constants.ReservedNames.TYPE_TYPE, MetaGraphTypes.IsNotNull), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "queryType"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "queryType"), (its) => its.QueryType.AsCompletedTask(), "The root query type of this schema."); - this.GraphFieldCollection.AddField( + this.AddField( "mutationType", + $"{this.InternalName}.{nameof(IntrospectedSchema.MutationType)}", new GraphTypeExpression(Constants.ReservedNames.TYPE_TYPE), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "mutationType"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "mutationType"), (its) => its.MutationType.AsCompletedTask(), "The root mutation type of this schema."); - this.GraphFieldCollection.AddField( + this.AddField( "subscriptionType", + $"{this.InternalName}.{nameof(IntrospectedSchema.SubscriptionType)}", new GraphTypeExpression(Constants.ReservedNames.TYPE_TYPE), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "subscriptionType"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "subscriptionType"), (its) => its.SubscriptionType.AsCompletedTask(), "The root subscription type of this schema. Will be null if this server does not support subscriptions."); - this.GraphFieldCollection.AddField>( + this.AddField>( "directives", + $"{this.InternalName}.{nameof(IntrospectedSchema.DeclaredDirectives)}", new GraphTypeExpression(Constants.ReservedNames.DIRECTIVE_TYPE, GraphTypeExpression.RequiredListRequiredItem), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "directives"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "directives"), (its) => its.DeclaredDirectives.Where(x => x.Publish).AsCompletedTask(), "A complete collection of the directives supported by this schema."); } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_TypeKindType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_TypeKindType.cs index 20ed5187d..d942a80a9 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_TypeKindType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_TypeKindType.cs @@ -30,8 +30,9 @@ 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)) + new IntrospectedFieldPath(Constants.ReservedNames.TYPE_KIND_ENUM)) { foreach (var value in Enum.GetValues(this.ObjectType)) { @@ -44,10 +45,11 @@ public Introspection_TypeKindType() var option = new EnumValue( this, name, + name, description, - this.Route.CreateChild(name), + this.ItemPath.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..2ed2de87d 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_TypeType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Introspection_TypeType.cs @@ -13,7 +13,8 @@ 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.Interfaces.Schema; using GraphQL.AspNet.Schemas.Structural; using GraphQL.AspNet.Schemas.TypeSystem; using GraphQL.AspNet.Schemas.TypeSystem.Introspection.Model; @@ -28,37 +29,43 @@ 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 // ------------------------------------------------------------------------- - this.GraphFieldCollection.AddField( + this.AddField( "kind", + $"{this.InternalName}.{nameof(IntrospectedType.Kind)}", new GraphTypeExpression(Constants.ReservedNames.TYPE_KIND_ENUM, MetaGraphTypes.IsNotNull), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "kind"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "kind"), (gt) => Task.FromResult(gt?.Kind ?? TypeKind.NONE), $"The specific {Constants.ReservedNames.TYPE_KIND_ENUM} of this graph type."); - this.GraphFieldCollection.AddField( + this.AddField( "name", + $"{this.InternalName}.{nameof(IntrospectedType.Name)}", new GraphTypeExpression(Constants.ScalarNames.STRING), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "name"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "name"), (gt) => Task.FromResult(gt?.Name), "The case-sensitive name of this graph type as it appears in the object graph"); - this.GraphFieldCollection.AddField( + this.AddField( "description", + $"{this.InternalName}.{nameof(IntrospectedType.Description)}", new GraphTypeExpression(Constants.ScalarNames.STRING), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "description"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "description"), (gt) => Task.FromResult(gt?.Description), "a human-readable phrase describing what this type represents."); // fields - var fieldsField = new MethodGraphField( + IGraphField fieldsField = new MethodGraphField( "fields", + $"{this.InternalName}.{nameof(IntrospectedType.Fields)}", new GraphTypeExpression(Constants.ReservedNames.FIELD_TYPE, MetaGraphTypes.IsList, MetaGraphTypes.IsNotNull), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "fields"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "fields"), + declaredReturnType: typeof(IEnumerable), + objectType: typeof(IEnumerable), mode: FieldResolutionMode.PerSourceItem, resolver: new Type_TypeGraphFieldResolver()) { @@ -71,30 +78,37 @@ public Introspection_TypeType() new GraphTypeExpression(Constants.ScalarNames.BOOLEAN), typeof(bool), false); + + fieldsField = fieldsField.Clone(this); this.GraphFieldCollection.AddField(fieldsField); // interfaces - this.GraphFieldCollection.AddField>( + this.AddField>( "interfaces", + $"{this.InternalName}.{nameof(IntrospectedType.Interfaces)}", new GraphTypeExpression(Constants.ReservedNames.TYPE_TYPE, MetaGraphTypes.IsList, MetaGraphTypes.IsNotNull), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "interfaces"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "interfaces"), (gt) => Task.FromResult(gt?.Interfaces), $"For {TypeKind.OBJECT.ToString()} types, contains a list of interface this type implements; otherwise null."); // possibleTypes - this.GraphFieldCollection.AddField>( + this.AddField>( "possibleTypes", + $"{this.InternalName}.{nameof(IntrospectedType.PossibleTypes)}", new GraphTypeExpression(Constants.ReservedNames.TYPE_TYPE, MetaGraphTypes.IsList, MetaGraphTypes.IsNotNull), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "possibleTypes"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "possibleTypes"), (gt) => Task.FromResult(gt?.PossibleTypes), $"For {TypeKind.INTERFACE.ToString()} and {TypeKind.UNION.ToString()} types, declares the possible types that implement the interface or are included " + "in the union; otherwise, null."); // enumValues - var enumValuesField = new MethodGraphField( + IGraphField enumValuesField = new MethodGraphField( "enumValues", + $"{this.InternalName}.{nameof(IntrospectedType.EnumValues)}", new GraphTypeExpression(Constants.ReservedNames.ENUM_VALUE_TYPE, MetaGraphTypes.IsList, MetaGraphTypes.IsNotNull), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, Constants.ReservedNames.ENUM_VALUE_TYPE), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, Constants.ReservedNames.ENUM_VALUE_TYPE), + declaredReturnType: typeof(IEnumerable), + objectType: typeof(IEnumerable), mode: FieldResolutionMode.PerSourceItem, resolver: new Type_EnumValuesGraphFieldResolver()) { @@ -107,30 +121,35 @@ public Introspection_TypeType() new GraphTypeExpression(Constants.ScalarNames.BOOLEAN), typeof(bool), false); + + enumValuesField = enumValuesField.Clone(this); this.GraphFieldCollection.AddField(enumValuesField); // inputFields - this.GraphFieldCollection.AddField>( + this.AddField>( "inputFields", + $"{this.InternalName}.{nameof(IntrospectedType.InputFields)}", new GraphTypeExpression(Constants.ReservedNames.INPUT_VALUE_TYPE, MetaGraphTypes.IsList, MetaGraphTypes.IsNotNull), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "inputFields"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "inputFields"), (gt) => Task.FromResult(gt?.InputFields), $"For {TypeKind.INPUT_OBJECT.ToString()} types, declares the fields that need to be supplied when submitting an object on a query; otherwise null."); // ofType - this.GraphFieldCollection.AddField( + this.AddField( "ofType", + $"{this.InternalName}.{nameof(IntrospectedType.OfType)}", new GraphTypeExpression(Constants.ReservedNames.TYPE_TYPE), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "ofType"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "ofType"), (gt) => Task.FromResult(gt?.OfType), $"For {TypeKind.NON_NULL.ToString()} and {TypeKind.LIST.ToString()} meta types, declares the underlying type that is " + "wrapped by this type; otherwise null."); // specifiedByURL - this.GraphFieldCollection.AddField( + this.AddField( "specifiedByURL", + $"{this.InternalName}.{nameof(IntrospectedType.SpecifiedByUrl)}", new GraphTypeExpression(Constants.ScalarNames.STRING), - new IntrospectedRoutePath(SchemaItemCollections.Types, this.Name, "specifiedByURL"), + new IntrospectedItemPath(ItemPathRoots.Types, this.Name, "specifiedByURL"), (gt) => Task.FromResult(gt?.SpecifiedByUrl), "A string, in the form of a URL, pointing to a specification " + "if the graph type is a scalar, otherwise null."); 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..345c1b100 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; /// @@ -27,7 +26,7 @@ public sealed class IntrospectedInputValueType : IntrospectedItem, ISchemaItem { private readonly object _rawDefaultValue; private readonly GraphTypeExpression _inputValueTypeExpression; - private readonly SchemaItemPath _inputValuePath; + private readonly ItemPath _inputValuePath; /// /// Initializes a new instance of the class. @@ -42,7 +41,7 @@ public IntrospectedInputValueType(IGraphArgument argument, IntrospectedType intr Validation.ThrowIfNull(argument, nameof(argument)); _rawDefaultValue = argument.HasDefaultValue ? argument.DefaultValue : IntrospectionNoDefaultValue.Instance; _inputValueTypeExpression = argument.TypeExpression; - _inputValuePath = argument.Route; + _inputValuePath = argument.ItemPath; } /// @@ -57,7 +56,7 @@ public IntrospectedInputValueType(IInputGraphField inputField, IntrospectedType this.IntrospectedGraphType = Validation.ThrowIfNullOrReturn(introspectedGraphType, nameof(introspectedGraphType)); _rawDefaultValue = IntrospectionNoDefaultValue.Instance; _inputValueTypeExpression = inputField.TypeExpression; - _inputValuePath = inputField.Route; + _inputValuePath = inputField.ItemPath; } /// @@ -74,7 +73,7 @@ public IntrospectedInputValueType(IInputGraphField inputField, IntrospectedType this.IntrospectedGraphType = Validation.ThrowIfNullOrReturn(introspectedGraphType, nameof(introspectedGraphType)); _rawDefaultValue = rawDefaultValue; _inputValueTypeExpression = inputField.TypeExpression; - _inputValuePath = inputField.Route; + _inputValuePath = inputField.ItemPath; } /// diff --git a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedItem.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedItem.cs index e7134a16d..c33bac151 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedItem.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedItem.cs @@ -30,8 +30,9 @@ 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.ItemPath = _item.ItemPath.ReParent(Constants.Routing.INTROSPECTION_ROOT); this.AppliedDirectives = new AppliedDirectiveCollection(this); } @@ -46,7 +47,7 @@ public virtual void Initialize(IntrospectedSchema introspectedSchema) /// [GraphSkip] - public virtual SchemaItemPath Route { get; } + public virtual ItemPath ItemPath { get; } /// [GraphSkip] @@ -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/Introspection/Model/IntrospectedType.cs b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedType.cs index 8ce45b326..2d94d612f 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/Introspection/Model/IntrospectedType.cs @@ -14,7 +14,6 @@ namespace GraphQL.AspNet.Schemas.TypeSystem.Introspection.Model using System.Diagnostics; using System.Linq; using GraphQL.AspNet.Common; - using GraphQL.AspNet.Common.Generics; using GraphQL.AspNet.Execution.Exceptions; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas.TypeSystem; diff --git a/src/graphql-aspnet/Schemas/TypeSystem/MethodGraphField.cs b/src/graphql-aspnet/Schemas/TypeSystem/MethodGraphField.cs index f46b05e7c..9ce775418 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/MethodGraphField.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/MethodGraphField.cs @@ -12,43 +12,41 @@ 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; /// /// A representation of a field as it would be defined in an object graph that originated - /// from a .NET method invocation. + /// from a .NET method or property invocation. /// - [DebuggerDisplay("Field: {Route.Path}")] + [DebuggerDisplay("Field: {ItemPath.Path}")] public class MethodGraphField : IGraphField { - private IGraphType _parent = null; - /// /// 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 formal path to this field in the object graph. /// 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, + ItemPath itemPath, + Type declaredReturnType, + Type objectType, FieldResolutionMode mode = FieldResolutionMode.PerSourceItem, IGraphFieldResolver resolver = null, IEnumerable securityPolicies = null, @@ -56,10 +54,11 @@ public MethodGraphField( { this.Name = Validation.ThrowIfNullWhiteSpaceOrReturn(fieldName, nameof(fieldName)); this.TypeExpression = Validation.ThrowIfNullOrReturn(typeExpression, nameof(typeExpression)); - this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); + this.ItemPath = Validation.ThrowIfNullOrReturn(itemPath, nameof(itemPath)); 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,40 +74,42 @@ 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); } /// - public void AssignParent(IGraphType parent) + public virtual IGraphField Clone( + ISchemaItem parent = null, + string fieldName = null, + GraphTypeExpression typeExpression = null) { - Validation.ThrowIfNull(parent, nameof(parent)); - _parent = parent; - } + parent = parent ?? this.Parent; + fieldName = fieldName?.Trim() ?? this.Name; - /// - public virtual IGraphField Clone(IGraphType parent) - { - Validation.ThrowIfNull(parent, nameof(parent)); + var clonedItem = this.CreateNewInstance(); - var newField = this.CreateNewInstance(parent); + // paths defined on operations cannot be repathed + // as they represent declared locations on the schema + var path = this.ItemPath.Clone(); + if (!this.ItemPath.IsOperationRoot) + path = parent?.ItemPath.CreateChild(this.ItemPath.Name) ?? path; // assign all publically alterable fields - newField.Description = this.Description; - newField.Publish = this.Publish; - newField.Complexity = this.Complexity; - newField.IsDeprecated = this.IsDeprecated; - newField.DeprecationReason = this.DeprecationReason; - newField.FieldSource = this.FieldSource; - - newField.AssignParent(parent); + clonedItem.Name = fieldName; + clonedItem.ItemPath = path; + clonedItem.TypeExpression = typeExpression ?? this.TypeExpression.Clone(); + clonedItem.Description = this.Description; + clonedItem.Publish = this.Publish; + clonedItem.Complexity = this.Complexity; + clonedItem.IsDeprecated = this.IsDeprecated; + clonedItem.DeprecationReason = this.DeprecationReason; + clonedItem.FieldSource = this.FieldSource; + clonedItem.Parent = parent; // clone over the arguments foreach (var argument in this.Arguments) - newField.Arguments.AddArgument(argument.Clone(newField)); + clonedItem.Arguments.AddArgument(argument.Clone(clonedItem)); - return newField; + return clonedItem; } /// @@ -142,16 +143,16 @@ public virtual bool CanResolveForGraphType(IGraphType graphType) /// /// This method is used as the basis for new object creation during cloning. /// - /// The item to assign as the parent of the new field. /// IGraphField. - protected virtual MethodGraphField CreateNewInstance(IGraphType parent) + protected virtual MethodGraphField CreateNewInstance() { return new MethodGraphField( this.Name, + this.InternalName, this.TypeExpression.Clone(), - parent.Route.CreateChild(this.Name), - this.ObjectType, + this.ItemPath, this.DeclaredReturnType, + this.ObjectType, this.Mode, this.Resolver, this.SecurityGroups, @@ -183,7 +184,7 @@ protected virtual MethodGraphField CreateNewInstance(IGraphType parent) public virtual bool Publish { get; set; } /// - public SchemaItemPath Route { get; } + public ItemPath ItemPath { get; protected set; } /// public IGraphFieldResolver Resolver { get; protected set; } @@ -191,9 +192,6 @@ protected virtual MethodGraphField CreateNewInstance(IGraphType parent) /// public FieldResolutionMode Mode { get; protected set; } - /// - public bool IsLeaf { get; protected set; } - /// public bool IsDeprecated { get; set; } @@ -210,9 +208,12 @@ protected virtual MethodGraphField CreateNewInstance(IGraphType parent) public bool IsVirtual => false; /// - public ISchemaItem Parent => _parent; + public ISchemaItem Parent { get; protected set; } /// 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..585d6ab68 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/ObjectGraphType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/ObjectGraphType.cs @@ -11,6 +11,7 @@ namespace GraphQL.AspNet.Schemas.TypeSystem { using System; using System.Diagnostics; + using System.Linq; using GraphQL.AspNet.Common; using GraphQL.AspNet.Common.Extensions; using GraphQL.AspNet.Interfaces.Schema; @@ -28,27 +29,46 @@ 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 item 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, + ItemPath itemPath, IAppliedDirectiveCollection directives = null) - : base(name, route, directives) + : base(name, internalName, itemPath, directives) { this.ObjectType = Validation.ThrowIfNullOrReturn(objectType, nameof(objectType)); - this.InternalName = this.ObjectType.FriendlyName(); - - this.GraphFieldCollection.AddField(new Introspection_TypeNameMetaField(name)); + this.Extend(new Introspection_TypeNameMetaField(name)); } /// - public IGraphField Extend(IGraphField newField) + public override IGraphType Clone(string typeName = null) { - return this.GraphFieldCollection.AddField(newField); + typeName = typeName?.Trim() ?? this.Name; + var itemPath = this.ItemPath.Clone().Parent.CreateChild(typeName); + + var clonedItem = new ObjectGraphType( + typeName, + this.InternalName, + this.ObjectType, + itemPath, + this.AppliedDirectives); + + clonedItem.Description = this.Description; + clonedItem.Publish = this.Publish; + + foreach (var item in this.InterfaceNames) + clonedItem.InterfaceNames.Add(item); + + foreach (var field in this.Fields.Where(x => !(x is Introspection_TypeNameMetaField))) + clonedItem.Extend(field.Clone(clonedItem)); + + return clonedItem; } /// @@ -59,8 +79,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..c3bdfd2fd 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/ObjectGraphTypeBase.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/ObjectGraphTypeBase.cs @@ -28,26 +28,42 @@ 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 route path of this object. + /// The defined internal name for this graph type. + /// The item path of this object in the schema. /// The directives applied to this schema item /// when its added to a schema. protected ObjectGraphTypeBase( string name, - SchemaItemPath route, + string internalName, + ItemPath itemPath, IAppliedDirectiveCollection directives = null) { this.Name = Validation.ThrowIfNullWhiteSpaceOrReturn(name, nameof(name)); - this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); - _graphFields = new GraphFieldCollection(this); + this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalName, nameof(internalName)); + this.ItemPath = Validation.ThrowIfNullOrReturn(itemPath, nameof(itemPath)); + _graphFields = new GraphFieldCollection(); this.InterfaceNames = new HashSet(); this.Publish = true; this.AppliedDirectives = directives?.Clone(this) ?? new AppliedDirectiveCollection(this); } + /// + public virtual IGraphField Extend(IGraphField newField) + { + Validation.ThrowIfNull(newField, nameof(newField)); + if (newField.Parent != this) + newField = newField.Clone(this); + + return this.GraphFieldCollection.AddField(newField); + } + /// public abstract bool ValidateObject(object item); + /// + public abstract IGraphType Clone(string typeName = null); + /// /// Gets the mutatable collection of graph fields. /// @@ -70,6 +86,9 @@ protected ObjectGraphTypeBase( /// public virtual string Name { get; set; } + /// + public string InternalName { get; } + /// public virtual string Description { get; set; } @@ -92,6 +111,6 @@ protected ObjectGraphTypeBase( public IAppliedDirectiveCollection AppliedDirectives { get; } /// - public SchemaItemPath Route { get; } + public ItemPath ItemPath { get; } } } \ No newline at end of file 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..a2a9d39a4 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; /// @@ -39,7 +39,7 @@ protected ScalarGraphTypeBase( string specifiedByUrl = null) { this.Name = Validation.ThrowIfNullWhiteSpaceOrReturn(name, nameof(name)); - this.Route = new SchemaItemPath(SchemaItemCollections.Scalars, this.Name); + this.ItemPath = new ItemPath(ItemPathRoots.Types, this.Name); this.ObjectType = Validation.ThrowIfNullOrReturn(primaryType, nameof(primaryType)); this.InternalName = this.ObjectType.FriendlyName(); this.Publish = true; @@ -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 IGraphType Clone(string typeName = null) + { + typeName = Validation.ThrowIfNullWhiteSpaceOrReturn(typeName, nameof(typeName)); + 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 = typeName; + + 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; @@ -133,7 +144,7 @@ public virtual string SerializeToQueryLanguage(object item) public IAppliedDirectiveCollection AppliedDirectives { get; } /// - public SchemaItemPath Route { get; } + public ItemPath ItemPath { get; } /// public string SpecifiedByUrl { get; set; } 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..398e1ee40 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/UnionGraphType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/UnionGraphType.cs @@ -10,6 +10,7 @@ namespace GraphQL.AspNet.Schemas.TypeSystem { using System; + using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -34,19 +35,22 @@ 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. + /// The unique path of this union in the schema. /// The collection of directives /// to execute against this union when it is added to a schema. public UnionGraphType( string name, + string internalName, IUnionGraphTypeMapper typeResolver, - SchemaItemPath route, + ItemPath itemPath, IAppliedDirectiveCollection directives = null) { this.Name = Validation.ThrowIfNullWhiteSpaceOrReturn(name, nameof(name)); - this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); + this.InternalName = Validation.ThrowIfNullWhiteSpaceOrReturn(internalName, nameof(internalName)); + this.ItemPath = Validation.ThrowIfNullOrReturn(itemPath, nameof(itemPath)); this.TypeMapper = typeResolver; this.Publish = true; this.AppliedDirectives = directives?.Clone(this) ?? new AppliedDirectiveCollection(this); @@ -79,6 +83,47 @@ public virtual void AddPossibleGraphType(string graphTypeName, Type concreteType _names = _names.Add(graphTypeName); } + /// + public IGraphType Clone(string typeName = null) + { + return this.Clone(typeName, null); + } + + /// + public virtual IGraphType Clone(string typeName = null, Func possibleGraphTypeNameFormatter = null) + { + typeName = typeName?.Trim() ?? this.Name; + var itemPath = this.ItemPath.Clone().Parent.CreateChild(typeName); + + var clonedItem = new UnionGraphType( + typeName, + this.InternalName, + this.TypeMapper, + itemPath, + this.AppliedDirectives); + + clonedItem.Publish = this.Publish; + clonedItem.Description = this.Description; + clonedItem.Publish = this.Publish; + + clonedItem._types = _types.ToImmutableHashSet(); + + if (possibleGraphTypeNameFormatter == null) + { + clonedItem._names = _names.ToImmutableHashSet(); + } + else + { + var list = new List(); + foreach (var str in _names) + list.Add(possibleGraphTypeNameFormatter(str)); + + clonedItem._names = ImmutableHashSet.Create(list.ToArray()); + } + + return clonedItem; + } + /// public virtual IImmutableSet PossibleConcreteTypes => _types; @@ -88,6 +133,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; } @@ -107,6 +155,6 @@ public virtual void AddPossibleGraphType(string graphTypeName, Type concreteType public IAppliedDirectiveCollection AppliedDirectives { get; } /// - public SchemaItemPath Route { get; } + public ItemPath ItemPath { get; } } } \ No newline at end of file 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..97b28820f 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/VirtualGraphField.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/VirtualGraphField.cs @@ -16,19 +16,19 @@ 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; /// - /// An representation of a field on an object graph that maps to no concrete structure in the application. Typically used - /// for nesting controller actions on lengthy route paths. + /// An representation of a field on an object graph type that maps to no concrete structure in the application. Typically used + /// for nesting controller actions on lengthy path templates. /// - [DebuggerDisplay("Virtual Field: {Route.Path}")] + [DebuggerDisplay("Virtual Field: {ItemPath.Path}")] public class VirtualGraphField : IGraphField, IGraphItemDependencies { private static readonly IList REQUIRED_TYPES; @@ -45,29 +45,25 @@ static VirtualGraphField() /// /// Initializes a new instance of the class. /// - /// The parent graph type that owns this field. /// Name of the field in the object graph. - /// The path segment that represents this virtual field. - /// The type name to use for the virtual type that owns this field. + /// The path segment that represents this virtual field. + /// The type expression declared for this field. public VirtualGraphField( - IGraphType parent, string fieldName, - SchemaItemPath route, - string parentTypeName) + ItemPath itemPath, + GraphTypeExpression typeExpression) { - Validation.ThrowIfNull(route, nameof(route)); - parentTypeName = Validation.ThrowIfNullWhiteSpaceOrReturn(parentTypeName, nameof(parentTypeName)); + Validation.ThrowIfNull(itemPath, nameof(itemPath)); - this.Parent = Validation.ThrowIfNullOrReturn(parent, nameof(parent)); this.Name = Validation.ThrowIfNullWhiteSpaceOrReturn(fieldName, nameof(fieldName)); - this.Route = Validation.ThrowIfNullOrReturn(route, nameof(route)); + this.ItemPath = Validation.ThrowIfNullOrReturn(itemPath, nameof(itemPath)); - this.AssociatedGraphType = new VirtualObjectGraphType(parentTypeName); - this.TypeExpression = new GraphTypeExpression(parentTypeName); + this.TypeExpression = Validation.ThrowIfNullOrReturn(typeExpression, nameof(typeExpression)); this.Arguments = new GraphFieldArgumentCollection(this); - this.Resolver = new GraphControllerRouteFieldResolver(new VirtualResolvedObject(this.TypeExpression.TypeName)); + this.Resolver = new GraphControllerVirtualFieldResolver(new VirtualResolvedObject(this.TypeExpression.TypeName)); + this.InternalName = $"VirtualField_{this.Name}"; - // fields made from controller route parameters have no policies directly unto themselves + // fields made from controller path parameters have no policies directly unto themselves // any controller class level policies are individually added to fields they declare this.SecurityGroups = new AppliedSecurityPolicyGroups(); this.Complexity = 1; @@ -77,6 +73,8 @@ public VirtualGraphField( this.Publish = true; this.IsDeprecated = false; this.DeprecationReason = null; + + this.TypeExpression = this.TypeExpression.ToFixed(); } /// @@ -86,15 +84,36 @@ public void UpdateResolver(IGraphFieldResolver newResolver, FieldResolutionMode? } /// - public void AssignParent(IGraphType parent) - { - this.Parent = this.Parent; - } - - /// - public IGraphField Clone(IGraphType parent) + public IGraphField Clone( + ISchemaItem parent = null, + string fieldName = null, + GraphTypeExpression typeExpression = null) { - throw new NotImplementedException("Virtual Fields cannot be cloned."); + parent = parent ?? this.Parent; + fieldName = fieldName?.Trim() ?? this.Name; + typeExpression = typeExpression ?? this.TypeExpression; + + var itemPath = this.ItemPath; + itemPath = parent?.ItemPath.CreateChild(fieldName) ?? itemPath; + + var clonedItem = new VirtualGraphField( + fieldName, + itemPath, + typeExpression); + + clonedItem.Description = this.Description; + clonedItem.TypeExpression = typeExpression.Clone(); + clonedItem.Publish = this.Publish; + clonedItem.DeprecationReason = this.DeprecationReason; + clonedItem.IsDeprecated = this.IsDeprecated; + clonedItem.Complexity = this.Complexity; + clonedItem.Parent = parent; + + // clone over the arguments + foreach (var argument in this.Arguments) + clonedItem.Arguments.AddArgument(argument.Clone(clonedItem)); + + return clonedItem; } /// @@ -111,12 +130,6 @@ public virtual bool CanResolveForGraphType(IGraphType graphType) return false; } - /// - /// Gets the tracked copy of the graph type that represents this virtual field. - /// - /// The type of the associated graph. - public IObjectGraphType AssociatedGraphType { get; } - /// public IGraphFieldResolver Resolver { get; } @@ -127,7 +140,7 @@ public virtual bool CanResolveForGraphType(IGraphType graphType) public string Name { get; set; } /// - public SchemaItemPath Route { get; } + public ItemPath ItemPath { get; } /// public GraphTypeExpression TypeExpression { get; set; } @@ -154,9 +167,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 +202,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..b7b00a6fb 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 item 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, + ItemPath itemPath, 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.ItemPath = Validation.ThrowIfNullOrReturn(itemPath, nameof(itemPath)); 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' @@ -64,9 +61,52 @@ public VirtualGraphFieldArgument( } /// - public IGraphArgument Clone(ISchemaItem parent) + public IGraphArgument Clone( + ISchemaItem parent = null, + string argumentName = null, + GraphTypeExpression typeExpression = null, + DefaultValueCloneOptions defaultValueOptions = DefaultValueCloneOptions.None, + object newDefaultValue = null) { - throw new NotImplementedException("Virtual graph arguments cannot be cloned"); + parent = parent ?? this.Parent; + + argumentName = argumentName?.Trim() ?? this.Name; + + var parentPath = parent?.ItemPath ?? this.ItemPath.Parent; + var itemPath = parentPath.CreateChild(argumentName); + + var hasDefaultValue = this.HasDefaultValue; + var defaultValue = this.DefaultValue; + + switch (defaultValueOptions) + { + case DefaultValueCloneOptions.None: + break; + + case DefaultValueCloneOptions.MakeRequired: + defaultValue = null; + hasDefaultValue = false; + break; + + case DefaultValueCloneOptions.UpdateDefaultValue: + defaultValue = newDefaultValue; + hasDefaultValue = true; + break; + } + + var clonedItem = new VirtualGraphFieldArgument( + parent, + argumentName, + this.InternalName, + typeExpression ?? this.TypeExpression.Clone(), + itemPath, + this.ObjectType, + hasDefaultValue, + defaultValue); + + clonedItem.Description = this.Description; + + return clonedItem; } /// @@ -84,9 +124,6 @@ public IGraphArgument Clone(ISchemaItem parent) /// public object DefaultValue { get; } - /// - public GraphArgumentModifiers ArgumentModifiers { get; } - /// public GraphTypeExpression TypeExpression { get; } @@ -100,7 +137,7 @@ public IGraphArgument Clone(ISchemaItem parent) public bool HasDefaultValue { get; } /// - public SchemaItemPath Route { get; } + public ItemPath ItemPath { get; } /// public ISchemaItem Parent { get; } diff --git a/src/graphql-aspnet/Schemas/TypeSystem/VirtualObjectGraphType.cs b/src/graphql-aspnet/Schemas/TypeSystem/VirtualObjectGraphType.cs index 1f9bdeca2..da25143b3 100644 --- a/src/graphql-aspnet/Schemas/TypeSystem/VirtualObjectGraphType.cs +++ b/src/graphql-aspnet/Schemas/TypeSystem/VirtualObjectGraphType.cs @@ -10,9 +10,10 @@ namespace GraphQL.AspNet.Schemas.TypeSystem { using System; + using System.Collections.Generic; using System.Diagnostics; using GraphQL.AspNet.Attributes; - using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Common; using GraphQL.AspNet.Execution; using GraphQL.AspNet.Interfaces.Schema; using GraphQL.AspNet.Schemas.Structural; @@ -33,17 +34,32 @@ public class VirtualObjectGraphType : ObjectGraphTypeBase, IObjectGraphType, IIn // to allow for proper navigation of an object structure in graphql. This object is generated dynamically from the parsed // metadata of a controller. + /// + /// Constructs a virtual type from the path template extracted from a controller action method. + /// + /// The path template base this virtual type off of. + /// VirtualObjectGraphType. + public static VirtualObjectGraphType FromControllerFieldPathTemplate(ItemPath pathTemplate) + { + var tempName = MakeSafeTypeNameFromItemPath(pathTemplate); + return new VirtualObjectGraphType(tempName, pathTemplate); + } + /// /// Initializes a new instance of the class. /// - /// The name to assign to this type. - public VirtualObjectGraphType(string name) + /// The formal name to assign to the type. + /// The path template that generated this virutal graph type. + private VirtualObjectGraphType(string typeName, ItemPath pathTemplate) : base( - name, - new SchemaItemPath(SchemaItemCollections.Types, name)) + typeName, + $"{nameof(VirtualObjectGraphType)}_{typeName}", + new ItemPath(ItemPathRoots.Types, typeName)) { + this.ItemPathTemplate = Validation.ThrowIfNullOrReturn(pathTemplate, nameof(pathTemplate)); + // add the __typename as a field for this virtual object - this.GraphFieldCollection.AddField(new Introspection_TypeNameMetaField(name)); + this.Extend(new Introspection_TypeNameMetaField(typeName)); } /// @@ -53,18 +69,69 @@ public override bool ValidateObject(object item) } /// - public IGraphField Extend(IGraphField newField) + public override IGraphType Clone(string typeName = null) { - return this.GraphFieldCollection.AddField(newField); + typeName = typeName?.Trim() ?? this.Name; + return new VirtualObjectGraphType( + typeName, + this.ItemPathTemplate); } /// public override bool IsVirtual => true; + /// + /// Gets the raw path template that was used to define this virtual type. + /// + /// The item path template. + public ItemPath ItemPathTemplate { get; private set; } + /// public Type ObjectType => typeof(VirtualObjectGraphType); - /// - public string InternalName => typeof(VirtualObjectGraphType).FriendlyName(); + /// + /// Converts a route path into a unique graph type, removing special control characters + /// but retaining its uniqueness. + /// + /// The path to convert. + /// An optional formatter that will apply + /// special casing to a path segment before its added to the name. + /// System.String. + public static string MakeSafeTypeNameFromItemPath( + ItemPath path, + Func segmentNameFormatter = null) + { + Validation.ThrowIfNull(path, nameof(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: + var segmentName = pathSegmentName; + if (segmentNameFormatter != null) + segmentName = segmentNameFormatter(pathSegmentName); + + segments.Add(segmentName); + break; + } + } + + segments.Reverse(); + return string.Join("_", segments); + } } } \ 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..68621ac96 100644 --- a/src/library-common.props +++ b/src/library-common.props @@ -1,12 +1,12 @@ - net8.0;net7.0;net6.0;netstandard2.0; + net8.0;net6.0; latest $(NoWarn);1701;1702;1705;1591;NU1603;IDE0019;IDE0017;RCS1146;RCS1194; GraphQL.AspNet - 1.0.0.0 + 2.0.0.0 GraphQL ASP.NET GraphQL ASP.NET @@ -50,6 +50,8 @@ + + @@ -62,20 +64,4 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/library-tests.props b/src/library-tests.props index 37541a616..5d3fee9ea 100644 --- a/src/library-tests.props +++ b/src/library-tests.props @@ -1,7 +1,7 @@ - net8.0;net7.0;net6.0; + net8.0;net6.0; latest $(NoWarn);1701;1702;1705;1591;NU1603;IDE0019;IDE0017;RCS1146;RCS1194; GraphQL.AspNet.Tests diff --git a/src/styles.ruleset b/src/styles.ruleset index 4084807a6..890ade469 100644 --- a/src/styles.ruleset +++ b/src/styles.ruleset @@ -84,6 +84,7 @@ + diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Attributes/SubscriptionAttributeTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Attributes/SubscriptionAttributeTests.cs index 69869d93a..735cb1abc 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Attributes/SubscriptionAttributeTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Attributes/SubscriptionAttributeTests.cs @@ -22,7 +22,7 @@ public class SubscriptionAttributeTests public void SubscriptionAttribute_EmptyConstructor_PropertyCheck() { var attrib = new SubscriptionAttribute(); - Assert.AreEqual(SchemaItemCollections.Subscription, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Subscription, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -35,7 +35,7 @@ public void SubscriptionAttribute_EmptyConstructor_PropertyCheck() public void SubscriptionAttribute_TemplateConstructor_PropertyCheck() { var attrib = new SubscriptionAttribute("mySubscriptionRoute"); - Assert.AreEqual(SchemaItemCollections.Subscription, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Subscription, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual("mySubscriptionRoute", attrib.Template); @@ -48,7 +48,7 @@ public void SubscriptionAttribute_TemplateConstructor_PropertyCheck() public void SubscriptionAttribute_ReturnTypeConstructor_PropertyCheck() { var attrib = new SubscriptionAttribute(typeof(SubscriptionAttributeTests)); - Assert.AreEqual(SchemaItemCollections.Subscription, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Subscription, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -62,7 +62,7 @@ public void SubscriptionAttribute_ReturnTypeConstructor_PropertyCheck() public void SubscriptionAttribute_MultiTypeConstructor_PropertyCheck() { var attrib = new SubscriptionAttribute(typeof(SubscriptionAttributeTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Subscription, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Subscription, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -77,7 +77,7 @@ public void SubscriptionAttribute_MultiTypeConstructor_PropertyCheck() public void SubscriptionAttribute_TemplateMultiTypeConstructor_PropertyCheck() { var attrib = new SubscriptionAttribute("myField", typeof(SubscriptionAttributeTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Subscription, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Subscription, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual("myField", attrib.Template); @@ -92,7 +92,7 @@ public void SubscriptionAttribute_TemplateMultiTypeConstructor_PropertyCheck() public void SubscriptionAttribute_UnionConstructor_PropertyCheck() { var attrib = new SubscriptionAttribute("myField", "myUnionType", typeof(SubscriptionAttributeTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Subscription, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Subscription, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual("myUnionType", attrib.UnionTypeName); Assert.AreEqual("myField", attrib.Template); @@ -107,7 +107,7 @@ public void SubscriptionAttribute_UnionConstructor_PropertyCheck() public void SubscriptionRootAttribute_EmptyConstructor_PropertyCheck() { var attrib = new SubscriptionRootAttribute(); - Assert.AreEqual(SchemaItemCollections.Subscription, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Subscription, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -120,7 +120,7 @@ public void SubscriptionRootAttribute_EmptyConstructor_PropertyCheck() public void SubscriptionRootAttribute_TemplateConstructor_PropertyCheck() { var attrib = new SubscriptionRootAttribute("mySubscriptionRootRoute"); - Assert.AreEqual(SchemaItemCollections.Subscription, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Subscription, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual("mySubscriptionRootRoute", attrib.Template); @@ -133,7 +133,7 @@ public void SubscriptionRootAttribute_TemplateConstructor_PropertyCheck() public void SubscriptionRootAttribute_ReturnTypeConstructor_PropertyCheck() { var attrib = new SubscriptionRootAttribute(typeof(SubscriptionAttributeTests)); - Assert.AreEqual(SchemaItemCollections.Subscription, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Subscription, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -147,7 +147,7 @@ public void SubscriptionRootAttribute_ReturnTypeConstructor_PropertyCheck() public void SubscriptionRootAttribute_MultiTypeConstructor_PropertyCheck() { var attrib = new SubscriptionRootAttribute(typeof(SubscriptionAttributeTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Subscription, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Subscription, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -162,7 +162,7 @@ public void SubscriptionRootAttribute_MultiTypeConstructor_PropertyCheck() public void SubscriptionRootAttribute_TemplateMultiTypeConstructor_PropertyCheck() { var attrib = new SubscriptionRootAttribute("myField", typeof(SubscriptionAttributeTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Subscription, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Subscription, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual("myField", attrib.Template); @@ -176,7 +176,7 @@ public void SubscriptionRootAttribute_TemplateMultiTypeConstructor_PropertyCheck public void SubscriptionRootAttribute_UnionConstructor_PropertyCheck() { var attrib = new SubscriptionRootAttribute("myField", "myUnionType", typeof(SubscriptionAttributeTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Subscription, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Subscription, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual("myUnionType", attrib.UnionTypeName); Assert.AreEqual("myField", attrib.Template); 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..15441d8bc 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,8 +27,8 @@ public class SubscriptionEnabledFieldFieldMakerTests public void SubscriptionActionField_TransfersDirectives() { var mockController = Substitute.For(); - mockController.InternalFullName.Returns(typeof(SubscriptionTestController).Name); - mockController.Route.Returns(new SchemaItemPath("path0")); + mockController.InternalName.Returns(typeof(SubscriptionTestController).Name); + mockController.ItemPath.Returns(new ItemPath("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..214ab7ad5 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/SubscriptionQueryExecutionDirectiveTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/SubscriptionQueryExecutionDirectiveTests.cs @@ -9,11 +9,14 @@ namespace GraphQL.AspNet.Tests.Execution { + using System.Linq; using System.Threading.Tasks; - using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.ExecutionDirectiveTestData; using GraphQL.AspNet.Tests.Execution.SubscriptionQueryExecutionData; + using GraphQL.AspNet.Tests.Framework; using GraphQL.AspNet.Tests.Mocks; using NUnit.Framework; @@ -29,6 +32,13 @@ public async Task ExecutionOfAQueryPlan_WithValidDefaultObject_forSubscription_Y .AddSubscriptionServer() .Build(); + var graphType = server + .Schema + .KnownTypes + .Single(x => x.Name.ToLowerInvariant() == "subscription_subscriptiondata"); + + var field = ((IGraphFieldContainer)graphType).Fields["retrieveObject"]; + var template = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(SubQueryController.RetrieveObject)); var sourceObject = new TwoPropertyObject() @@ -49,8 +59,9 @@ property1 @toLower } } }") - .AddDefaultValue(template.Route, sourceObject); + .AddDefaultValue(field.ItemPath, sourceObject); + var result1 = await server.ExecuteQuery(builder); var result = await server.RenderResult(builder); var expectedOutput = @"{ 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..bf46a0372 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/SubscriptionQueryExecutionTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Execution/SubscriptionQueryExecutionTests.cs @@ -9,14 +9,16 @@ namespace GraphQL.AspNet.Tests.Execution { - using System.Security.Cryptography; + using System.Linq; using System.Threading.Tasks; using GraphQL.AspNet; - using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; + using GraphQL.AspNet.Configuration; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Common.CommonHelpers; using GraphQL.AspNet.Tests.Execution.SubscriptionQueryExecutionData; + using GraphQL.AspNet.Tests.Framework; using GraphQL.AspNet.Tests.Mocks; - using NuGet.Frameworks; using NUnit.Framework; [TestFixture] @@ -30,7 +32,12 @@ public async Task ExecutionOfAQueryPlan_WithValidDefaultObject_forSubscription_Y .AddSubscriptionServer() .Build(); - var template = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(SubQueryController.RetrieveObject)); + var graphType = server + .Schema + .KnownTypes + .Single(x => x.Name.ToLowerInvariant() == "subscription_subscriptiondata"); + + var field = ((IGraphFieldContainer)graphType).Fields["retrieveObject"]; var sourceObject = new TwoPropertyObject() { @@ -40,7 +47,7 @@ public async Task ExecutionOfAQueryPlan_WithValidDefaultObject_forSubscription_Y var builder = server.CreateQueryContextBuilder() .AddQueryText("subscription { subscriptionData { retrieveObject { property1 } } }") - .AddDefaultValue(template.Route, sourceObject); + .AddDefaultValue(field.ItemPath, sourceObject); var result = await server.RenderResult(builder); var expectedOutput = @@ -65,7 +72,12 @@ public async Task ExecutionOfASubscription_ThatReturnsSkipActionResult_AddsKeyTo .AddSubscriptionServer() .Build(); - var template = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(SubQueryController.SkipEventMethod)); + var graphType = server + .Schema + .KnownTypes + .Single(x => x.Name.ToLowerInvariant() == "subscription_subscriptiondata"); + + var field = ((IGraphFieldContainer)graphType).Fields["skipEventMethod"]; var sourceObject = new TwoPropertyObject() { @@ -75,7 +87,7 @@ public async Task ExecutionOfASubscription_ThatReturnsSkipActionResult_AddsKeyTo var builder = server.CreateQueryContextBuilder() .AddQueryText("subscription { subscriptionData { skipEventMethod { property1 } } }") - .AddDefaultValue(template.Route, sourceObject); + .AddDefaultValue(field.ItemPath, sourceObject); var context = builder.Build(); await server.ExecuteQuery(context); @@ -91,7 +103,12 @@ public async Task ExecutionOfASubscription_ThatReturnsSkipAndCompleteActionResul .AddSubscriptionServer() .Build(); - var template = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(SubQueryController.SkipEventAndCompleteMethod)); + var graphType = server + .Schema + .KnownTypes + .Single(x => x.Name.ToLowerInvariant() == "subscription_subscriptiondata"); + + var field = ((IGraphFieldContainer)graphType).Fields["skipEventAndCompleteMethod"]; var sourceObject = new TwoPropertyObject() { @@ -101,7 +118,7 @@ public async Task ExecutionOfASubscription_ThatReturnsSkipAndCompleteActionResul var builder = server.CreateQueryContextBuilder() .AddQueryText("subscription { subscriptionData { skipEventAndCompleteMethod { property1 } } }") - .AddDefaultValue(template.Route, sourceObject); + .AddDefaultValue(field.ItemPath, sourceObject); var context = builder.Build(); await server.ExecuteQuery(context); @@ -118,7 +135,12 @@ public async Task ExecutionOfASubscription_ThatReturnsACompleteActionResult_Adds .AddSubscriptionServer() .Build(); - var template = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(SubQueryController.CompleteMethod)); + var graphType = server + .Schema + .KnownTypes + .Single(x => x.Name.ToLowerInvariant() == "subscription_subscriptiondata"); + + var field = ((IGraphFieldContainer)graphType).Fields["completeMethod"]; var sourceObject = new TwoPropertyObject() { @@ -128,7 +150,7 @@ public async Task ExecutionOfASubscription_ThatReturnsACompleteActionResult_Adds var builder = server.CreateQueryContextBuilder() .AddQueryText("subscription { subscriptionData { completeMethod { property1 } } }") - .AddDefaultValue(template.Route, sourceObject); + .AddDefaultValue(field.ItemPath, sourceObject); var expectedResult = @" { @@ -187,5 +209,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.ItemPath, 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.ItemPath, 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/Logging/ClientProxyLoggingTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Logging/ClientProxyLoggingTests.cs index 5c2a875ea..4a661abfa 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Logging/ClientProxyLoggingTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Logging/ClientProxyLoggingTests.cs @@ -77,7 +77,7 @@ public void ClientSubscriptionCreated_PropertyCheck() var sub = Substitute.For(); sub.Id.Returns("sub1"); - sub.Route.Returns(new SchemaItemPath("[subscription]/bobSub1")); + sub.ItemPath.Returns(new ItemPath("[subscription]/bobSub1")); var entry = new ClientProxySubscriptionCreatedLogEntry(client, sub); @@ -97,7 +97,7 @@ public void ClientSubscriptionStopped_PropertyCheck() var sub = Substitute.For(); sub.Id.Returns("sub1"); - sub.Route.Returns(new SchemaItemPath("[subscription]/bobSub1")); + sub.ItemPath.Returns(new ItemPath("[subscription]/bobSub1")); var entry = new ClientProxySubscriptionStoppedLogEntry(client, sub); @@ -122,7 +122,7 @@ public void GraphQLWSClientSubscriptionEventReceived_PropertyCheck() var subs = new List(); subs.Add(sub); - var fieldPath = new SchemaItemPath("[subscription]/bob1"); + var fieldPath = new ItemPath("[subscription]/bob1"); var entry = new ClientProxySubscriptionEventReceived( proxy, diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/Logging/ExtensionTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/Logging/ExtensionTests.cs index 720043406..100aaac85 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/Logging/ExtensionTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/Logging/ExtensionTests.cs @@ -40,7 +40,7 @@ public void SchemaSubscriptionRouteRegistered_EnsureEventEntryIsGeneratedAndPass recordedlogEntry = ((Func)x[1])(); }); - mock.SchemaSubscriptionRouteRegistered("testPath"); + mock.SchemaSubscriptionUrlRouteRegistered("testPath"); var entry = recordedlogEntry as SchemaSubscriptionRouteRegisteredLogEntry; Assert.IsNotNull(entry); 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..8c300986b 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; @@ -33,7 +33,7 @@ public class SubscriptionContextBuilder private readonly IUserSecurityContext _seceurityContext; private readonly IQueryExecutionRequest _mockRequest; - private readonly List> _sourceData; + private readonly List> _sourceData; private IServiceProvider _serviceProvider; private IQueryExecutionMetrics _metrics; @@ -57,7 +57,7 @@ public SubscriptionContextBuilder( _mockRequest = Substitute.For(); _mockRequest.OperationName.Returns(string.Empty); - _sourceData = new List>(); + _sourceData = new List>(); _mockRequest.ToDataPackage().Returns((x) => new AspNet.GraphQueryData() @@ -130,9 +130,9 @@ public SubscriptionContextBuilder AddLogger(IGraphEventLogger eventLogger) /// The field path representing the action to accept the parameter. /// The source data. /// SubscriptionContextBuilder. - public SubscriptionContextBuilder AddDefaultValue(SchemaItemPath path, object sourceData) + public SubscriptionContextBuilder AddDefaultValue(ItemPath path, object sourceData) { - _sourceData.Add(new KeyValuePair(path, sourceData)); + _sourceData.Add(new KeyValuePair(path, sourceData)); return this; } @@ -164,7 +164,7 @@ public virtual SubcriptionQueryExecutionContext Build(string subscriptionId = nu { var mockField = Substitute.For(); mockField.FieldSource.Returns(GraphFieldSource.Action); - mockField.Route.Returns(kvp.Key); + mockField.ItemPath.Returns(kvp.Key); context.DefaultFieldSources.AddSource(mockField, kvp.Value); } 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..a6bfb785a --- /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.ItemPath.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.ItemPath.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.ItemPath.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.ItemPath.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..f8017c5c7 --- /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.ItemPath.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.ItemPath.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.ItemPath.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 78% 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..54055f12d 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,17 @@ // 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,8 +28,8 @@ private SubscriptionControllerActionGraphFieldTemplate CreateActionTemplate(); - mockController.InternalFullName.Returns(typeof(TControllerType).Name); - mockController.Route.Returns(new SchemaItemPath("path0")); + mockController.InternalName.Returns(typeof(TControllerType).Name); + mockController.ItemPath.Returns(new ItemPath("path0")); mockController.Name.Returns("path0"); mockController.ObjectType.Returns(typeof(TControllerType)); @@ -48,18 +46,19 @@ 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(ItemPathRoots.Subscription, action.ItemPath.Root); + Assert.AreEqual("[subscription]/path0/path1", action.ItemPath.Path); + 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); - Assert.IsFalse(action.Route.IsTopLevelField); + Assert.IsFalse(action.ItemPath.IsTopLevelField); Assert.IsFalse(action.IsAsyncField); Assert.AreEqual("SingleMethod", action.EventName); @@ -78,8 +77,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(ParameterModifiers.ParentFieldResult)); } [Test] @@ -88,8 +86,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(ParameterModifiers.ParentFieldResult)); } [Test] @@ -99,7 +96,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(ParameterModifiers.ParentFieldResult)); Assert.IsNotNull(sourceDataParam); Assert.AreEqual(typeof(TwoPropertyObjectV2), sourceDataParam.DeclaredArgumentType); } @@ -111,7 +108,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(ParameterModifiers.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 52% 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..4cc705665 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.ItemPath.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/ClientSubscriptionTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/ClientSubscriptionTests.cs index 6c641ce14..cce384516 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/ClientSubscriptionTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/ClientSubscriptionTests.cs @@ -53,7 +53,7 @@ public async Task ClientSubscription_FromQueryData_GeneralPropertyCheck() "abc123"); Assert.IsTrue(sub.IsValid); - Assert.AreEqual("[subscription]/WatchObjects", sub.Route.Path); + Assert.AreEqual("[subscription]/WatchObjects", sub.ItemPath.Path); Assert.AreEqual("abc123", sub.Id); Assert.AreEqual(field, sub.Field); Assert.AreEqual(result.Client, sub.Client); 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-subscriptions-tests/SubscriptionServer/SubscriptionCollectionTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/SubscriptionCollectionTests.cs index d3def0dd7..90406fa5d 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/SubscriptionCollectionTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/SubscriptionCollectionTests.cs @@ -28,12 +28,12 @@ public void AddNewSub_ReflectsInCollectionCount() var fakeSub = Substitute.For>(); - var field = new SchemaItemPath("[subscription]/field1"); + var field = new ItemPath("[subscription]/field1"); fakeSub.Id.Returns("abc123"); - fakeSub.Route.Returns(field); + fakeSub.ItemPath.Returns(field); collection.Add(fakeSub); - Assert.AreEqual(1, collection.CountByRoute(field)); + Assert.AreEqual(1, collection.CountByPath(field)); Assert.AreEqual(1, collection.Count); Assert.AreEqual(1, collection.Keys.Count()); @@ -49,17 +49,17 @@ public void AddNewSub_OfSameRouteButDiffererntId_ReflectsInCollectionCount() var fakeSub = Substitute.For>(); - var field = new SchemaItemPath("[subscription]/field1"); + var field = new ItemPath("[subscription]/field1"); fakeSub.Id.Returns("abc123"); - fakeSub.Route.Returns(field); + fakeSub.ItemPath.Returns(field); var fakeSub2 = Substitute.For>(); fakeSub2.Id.Returns("abc1234"); - fakeSub2.Route.Returns(field); + fakeSub2.ItemPath.Returns(field); collection.Add(fakeSub); collection.Add(fakeSub2); - Assert.AreEqual(2, collection.CountByRoute(field)); + Assert.AreEqual(2, collection.CountByPath(field)); Assert.AreEqual(2, collection.Count); } @@ -70,30 +70,30 @@ public void AddNewSub_OfDifferentRoute_ReflectsInCollectionCount() var fakeSub = Substitute.For>(); - var field = new SchemaItemPath("[subscription]/field1"); - var field2 = new SchemaItemPath("[subscription]/field2"); - var field3 = new SchemaItemPath("[subscription]/field3"); + var field = new ItemPath("[subscription]/field1"); + var field2 = new ItemPath("[subscription]/field2"); + var field3 = new ItemPath("[subscription]/field3"); fakeSub.Id.Returns("abc123"); - fakeSub.Route.Returns(field); + fakeSub.ItemPath.Returns(field); var fakeSub2 = Substitute.For>(); fakeSub2.Id.Returns("abc1234"); - fakeSub2.Route.Returns(field2); + fakeSub2.ItemPath.Returns(field2); collection.Add(fakeSub); collection.Add(fakeSub2); - Assert.AreEqual(1, collection.CountByRoute(field)); - Assert.AreEqual(1, collection.CountByRoute(field2)); + Assert.AreEqual(1, collection.CountByPath(field)); + Assert.AreEqual(1, collection.CountByPath(field2)); Assert.AreEqual(2, collection.Count); - var foundSubs = collection.RetreiveByRoute(field); + var foundSubs = collection.RetreiveByItemPath(field); Assert.AreEqual(fakeSub, foundSubs.Single()); - foundSubs = collection.RetreiveByRoute(field2); + foundSubs = collection.RetreiveByItemPath(field2); Assert.AreEqual(fakeSub2, foundSubs.Single()); - foundSubs = collection.RetreiveByRoute(field3); + foundSubs = collection.RetreiveByItemPath(field3); CollectionAssert.IsEmpty(foundSubs); var counted = 0; @@ -113,9 +113,9 @@ public void RemoveExistingSub_ReflectsInCollectionCount() var fakeSub = Substitute.For>(); - var field = new SchemaItemPath("[subscription]/field1"); + var field = new ItemPath("[subscription]/field1"); fakeSub.Id.Returns("abc123"); - fakeSub.Route.Returns(field); + fakeSub.ItemPath.Returns(field); collection.Add(fakeSub); Assert.AreEqual(1, collection.Count); @@ -130,15 +130,15 @@ public void RemoveExistingSub_ReflectsInCollectionCount() public void AddExistingSubId_ThrowsException() { var collection = new SubscriptionCollection(); - var field = new SchemaItemPath("[subscription]/field1"); + var field = new ItemPath("[subscription]/field1"); var fakeSub = Substitute.For>(); fakeSub.Id.Returns("abc123"); - fakeSub.Route.Returns(field); + fakeSub.ItemPath.Returns(field); var fakeSub2 = Substitute.For>(); fakeSub2.Id.Returns("abc123"); - fakeSub2.Route.Returns(field); + fakeSub2.ItemPath.Returns(field); collection.Add(fakeSub); @@ -165,13 +165,13 @@ public void AddNewSub_NotReturnedOnInvalidRoute() var fakeSub = Substitute.For>(); - var field = new SchemaItemPath("[subscription]/field1"); - var field2 = new SchemaItemPath("[wrong]/field2"); + var field = new ItemPath("[subscription]/field1"); + var field2 = new ItemPath("[wrong]/field2"); fakeSub.Id.Returns("abc123"); - fakeSub.Route.Returns(field); + fakeSub.ItemPath.Returns(field); collection.Add(fakeSub); - Assert.AreEqual(0, collection.CountByRoute(field2)); + Assert.AreEqual(0, collection.CountByPath(field2)); } [Test] @@ -181,12 +181,12 @@ public void AddNewSub_NotReturnedOnNullRoute() var fakeSub = Substitute.For>(); - var field = new SchemaItemPath("[subscription]/field1"); + var field = new ItemPath("[subscription]/field1"); fakeSub.Id.Returns("abc123"); - fakeSub.Route.Returns(field); + fakeSub.ItemPath.Returns(field); collection.Add(fakeSub); - Assert.AreEqual(0, collection.CountByRoute(null)); + Assert.AreEqual(0, collection.CountByPath(null)); } } } \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/SubscriptionCreationTests.cs b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/SubscriptionCreationTests.cs index 2209b5b44..1320583ea 100644 --- a/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/SubscriptionCreationTests.cs +++ b/src/unit-tests/graphql-aspnet-subscriptions-tests/SubscriptionServer/SubscriptionCreationTests.cs @@ -44,7 +44,7 @@ public async Task SubscriptionExecution_YieldsSubscriptionOnContext() var createdSub = context.Subscription; Assert.IsTrue(createdSub.IsValid); - Assert.AreEqual("[subscription]/subscriptionData/RetrieveObject", createdSub.Field.Route.Path); + Assert.AreEqual("[subscription]/subscriptionData/RetrieveObject", createdSub.Field.ItemPath.Path); Assert.AreEqual(id, createdSub.Id); Assert.AreEqual(0, createdSub.Messages.Count); Assert.AreEqual(result.Client, createdSub.Client); 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/GraphQLSchemaHelperMethods.cs b/src/unit-tests/graphql-aspnet-testframework/GraphQLSchemaHelperMethods.cs index 9b466cf79..b13de74ce 100644 --- a/src/unit-tests/graphql-aspnet-testframework/GraphQLSchemaHelperMethods.cs +++ b/src/unit-tests/graphql-aspnet-testframework/GraphQLSchemaHelperMethods.cs @@ -9,11 +9,9 @@ namespace GraphQL.AspNet.Tests.Framework { - using System; using GraphQL.AspNet.Configuration; using GraphQL.AspNet.Configuration.Formatting; using GraphQL.AspNet.Interfaces.Schema; - using GraphQL.AspNet.Schemas.TypeSystem; /// /// Methods for quickly configuring schema related things during testing. @@ -30,7 +28,9 @@ public static void SetNoAlterationConfiguration(this ISchema schema) { var declarationOptions = new SchemaDeclarationConfiguration(); declarationOptions.Merge(schema.Configuration.DeclarationOptions); - declarationOptions.GraphNamingFormatter = new GraphNameFormatter(GraphNameFormatStrategy.NoChanges); + declarationOptions.SchemaFormatStrategy = SchemaFormatStrategyBuilder + .Create(TextFormatOptions.NoChanges, applyDefaultRules: false) + .Build(); var config = new SchemaConfiguration( declarationOptions, diff --git a/src/unit-tests/graphql-aspnet-testframework/GraphQLTemplateHelper.cs b/src/unit-tests/graphql-aspnet-testframework/GraphQLTemplateHelper.cs index 04ccf6b03..2405fc1a2 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 @@ -71,11 +93,11 @@ public static IGraphFieldTemplate CreateFieldTemplate(string fieldOrMetho if (string.Equals(kvp.InternalName, fieldOrMethodName, StringComparison.OrdinalIgnoreCase)) return kvp; - if (string.Equals(kvp.Route.Name, fieldOrMethodName, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(kvp.ItemPath.Name, fieldOrMethodName, StringComparison.OrdinalIgnoreCase)) 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..9a2347eff 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; @@ -30,7 +31,7 @@ public class QueryContextBuilder { private readonly IServiceProvider _serviceProvider; private readonly IQueryExecutionRequest _mockRequest; - private readonly List> _sourceData; + private readonly List> _sourceData; private IUserSecurityContext _userSecurityContext; private IQueryExecutionMetrics _metrics; @@ -52,7 +53,7 @@ public QueryContextBuilder( _items = new MetaDataCollection(); _mockRequest = Substitute.For(); _mockRequest.Items.Returns(_items); - _sourceData = new List>(); + _sourceData = new List>(); } /// @@ -129,9 +130,9 @@ public QueryContextBuilder AddUserSecurityContext(IUserSecurityContext securityC /// The field path representing the action to accept the parameter. /// The source data. /// QueryContextBuilder. - public QueryContextBuilder AddDefaultValue(SchemaItemPath path, object sourceData) + public QueryContextBuilder AddDefaultValue(ItemPath path, object sourceData) { - _sourceData.Add(new KeyValuePair(path, sourceData)); + _sourceData.Add(new KeyValuePair(path, sourceData)); return this; } @@ -157,8 +158,8 @@ public virtual QueryExecutionContext Build() foreach (var kvp in _sourceData) { var mockField = Substitute.For(); - mockField.FieldSource.Returns(Internal.TypeTemplates.GraphFieldSource.Action); - mockField.Route.Returns(kvp.Key); + mockField.FieldSource.Returns(GraphFieldSource.Action); + mockField.ItemPath.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..4215c04de 100644 --- a/src/unit-tests/graphql-aspnet-testframework/TestServerBuilder{TSchema}.cs +++ b/src/unit-tests/graphql-aspnet-testframework/TestServerBuilder{TSchema}.cs @@ -105,7 +105,11 @@ protected virtual void PerformInitialConfiguration(SchemaOptions options) if (_initialSetup.HasFlag(TestOptions.UseCodeDeclaredNames)) { - options.DeclarationOptions.GraphNamingFormatter = new GraphNameFormatter(GraphNameFormatStrategy.NoChanges); + // use create empty here to be backwards compatiable with + // the original implementation of intermeidate types from v1x + options.DeclarationOptions.SchemaFormatStrategy = SchemaFormatStrategyBuilder + .Create(TextFormatOptions.NoChanges, applyDefaultRules: false) + .Build(); } if (_initialSetup.HasFlag(TestOptions.IncludeExceptions)) @@ -127,6 +131,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 +231,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-testframework/graphql-aspnet-testframework.csproj b/src/unit-tests/graphql-aspnet-testframework/graphql-aspnet-testframework.csproj index d294329bb..ee9dc7f42 100644 --- a/src/unit-tests/graphql-aspnet-testframework/graphql-aspnet-testframework.csproj +++ b/src/unit-tests/graphql-aspnet-testframework/graphql-aspnet-testframework.csproj @@ -2,7 +2,7 @@ - net8.0;net7.0;net6.0; + net8.0;net6.0; GraphQL.AspNet.Tests.Framework GraphQL.AspNet.TestFramework GraphQL ASP.NET Test Framework 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-thirdpartydll/graphql-aspnet-tests-thirdpartydll.csproj b/src/unit-tests/graphql-aspnet-tests-thirdpartydll/graphql-aspnet-tests-thirdpartydll.csproj index fa889e4e6..246a3ca57 100644 --- a/src/unit-tests/graphql-aspnet-tests-thirdpartydll/graphql-aspnet-tests-thirdpartydll.csproj +++ b/src/unit-tests/graphql-aspnet-tests-thirdpartydll/graphql-aspnet-tests-thirdpartydll.csproj @@ -1,7 +1,7 @@  - net8.0;net7.0;net6.0; + net8.0;net6.0; latest $(NoWarn);1701;1702;1705;1591;NU1603;IDE0060;IDE0052;IDE0044;IDE0059;IDE0052 GraphQL.AspNet.Tests.ThirdPartyDll 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..7a4ca788d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Attributes/AttributeDataIntegrityTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Attributes/AttributeDataIntegrityTests.cs @@ -28,7 +28,7 @@ public class AttributeDataIntegrityTests public void GraphFieldAttribute_EmptyConstructor_PropertyCheck() { var attrib = new GraphFieldAttribute(); - Assert.AreEqual(SchemaItemCollections.Types, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Types, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -41,7 +41,7 @@ public void GraphFieldAttribute_EmptyConstructor_PropertyCheck() public void GraphFieldAttribute_FieldNameConstructor_PropertyCheck() { var attrib = new GraphFieldAttribute("myFieldName"); - Assert.AreEqual(SchemaItemCollections.Types, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Types, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual("myFieldName", attrib.Template); @@ -54,7 +54,7 @@ public void GraphFieldAttribute_FieldNameConstructor_PropertyCheck() public void QueryAttribute_EmptyConstructor_PropertyCheck() { var attrib = new QueryAttribute(); - Assert.AreEqual(SchemaItemCollections.Query, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Query, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -67,7 +67,7 @@ public void QueryAttribute_EmptyConstructor_PropertyCheck() public void QueryAttribute_TemplateConstructor_PropertyCheck() { var attrib = new QueryAttribute("myQueryRoute"); - Assert.AreEqual(SchemaItemCollections.Query, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Query, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual("myQueryRoute", attrib.Template); @@ -80,7 +80,7 @@ public void QueryAttribute_TemplateConstructor_PropertyCheck() public void QueryAttribute_ReturnTypeConstructor_PropertyCheck() { var attrib = new QueryAttribute(typeof(AttributeDataIntegrityTests)); - Assert.AreEqual(SchemaItemCollections.Query, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Query, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -94,7 +94,7 @@ public void QueryAttribute_ReturnTypeConstructor_PropertyCheck() public void QueryAttribute_MultiTypeConstructor_PropertyCheck() { var attrib = new QueryAttribute(typeof(AttributeDataIntegrityTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Query, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Query, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -109,7 +109,7 @@ public void QueryAttribute_MultiTypeConstructor_PropertyCheck() public void QueryAttribute_TemplateMultiTypeConstructor_PropertyCheck() { var attrib = new QueryAttribute("myField", typeof(AttributeDataIntegrityTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Query, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Query, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual("myField", attrib.Template); @@ -124,7 +124,7 @@ public void QueryAttribute_TemplateMultiTypeConstructor_PropertyCheck() public void QueryAttribute_UnionConstructor_PropertyCheck() { var attrib = new QueryAttribute("myField", "myUnionType", typeof(AttributeDataIntegrityTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Query, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Query, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual("myUnionType", attrib.UnionTypeName); Assert.AreEqual("myField", attrib.Template); @@ -135,11 +135,51 @@ 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(ItemPathRoots.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(ItemPathRoots.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(ItemPathRoots.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() { var attrib = new MutationAttribute(); - Assert.AreEqual(SchemaItemCollections.Mutation, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Mutation, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -152,7 +192,7 @@ public void MutationAttribute_EmptyConstructor_PropertyCheck() public void MutationAttribute_TemplateConstructor_PropertyCheck() { var attrib = new MutationAttribute("myMutationRoute"); - Assert.AreEqual(SchemaItemCollections.Mutation, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Mutation, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual("myMutationRoute", attrib.Template); @@ -165,7 +205,7 @@ public void MutationAttribute_TemplateConstructor_PropertyCheck() public void MutationAttribute_ReturnTypeConstructor_PropertyCheck() { var attrib = new MutationAttribute(typeof(AttributeDataIntegrityTests)); - Assert.AreEqual(SchemaItemCollections.Mutation, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Mutation, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -179,7 +219,7 @@ public void MutationAttribute_ReturnTypeConstructor_PropertyCheck() public void MutationAttribute_MultiTypeConstructor_PropertyCheck() { var attrib = new MutationAttribute(typeof(AttributeDataIntegrityTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Mutation, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Mutation, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -194,7 +234,7 @@ public void MutationAttribute_MultiTypeConstructor_PropertyCheck() public void MutationAttribute_TemplateMultiTypeConstructor_PropertyCheck() { var attrib = new MutationAttribute("myField", typeof(AttributeDataIntegrityTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Mutation, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Mutation, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual("myField", attrib.Template); @@ -209,7 +249,7 @@ public void MutationAttribute_TemplateMultiTypeConstructor_PropertyCheck() public void MutationAttribute_UnionConstructor_PropertyCheck() { var attrib = new MutationAttribute("myField", "myUnionType", typeof(AttributeDataIntegrityTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Mutation, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Mutation, attrib.FieldType); Assert.AreEqual(false, attrib.IsRootFragment); Assert.AreEqual("myUnionType", attrib.UnionTypeName); Assert.AreEqual("myField", attrib.Template); @@ -220,11 +260,51 @@ 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(ItemPathRoots.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(ItemPathRoots.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(ItemPathRoots.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() { var attrib = new QueryRootAttribute(); - Assert.AreEqual(SchemaItemCollections.Query, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Query, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -237,7 +317,7 @@ public void QueryRootAttribute_EmptyConstructor_PropertyCheck() public void QueryRootAttribute_TemplateConstructor_PropertyCheck() { var attrib = new QueryRootAttribute("myQueryRootRoute"); - Assert.AreEqual(SchemaItemCollections.Query, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Query, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual("myQueryRootRoute", attrib.Template); @@ -250,7 +330,7 @@ public void QueryRootAttribute_TemplateConstructor_PropertyCheck() public void QueryRootAttribute_ReturnTypeConstructor_PropertyCheck() { var attrib = new QueryRootAttribute(typeof(AttributeDataIntegrityTests)); - Assert.AreEqual(SchemaItemCollections.Query, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Query, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -264,7 +344,7 @@ public void QueryRootAttribute_ReturnTypeConstructor_PropertyCheck() public void QueryRootAttribute_MultiTypeConstructor_PropertyCheck() { var attrib = new QueryRootAttribute(typeof(AttributeDataIntegrityTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Query, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Query, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -279,7 +359,7 @@ public void QueryRootAttribute_MultiTypeConstructor_PropertyCheck() public void QueryRootAttribute_TemplateMultiTypeConstructor_PropertyCheck() { var attrib = new QueryRootAttribute("myField", typeof(AttributeDataIntegrityTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Query, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Query, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual("myField", attrib.Template); @@ -293,7 +373,7 @@ public void QueryRootAttribute_TemplateMultiTypeConstructor_PropertyCheck() public void QueryRootAttribute_UnionConstructor_PropertyCheck() { var attrib = new QueryRootAttribute("myField", "myUnionType", typeof(AttributeDataIntegrityTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Query, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Query, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual("myUnionType", attrib.UnionTypeName); Assert.AreEqual("myField", attrib.Template); @@ -307,7 +387,7 @@ public void QueryRootAttribute_UnionConstructor_PropertyCheck() public void MutationRootAttribute_EmptyConstructor_PropertyCheck() { var attrib = new MutationRootAttribute(); - Assert.AreEqual(SchemaItemCollections.Mutation, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Mutation, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -320,7 +400,7 @@ public void MutationRootAttribute_EmptyConstructor_PropertyCheck() public void MutationRootAttribute_TemplateConstructor_PropertyCheck() { var attrib = new MutationRootAttribute("myMutationRootRoute"); - Assert.AreEqual(SchemaItemCollections.Mutation, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Mutation, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual("myMutationRootRoute", attrib.Template); @@ -333,7 +413,7 @@ public void MutationRootAttribute_TemplateConstructor_PropertyCheck() public void MutationRootAttribute_ReturnTypeConstructor_PropertyCheck() { var attrib = new MutationRootAttribute(typeof(AttributeDataIntegrityTests)); - Assert.AreEqual(SchemaItemCollections.Mutation, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Mutation, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -347,7 +427,7 @@ public void MutationRootAttribute_ReturnTypeConstructor_PropertyCheck() public void MutationRootAttribute_MultiTypeConstructor_PropertyCheck() { var attrib = new MutationRootAttribute(typeof(AttributeDataIntegrityTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Mutation, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Mutation, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual(Constants.Routing.ACTION_METHOD_META_NAME, attrib.Template); @@ -362,7 +442,7 @@ public void MutationRootAttribute_MultiTypeConstructor_PropertyCheck() public void MutationRootAttribute_TemplateMultiTypeConstructor_PropertyCheck() { var attrib = new MutationRootAttribute("myField", typeof(AttributeDataIntegrityTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Mutation, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Mutation, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual(null, attrib.UnionTypeName); Assert.AreEqual("myField", attrib.Template); @@ -377,7 +457,7 @@ public void MutationRootAttribute_TemplateMultiTypeConstructor_PropertyCheck() public void MutationRootAttribute_UnionConstructor_PropertyCheck() { var attrib = new MutationRootAttribute("myField", "myUnionType", typeof(AttributeDataIntegrityTests), typeof(GraphFieldAttribute)); - Assert.AreEqual(SchemaItemCollections.Mutation, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Mutation, attrib.FieldType); Assert.AreEqual(true, attrib.IsRootFragment); Assert.AreEqual("myUnionType", attrib.UnionTypeName); Assert.AreEqual("myField", attrib.Template); @@ -591,7 +671,7 @@ public void TypeExtension_UnionConstructor_PropertyCheck() { var attrib = new TypeExtensionAttribute(typeof(AttributeDataIntegrityTests), "myField", "myUnionType", typeof(GraphFieldAttribute), typeof(string)); - Assert.AreEqual(SchemaItemCollections.Types, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Types, attrib.FieldType); Assert.AreEqual(typeof(AttributeDataIntegrityTests), attrib.TypeToExtend); Assert.AreEqual("myField", attrib.Template); Assert.AreEqual("myUnionType", attrib.UnionTypeName); @@ -627,7 +707,7 @@ public void BatchTypeExtension_UnionConstructor_PropertyCheck() { var attrib = new BatchTypeExtensionAttribute(typeof(AttributeDataIntegrityTests), "myField", "myUnionType", typeof(GraphFieldAttribute), typeof(string)); - Assert.AreEqual(SchemaItemCollections.Types, attrib.FieldType); + Assert.AreEqual(ItemPathRoots.Types, attrib.FieldType); Assert.AreEqual(typeof(AttributeDataIntegrityTests), attrib.TypeToExtend); Assert.AreEqual("myField", attrib.Template); Assert.AreEqual("myUnionType", attrib.UnionTypeName); 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..15f537f2c 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] @@ -52,13 +52,15 @@ public async Task ProductionDefaults_UpperCaseFieldNames() builder.AddGraphQL(o => { - o.DeclarationOptions.GraphNamingFormatter = new GraphNameFormatter(fieldNameStrategy: GraphNameFormatStrategy.UpperCase); + o.DeclarationOptions.SchemaFormatStrategy = SchemaFormatStrategyBuilder + .Create(fieldNameFormat: TextFormatOptions.UpperCase, applyDefaultRules: false) + .Build(); }); var server = builder.Build(); var queryBuilder = server.CreateQueryContextBuilder(); - queryBuilder.AddQueryText("{ RETRIEVEFAN(NAME: \"bob\"){ ID, NAME, FANSPEED} }"); + queryBuilder.AddQueryText("{ RETRIEVEFAN(name: \"bob\"){ ID, NAME, FANSPEED} }"); var result = await server.RenderResult(queryBuilder).ConfigureAwait(false); @@ -84,7 +86,9 @@ public async Task ProductionDefaults_LowerCaseEnumValues() builder.AddGraphQL(o => { - o.DeclarationOptions.GraphNamingFormatter = new GraphNameFormatter(enumValueStrategy: GraphNameFormatStrategy.LowerCase); + o.DeclarationOptions.SchemaFormatStrategy = SchemaFormatStrategyBuilder + .Create(enumValueNameFormat: TextFormatOptions.LowerCase, applyDefaultRules: false) + .Build(); }); var server = builder.Build(); @@ -116,7 +120,9 @@ public async Task ProductionDefaults_CamelCasedTypeNames() builder.AddGraphQL(o => { - o.DeclarationOptions.GraphNamingFormatter = new GraphNameFormatter(typeNameStrategy: GraphNameFormatStrategy.CamelCase); + o.DeclarationOptions.SchemaFormatStrategy = SchemaFormatStrategyBuilder + .Create(typeNameFormat: TextFormatOptions.CamelCase, applyDefaultRules: false) + .Build(); }); var server = builder.Build(); diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/ConfigurationSetupTests.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/ConfigurationSetupTests.cs index 397534745..49e06aa48 100644 --- a/src/unit-tests/graphql-aspnet-tests/Configuration/ConfigurationSetupTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/ConfigurationSetupTests.cs @@ -16,10 +16,7 @@ namespace GraphQL.AspNet.Tests.Configuration using GraphQL.AspNet.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; @@ -35,20 +32,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() { @@ -72,8 +55,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] @@ -82,29 +63,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] @@ -119,11 +81,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(); @@ -153,11 +110,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); @@ -185,11 +137,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); @@ -220,7 +167,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); @@ -303,8 +249,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) @@ -365,7 +309,7 @@ public void AddGraphQL_AttemptingToInitializeSchemaASecondTime_ThrowsException() options.AddGraphType(); }); - Assert.Throws(() => + Assert.Throws(() => { service1Collection.AddGraphQL(options => { 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/FormatStrategyTestData/IWidget.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/IWidget.cs new file mode 100644 index 000000000..0272f5192 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/IWidget.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.FormatStrategyTestData +{ + using GraphQL.AspNet.Attributes; + + public interface IWidget + { + [GraphField] + string ArgItem(string arg1); + + [GraphField] + string FixedArgItem([FromGraphQL(TypeExpression = "Type")] string arg1); + + int IntProp { get; set; } + + [GraphField(TypeExpression = "Type")] + int FixedIntPropAsNullable { get; set; } + + [GraphField(TypeExpression = "Type!")] + int FixedIntPropAsNotNullable { get; set; } + + string StringProp { get; set; } + + [GraphField(TypeExpression = "Type")] + string FixedStringProp { get; set; } + + IWidget ReferenceProp { get; set; } + + [GraphField(TypeExpression = "Type")] + IWidget FixedReferenceProp { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/IWidgetList.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/IWidgetList.cs new file mode 100644 index 000000000..0fcc73c9e --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/IWidgetList.cs @@ -0,0 +1,28 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Configuration.FormatStrategyTestData +{ + using System.Collections.Generic; + using GraphQL.AspNet.Attributes; + + public interface IWidgetList + { + [GraphField] + string TripleListArg(List>> arg1); + + [GraphField] + string TripleListArgFixed([FromGraphQL(TypeExpression = "[[[Type]]]")] List>> arg1); + + List>> TripleListProp { get; set; } + + [GraphField(TypeExpression = "[[[Type]]]")] + List>> TripleListPropFixed { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/Widget.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/Widget.cs new file mode 100644 index 000000000..ad142f8ad --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/Widget.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.Configuration.FormatStrategyTestData +{ + using GraphQL.AspNet.Attributes; + + public class Widget + { + [GraphField] + public string ArgItem(string arg1) + { + return string.Empty; + } + + [GraphField] + public string FixedArgItem([FromGraphQL(TypeExpression = "Type")] string arg1) + { + return string.Empty; + } + + public int IntProp { get; set; } + + [GraphField(TypeExpression = "Type")] + public int FixedIntPropAsNullable { get; set; } + + [GraphField(TypeExpression = "Type!")] + public int FixedIntPropAsNotNullable { get; set; } + + public string StringProp { get; set; } + + [GraphField(TypeExpression = "Type")] + public string FixedStringProp { get; set; } + + public Widget ReferenceProp { get; set; } + + [GraphField(TypeExpression = "Type")] + public Widget FixedReferenceProp { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetArgumentController.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetArgumentController.cs new file mode 100644 index 000000000..010183291 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetArgumentController.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.Configuration.FormatStrategyTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Controllers; + + public class WidgetArgumentController : GraphController + { + [QueryRoot] + public Widget WithArgument(string arg1) + { + return new Widget(); + } + + [QueryRoot(TypeExpression = "Type")] + public Widget WithArgumentFixed([FromGraphQL(TypeExpression = "Type")] string arg1) + { + return new Widget(); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetControllerWithDefaultValue.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetControllerWithDefaultValue.cs new file mode 100644 index 000000000..1cff269a4 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetControllerWithDefaultValue.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.Configuration.FormatStrategyTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Controllers; + + public class WidgetControllerWithDefaultValue : GraphController + { + [QueryRoot("retrieveRootWidget")] + public Widget RetrieveWidgetFromRoot(string arg1 = "default 1") + { + return new Widget(); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetDirective.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetDirective.cs new file mode 100644 index 000000000..868c54876 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetDirective.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.Configuration.FormatStrategyTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Directives; + using GraphQL.AspNet.Interfaces.Controllers; + using GraphQL.AspNet.Schemas.TypeSystem; + + [GraphType("widgetD")] + public class WidgetDirective : GraphDirective + { + [DirectiveLocations(DirectiveLocation.FIELD | DirectiveLocation.FRAGMENT_SPREAD | DirectiveLocation.INLINE_FRAGMENT)] + public IGraphActionResult Execute() + { + return this.Ok(); + } + } +} diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetList.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetList.cs new file mode 100644 index 000000000..02dc15a48 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetList.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.FormatStrategyTestData +{ + using System.Collections.Generic; + using GraphQL.AspNet.Attributes; + + public class WidgetList + { + [GraphField] + public string TripleListArg(List>> arg1) + { + return string.Empty; + } + + [GraphField] + public string TripleListArgFixed([FromGraphQL(TypeExpression = "[[[Type]]]")] List>> arg1) + { + return string.Empty; + } + + [GraphField(TypeExpression = "[[[Type]]]")] + public List>> TripleListPropFixed { get; set; } + + public List>> TripleListProp { get; set; } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetListController.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetListController.cs new file mode 100644 index 000000000..ae23bf535 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetListController.cs @@ -0,0 +1,78 @@ +// ************************************************************* +// project: graphql-aspnet +// -- +// repo: https://github.com/graphql-aspnet +// docs: https://graphql-aspnet.github.io +// -- +// License: MIT +// ************************************************************* + +namespace GraphQL.AspNet.Tests.Configuration.FormatStrategyTestData +{ + using System.Collections.Generic; + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Controllers; + + public class WidgetListController : GraphController + { + [QueryRoot] + public Widget IntArgument(List arg1) + { + return new Widget(); + } + + [QueryRoot] + public Widget IntArgumentFixed([FromGraphQL(TypeExpression = "[Type!]")] List arg1) + { + return new Widget(); + } + + [QueryRoot] + public Widget StringListArgument(List arg1) + { + return new Widget(); + } + + [QueryRoot] + public Widget StringListArgumentFixed([FromGraphQL(TypeExpression = "[Type]")] List arg1) + { + return new Widget(); + } + + [QueryRoot] + public Widget InputObjectArgument(List arg1) + { + return new Widget(); + } + + [QueryRoot] + public Widget InputObjectArgumentFixed([FromGraphQL(TypeExpression = "[Type]")] List arg1) + { + return new Widget(); + } + + [QueryRoot] + public Widget WidgetField() + { + return null; + } + + [QueryRoot(TypeExpression = "Type")] + public Widget WidgetFieldFixed() + { + return null; + } + + [QueryRoot] + public List ReturnedStringList() + { + return null; + } + + [QueryRoot(TypeExpression = "[Type]")] + public List ReturnedStringListFixed() + { + return null; + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetType.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetType.cs new file mode 100644 index 000000000..aca99c34d --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetType.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.FormatStrategyTestData +{ + public enum WidgetType + { + Type1, + } +} diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetWithDefaultValue.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetWithDefaultValue.cs new file mode 100644 index 000000000..b0286cfce --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetWithDefaultValue.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.Configuration.FormatStrategyTestData +{ + public class WidgetWithDefaultValue + { + public WidgetWithDefaultValue() + { + this.StringProp = "default 1"; + this.IntProp = 4; + } + + public string StringProp { get; set; } + + public int IntProp { get; set; } + } +} diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetsController.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetsController.cs new file mode 100644 index 000000000..aa65d1cb5 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/FormatStrategyTestData/WidgetsController.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.Configuration.FormatStrategyTestData +{ + using GraphQL.AspNet.Attributes; + using GraphQL.AspNet.Controllers; + + public class WidgetsController : GraphController + { + [QueryRoot("retrieveRootWidget")] + public Widget RetrieveWidgetFromRoot() + { + return new Widget(); + } + + [Query("/path1/path2/retrieveWidget")] + public Widget RetrieveWidgetFromTemplatePath() + { + return new Widget(); + } + } +} \ No newline at end of file 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/SchemaFormatStrategy_NameFormatTests.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaFormatStrategy_NameFormatTests.cs new file mode 100644 index 000000000..5b7587874 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaFormatStrategy_NameFormatTests.cs @@ -0,0 +1,223 @@ +// ************************************************************* +// 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 GraphQL.AspNet.Configuration.Formatting; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Configuration.FormatStrategyTestData; + using GraphQL.AspNet.Tests.Framework; + using NUnit.Framework; + + [TestFixture] + public class SchemaFormatStrategy_NameFormatTests + { + [TestCase("TYPE1", TextFormatOptions.UpperCase)] + [TestCase("type1", TextFormatOptions.LowerCase)] + [TestCase("Type1", TextFormatOptions.ProperCase)] + public void EnumValues_FormatTests( + string expectedValueName, + TextFormatOptions enumValueFormat) + { + var strategy = SchemaFormatStrategyBuilder.Create() + .WithEnumValueFormat(enumValueFormat) + .Build(); + + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddType(); + o.DeclarationOptions.SchemaFormatStrategy = strategy; + }) + .Build(); + + var enumType = server.Schema.KnownTypes.FindGraphType(typeof(WidgetType)) as IEnumGraphType; + var firstValue = enumType.Values.FindByEnumValue(WidgetType.Type1); + + Assert.AreEqual(expectedValueName, firstValue.Name); + } + + [TestCase(typeof(Widget), TypeKind.OBJECT, TextFormatOptions.ProperCase, "Widget")] + [TestCase(typeof(Widget), TypeKind.OBJECT, TextFormatOptions.UpperCase, "WIDGET")] + [TestCase(typeof(Widget), TypeKind.OBJECT, TextFormatOptions.CamelCase, "widget")] + [TestCase(typeof(Widget), TypeKind.OBJECT, TextFormatOptions.LowerCase, "widget")] + [TestCase(typeof(Widget), TypeKind.INPUT_OBJECT, TextFormatOptions.ProperCase, "Input_Widget")] + [TestCase(typeof(Widget), TypeKind.INPUT_OBJECT, TextFormatOptions.CamelCase, "input_Widget")] + [TestCase(typeof(Widget), TypeKind.INPUT_OBJECT, TextFormatOptions.UpperCase, "INPUT_WIDGET")] + [TestCase(typeof(Widget), TypeKind.INPUT_OBJECT, TextFormatOptions.LowerCase, "input_widget")] + [TestCase(typeof(IWidget), TypeKind.OBJECT, TextFormatOptions.ProperCase, "IWidget")] + [TestCase(typeof(IWidget), TypeKind.OBJECT, TextFormatOptions.CamelCase, "iWidget")] + [TestCase(typeof(IWidget), TypeKind.INTERFACE, TextFormatOptions.UpperCase, "IWIDGET")] + [TestCase(typeof(IWidget), TypeKind.INTERFACE, TextFormatOptions.LowerCase, "iwidget")] + [TestCase(typeof(WidgetType), TypeKind.ENUM, TextFormatOptions.ProperCase, "WidgetType")] + [TestCase(typeof(WidgetType), TypeKind.ENUM, TextFormatOptions.CamelCase, "widgetType")] + [TestCase(typeof(WidgetType), TypeKind.ENUM, TextFormatOptions.UpperCase, "WIDGETTYPE")] + [TestCase(typeof(WidgetType), TypeKind.ENUM, TextFormatOptions.LowerCase, "widgettype")] + + // directive name is uneffected by graph type name formats + [TestCase(typeof(WidgetDirective), TypeKind.DIRECTIVE, TextFormatOptions.ProperCase, "widgetD")] + [TestCase(typeof(WidgetDirective), TypeKind.DIRECTIVE, TextFormatOptions.UpperCase, "widgetD")] + [TestCase(typeof(WidgetDirective), TypeKind.DIRECTIVE, TextFormatOptions.LowerCase, "widgetD")] + public void GraphTypeNames_FormatTests( + Type targetType, + TypeKind typeKind, + TextFormatOptions typeNameFormat, + string expectedName) + { + var strategy = SchemaFormatStrategyBuilder.Create() + .WithGraphTypeNameFormat(typeNameFormat) + .Build(); + + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddType(targetType, typeKind); + o.DeclarationOptions.SchemaFormatStrategy = strategy; + }) + .Build(); + + var graphType = server.Schema.KnownTypes.FindGraphType(targetType); + + Assert.AreEqual(expectedName, graphType.Name); + } + + [TestCase(typeof(Widget), TypeKind.OBJECT, TextFormatOptions.ProperCase, "IntProp")] + [TestCase(typeof(Widget), TypeKind.OBJECT, TextFormatOptions.CamelCase, "intProp")] + [TestCase(typeof(Widget), TypeKind.OBJECT, TextFormatOptions.UpperCase, "INTPROP")] + [TestCase(typeof(Widget), TypeKind.OBJECT, TextFormatOptions.LowerCase, "intprop")] + [TestCase(typeof(Widget), TypeKind.INPUT_OBJECT, TextFormatOptions.ProperCase, "IntProp")] + [TestCase(typeof(Widget), TypeKind.INPUT_OBJECT, TextFormatOptions.CamelCase, "intProp")] + [TestCase(typeof(Widget), TypeKind.INPUT_OBJECT, TextFormatOptions.UpperCase, "INTPROP")] + [TestCase(typeof(Widget), TypeKind.INPUT_OBJECT, TextFormatOptions.LowerCase, "intprop")] + [TestCase(typeof(IWidget), TypeKind.INTERFACE, TextFormatOptions.ProperCase, "IntProp")] + [TestCase(typeof(IWidget), TypeKind.INTERFACE, TextFormatOptions.CamelCase, "intProp")] + [TestCase(typeof(IWidget), TypeKind.INTERFACE, TextFormatOptions.UpperCase, "INTPROP")] + [TestCase(typeof(IWidget), TypeKind.INTERFACE, TextFormatOptions.LowerCase, "intprop")] + public void FieldNames_FormatTests( + Type targetType, + TypeKind typeKind, + TextFormatOptions typeNameFormat, + string expectedName) + { + var strategy = SchemaFormatStrategyBuilder.Create() + .WithFieldNameFormat(typeNameFormat) + .Build(); + + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddType(targetType, typeKind); + o.DeclarationOptions.SchemaFormatStrategy = strategy; + }) + .Build(); + + var graphType = server.Schema.KnownTypes.FindGraphType(targetType); + + switch (typeKind) + { + case TypeKind.INPUT_OBJECT: + Assert.IsNotNull(((IInputObjectGraphType)graphType).Fields.FindField(expectedName)); + return; + + case TypeKind.OBJECT: + case TypeKind.INTERFACE: + Assert.IsNotNull(((IGraphFieldContainer)graphType).Fields.FindField(expectedName)); + return; + } + } + + // directive name is uneffected by field name formats + [TestCase(typeof(WidgetDirective), TypeKind.DIRECTIVE, TextFormatOptions.ProperCase, "WidgetD")] + [TestCase(typeof(WidgetDirective), TypeKind.DIRECTIVE, TextFormatOptions.CamelCase, "widgetD")] + [TestCase(typeof(WidgetDirective), TypeKind.DIRECTIVE, TextFormatOptions.UpperCase, "WIDGETD")] + [TestCase(typeof(WidgetDirective), TypeKind.DIRECTIVE, TextFormatOptions.LowerCase, "widgetd")] + public void DirectiveNames_FormatTests( + Type targetType, + TypeKind typeKind, + TextFormatOptions typeNameFormat, + string expectedName) + { + var strategy = SchemaFormatStrategyBuilder.Create() + .WithDirectiveNameFormat(typeNameFormat) + .Build(); + + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddType(targetType, typeKind); + o.DeclarationOptions.SchemaFormatStrategy = strategy; + }) + .Build(); + + var directive = server.Schema.KnownTypes.FindGraphType(targetType) as IDirective; + + Assert.AreEqual(expectedName, directive.Name); + } + + [TestCase(typeof(Widget), TypeKind.OBJECT, TextFormatOptions.ProperCase, "argItem", "Arg1")] + [TestCase(typeof(Widget), TypeKind.OBJECT, TextFormatOptions.CamelCase, "argItem", "arg1")] + [TestCase(typeof(Widget), TypeKind.OBJECT, TextFormatOptions.UpperCase, "argItem", "ARG1")] + [TestCase(typeof(Widget), TypeKind.OBJECT, TextFormatOptions.LowerCase, "argItem", "arg1")] + [TestCase(typeof(IWidget), TypeKind.INTERFACE, TextFormatOptions.ProperCase, "argItem", "Arg1")] + [TestCase(typeof(IWidget), TypeKind.INTERFACE, TextFormatOptions.CamelCase, "argItem", "arg1")] + [TestCase(typeof(IWidget), TypeKind.INTERFACE, TextFormatOptions.UpperCase, "argItem", "ARG1")] + [TestCase(typeof(IWidget), TypeKind.INTERFACE, TextFormatOptions.LowerCase, "argItem", "arg1")] + public void ArgumentNames_FormatTests( + Type targetType, + TypeKind typeKind, + TextFormatOptions typeNameFormat, + string fieldName, + string expectedArgName) + { + var strategy = SchemaFormatStrategyBuilder.Create() + .WithFieldArgumentNameFormat(typeNameFormat) + .Build(); + + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddType(targetType, typeKind); + o.DeclarationOptions.SchemaFormatStrategy = strategy; + }) + .Build(); + + var graphType = server.Schema.KnownTypes.FindGraphType(targetType) as IGraphFieldContainer; + var field = graphType.Fields[fieldName]; + + Assert.IsNotNull(field.Arguments.FindArgument(expectedArgName)); + } + + [TestCase(TextFormatOptions.ProperCase, "RetrieveRootWidget")] + [TestCase(TextFormatOptions.CamelCase, "retrieveRootWidget")] + [TestCase(TextFormatOptions.UpperCase, "RETRIEVEROOTWIDGET")] + [TestCase(TextFormatOptions.LowerCase, "retrieverootwidget")] + public void Controller_FieldNames_FormatTests( + TextFormatOptions typeNameFormat, + string expectedFieldName) + { + var strategy = SchemaFormatStrategyBuilder.Create() + .WithFieldNameFormat(typeNameFormat) + .Build(); + + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddType(); + o.DeclarationOptions.SchemaFormatStrategy = strategy; + }) + .Build(); + + var fieldSet = server.Schema.KnownTypes.FindGraphType("Query") as IGraphFieldContainer; + + Assert.IsNotNull(fieldSet.Fields.FindField(expectedFieldName)); + } + } +} diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaFormatStrategy_NonNullListTests.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaFormatStrategy_NonNullListTests.cs new file mode 100644 index 000000000..a496c255c --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaFormatStrategy_NonNullListTests.cs @@ -0,0 +1,139 @@ +// ************************************************************* +// 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 GraphQL.AspNet.Configuration.Formatting; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Configuration.FormatStrategyTestData; + using GraphQL.AspNet.Tests.Framework; + using NUnit.Framework; + + [TestFixture] + public class SchemaFormatStrategy_NonNullListTests + { + [TestCase(typeof(WidgetList), TypeKind.INPUT_OBJECT, "tripleListProp", "[[[String]!]!]!")] + [TestCase(typeof(WidgetList), TypeKind.INPUT_OBJECT, "tripleListPropFixed", "[[[String]]]")] + public void DeclareInputFieldListsAsNonNull_TypeExpressionsAreUpdated( + Type targetType, + TypeKind typeKind, + string fieldName, + string expectedTypeExpression) + { + ExecuteStrategyTest( + targetType, + typeKind, + fieldName, + string.Empty, + expectedTypeExpression, + x => x.DeclareInputFieldListsAsNonNull(x => true)); + } + + [TestCase(typeof(WidgetList), TypeKind.OBJECT, "tripleListProp", "[[[String]!]!]!")] + [TestCase(typeof(WidgetList), TypeKind.OBJECT, "tripleListPropFixed", "[[[String]]]")] + [TestCase(typeof(IWidgetList), TypeKind.INTERFACE, "tripleListProp", "[[[String]!]!]!")] + [TestCase(typeof(IWidgetList), TypeKind.INTERFACE, "tripleListPropFixed", "[[[String]]]")] + [TestCase(typeof(WidgetListController), TypeKind.CONTROLLER, "returnedStringList", "[String]!")] + [TestCase(typeof(WidgetListController), TypeKind.CONTROLLER, "returnedStringListFixed", "[String]")] + public void DeclareFieldListsAsNonNull_TypeExpressionsAreUpdated( + Type targetType, + TypeKind typeKind, + string fieldName, + string expectedTypeExpression) + { + ExecuteStrategyTest( + targetType, + typeKind, + fieldName, + string.Empty, + expectedTypeExpression, + x => x.DeclareFieldListsAsNonNull(x => true)); + } + + [TestCase(typeof(WidgetList), TypeKind.OBJECT, "tripleListArg", "arg1", "[[[String]!]!]!")] + [TestCase(typeof(WidgetList), TypeKind.OBJECT, "tripleListArgFixed", "arg1", "[[[String]]]")] + [TestCase(typeof(IWidgetList), TypeKind.INTERFACE, "tripleListArg", "arg1", "[[[String]!]!]!")] + [TestCase(typeof(IWidgetList), TypeKind.INTERFACE, "tripleListArgFixed", "arg1", "[[[String]]]")] + [TestCase(typeof(WidgetListController), TypeKind.CONTROLLER, "stringListArgument", "arg1", "[String]!")] + [TestCase(typeof(WidgetListController), TypeKind.CONTROLLER, "stringListArgumentFixed", "arg1", "[String]")] + public void DeclareArgumentListsAsNonNull_TypeExpressionsAreUpdated( + Type targetType, + TypeKind typeKind, + string fieldName, + string argName, + string expectedTypeExpression) + { + ExecuteStrategyTest( + targetType, + typeKind, + fieldName, + argName, + expectedTypeExpression, + x => x.DeclareArgumentListsAsNonNull(x => true)); + } + + private void ExecuteStrategyTest( + Type targetType, + TypeKind typeKind, + string fieldName, + string argName, + string expectedTypeExpression, + Action strategyToApply) + { + var builder = SchemaFormatStrategyBuilder.Create(); + strategyToApply(builder); + + var strategy = builder.Build(); + + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddType(targetType, typeKind); + o.DeclarationOptions.SchemaFormatStrategy = strategy; + }) + .Build(); + + if (typeKind == TypeKind.CONTROLLER) + { + var query = server.Schema.KnownTypes.FindGraphType("Query") as IObjectGraphType; + var controllerField = query.Fields[fieldName]; + + if (!string.IsNullOrWhiteSpace(argName)) + { + var controllerFieldArg = controllerField.Arguments[argName]; + Assert.AreEqual(expectedTypeExpression, controllerFieldArg.TypeExpression.ToString()); + return; + } + + Assert.AreEqual(expectedTypeExpression, controllerField.TypeExpression.ToString()); + return; + } + + var graphType = server.Schema.KnownTypes.FindGraphType(targetType, typeKind); + if (typeKind == TypeKind.INPUT_OBJECT) + { + var field = ((IInputObjectGraphType)graphType).Fields[fieldName]; + Assert.AreEqual(expectedTypeExpression, field.TypeExpression.ToString()); + return; + } + + var container = graphType as IGraphFieldContainer; + if (string.IsNullOrWhiteSpace(argName)) + { + Assert.AreEqual(expectedTypeExpression, container[fieldName].TypeExpression.ToString()); + return; + } + + var arg = container[fieldName].Arguments[argName]; + Assert.AreEqual(expectedTypeExpression, arg.TypeExpression.ToString()); + } + } +} \ No newline at end of file diff --git a/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaFormatStrategy_NonNullValueTests.cs b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaFormatStrategy_NonNullValueTests.cs new file mode 100644 index 000000000..20c131339 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Configuration/SchemaFormatStrategy_NonNullValueTests.cs @@ -0,0 +1,186 @@ +// ************************************************************* +// 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 GraphQL.AspNet.Configuration.Formatting; + using GraphQL.AspNet.Interfaces.Schema; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Tests.Configuration.FormatStrategyTestData; + using GraphQL.AspNet.Tests.Framework; + using NUnit.Framework; + + [TestFixture] + public class SchemaFormatStrategy_NonNullValueTests + { + [TestCase(typeof(Widget), TypeKind.INPUT_OBJECT, "stringProp", "String!")] + [TestCase(typeof(Widget), TypeKind.INPUT_OBJECT, "fixedStringProp", "String")] + public void DeclareInputFieldValuesAsNonNull_TypeExpressionsAreUpdated( + Type targetType, + TypeKind typeKind, + string fieldName, + string expectedTypeExpression) + { + ExecuteStrategyTest( + targetType, + typeKind, + fieldName, + string.Empty, + expectedTypeExpression, + x => x.DeclareInputFieldValuesAsNonNull(x => true)); + } + + [TestCase(typeof(Widget), TypeKind.OBJECT, "stringProp", "String!")] + [TestCase(typeof(Widget), TypeKind.OBJECT, "fixedStringProp", "String")] + [TestCase(typeof(IWidget), TypeKind.INTERFACE, "stringProp", "String!")] + [TestCase(typeof(IWidget), TypeKind.INTERFACE, "fixedStringProp", "String")] + [TestCase(typeof(WidgetArgumentController), TypeKind.CONTROLLER, "withArgument", "Widget!")] + [TestCase(typeof(WidgetArgumentController), TypeKind.CONTROLLER, "withArgumentFixed", "Widget")] + public void DeclareFieldValuesAsNonNull_TypeExpressionsAreUpdated( + Type targetType, + TypeKind typeKind, + string fieldName, + string expectedTypeExpression) + { + ExecuteStrategyTest( + targetType, + typeKind, + fieldName, + string.Empty, + expectedTypeExpression, + x => x.DeclareFieldValuesAsNonNull(x => true)); + } + + [TestCase(typeof(Widget), TypeKind.OBJECT, "argItem", "arg1", "String!")] + [TestCase(typeof(Widget), TypeKind.OBJECT, "fixedArgItem", "arg1", "String")] + [TestCase(typeof(IWidget), TypeKind.INTERFACE, "argItem", "arg1", "String!")] + [TestCase(typeof(IWidget), TypeKind.INTERFACE, "fixedArgItem", "arg1", "String")] + [TestCase(typeof(WidgetArgumentController), TypeKind.CONTROLLER, "withArgument", "arg1", "String!")] + [TestCase(typeof(WidgetArgumentController), TypeKind.CONTROLLER, "withArgumentFixed", "arg1", "String")] + public void DeclareArgumentValuesAsNonNull_TypeExpressionsAreUpdated( + Type targetType, + TypeKind typeKind, + string fieldName, + string argName, + string expectedTypeExpression) + { + ExecuteStrategyTest( + targetType, + typeKind, + fieldName, + argName, + expectedTypeExpression, + x => x.DeclareArgumentValuesAsNonNull(x => true)); + } + + private void ExecuteStrategyTest( + Type targetType, + TypeKind typeKind, + string fieldName, + string argName, + string expectedTypeExpression, + Action strategyToApply) + { + var builder = SchemaFormatStrategyBuilder.Create(); + strategyToApply(builder); + + var strategy = builder.Build(); + + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddType(targetType, typeKind); + o.DeclarationOptions.SchemaFormatStrategy = strategy; + }) + .Build(); + + if (typeKind == TypeKind.CONTROLLER) + { + var query = server.Schema.KnownTypes.FindGraphType("Query") as IObjectGraphType; + var controllerField = query.Fields[fieldName]; + + if (!string.IsNullOrWhiteSpace(argName)) + { + var controllerFieldArg = controllerField.Arguments[argName]; + Assert.AreEqual(expectedTypeExpression, controllerFieldArg.TypeExpression.ToString()); + return; + } + + Assert.AreEqual(expectedTypeExpression, controllerField.TypeExpression.ToString()); + return; + } + + var graphType = server.Schema.KnownTypes.FindGraphType(targetType, typeKind); + if (typeKind == TypeKind.INPUT_OBJECT) + { + var field = ((IInputObjectGraphType)graphType).Fields[fieldName]; + Assert.AreEqual(expectedTypeExpression, field.TypeExpression.ToString()); + return; + } + + var container = graphType as IGraphFieldContainer; + if (string.IsNullOrWhiteSpace(argName)) + { + Assert.AreEqual(expectedTypeExpression, container[fieldName].TypeExpression.ToString()); + return; + } + + var arg = container[fieldName].Arguments[argName]; + Assert.AreEqual(expectedTypeExpression, arg.TypeExpression.ToString()); + } + + [Test] + public void InputObjectWithNullableField_WithNonNullDefault_DefaultIsRetained_WhenFieldMadeNonNull() + { + var strategy = SchemaFormatStrategyBuilder.Create() + .DeclareInputFieldValuesAsNonNull(x => x.ObjectType == typeof(string)) + .Build(); + + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddType(TypeKind.INPUT_OBJECT); + o.DeclarationOptions.SchemaFormatStrategy = strategy; + }) + .Build(); + + var graphType = server.Schema.KnownTypes.FindGraphType($"Input_{nameof(WidgetWithDefaultValue)}") as IInputObjectGraphType; + var field = graphType.Fields["stringProp"]; + + Assert.AreEqual("String!", field.TypeExpression.ToString()); + Assert.IsTrue(field.HasDefaultValue); + Assert.AreEqual("default 1", field.DefaultValue.ToString()); + } + + [Test] + public void FieldWithNullableFieldArgument_WithNonNullDefault_DefaultIsRetained_WhenFieldMadeNonNull() + { + var strategy = SchemaFormatStrategyBuilder.Create() + .DeclareArgumentValuesAsNonNull(x => x.ObjectType == typeof(string)) + .Build(); + + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.AddController(); + o.DeclarationOptions.SchemaFormatStrategy = strategy; + }) + .Build(); + + var graphType = server.Schema.KnownTypes.FindGraphType("Query") as IGraphFieldContainer; + var field = graphType.Fields["retrieveRootWidget"]; + var arg1 = field.Arguments["arg1"]; + + Assert.AreEqual("String!", arg1.TypeExpression.ToString()); + Assert.IsTrue(arg1.HasDefaultValue); + Assert.AreEqual("default 1", arg1.DefaultValue.ToString()); + } + } +} \ 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..495e42d4d 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; @@ -86,7 +86,7 @@ public void IsField_ByName_ForInputField() var foundItems = new List(); - foreach (var item in server.Schema.AllSchemaItems()) + foreach (var item in server.Schema.AllSchemaItems().OfType()) { if (item.IsField("Input_TwoPropertyObject", "property1")) foundItems.Add(item); 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..f78759a3d 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,47 +166,51 @@ 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 PathNotFoundGraphActionResult(resolverMetadata, exception); var context = this.CreateResolutionContext(); await actionResult.CompleteAsync(context); 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)); + Assert.AreEqual(Constants.ErrorCodes.INVALID_PATH, context.Messages[0].Code); + + // exception message should have the resolver name in it + Assert.IsTrue(context.Messages[0].Message.Contains(context.ItemPath.Name)); + Assert.IsTrue(context.Messages[0].Exception.Message.Contains(resolverMetadata.InternalName)); + Assert.AreEqual(context.Messages[0].Exception.InnerException, exception); } [Test] public async Task RouteNotFound_ViaMessage_YieldsMessageInResult() { - var actionResult = new RouteNotFoundGraphActionResult("The route was not found"); + var actionResult = new PathNotFoundGraphActionResult("The route was not found"); var context = this.CreateResolutionContext(); await actionResult.CompleteAsync(context); Assert.IsTrue(context.IsCancelled); Assert.AreEqual(1, context.Messages.Count); - Assert.AreEqual(Constants.ErrorCodes.INVALID_ROUTE, context.Messages[0].Code); + Assert.AreEqual(Constants.ErrorCodes.INVALID_PATH, context.Messages[0].Code); Assert.AreEqual("The route was not found", context.Messages[0].Message); } [Test] public async Task RouteNotFound_ViaNoParameters_YieldsDefaultResult() { - var actionResult = new RouteNotFoundGraphActionResult(); + var actionResult = new PathNotFoundGraphActionResult(); var context = this.CreateResolutionContext(); await actionResult.CompleteAsync(context); Assert.IsTrue(context.IsCancelled); Assert.AreEqual(1, context.Messages.Count); - Assert.AreEqual(Constants.ErrorCodes.INVALID_ROUTE, context.Messages[0].Code); + Assert.AreEqual(Constants.ErrorCodes.INVALID_PATH, context.Messages[0].Code); } [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..9ec20f7b7 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); @@ -279,7 +278,7 @@ public async Task NotFoundResult() ""notFoundResult"" ], ""extensions"": { - ""code"": ""INVALID_ROUTE"", + ""code"": ""INVALID_PATH"", ""timestamp"": ""1900-01-01T00:00:00.000+00:00"", ""severity"": ""CRITICAL"" } 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..7a0c80d65 100644 --- a/src/unit-tests/graphql-aspnet-tests/Controllers/SchemaItemPathTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Controllers/SchemaItemPathTests.cs @@ -16,15 +16,15 @@ namespace GraphQL.AspNet.Tests.Controllers [TestFixture] public class SchemaItemPathTests { - [TestCase(SchemaItemCollections.Query, "path1", "path2", "[query]/path1/path2")] - [TestCase(SchemaItemCollections.Types, "path1", "path2", "[type]/path1/path2")] - [TestCase(SchemaItemCollections.Mutation, "path1", "path2", "[mutation]/path1/path2")] - [TestCase(SchemaItemCollections.Subscription, "path1", "path2", "[subscription]/path1/path2")] - [TestCase(SchemaItemCollections.Unknown, "path1", "path2", "[noop]/path1/path2")] - public void Join_WithRoot_JoinsAsExpected(SchemaItemCollections root, string leftSide, string rightSide, string expectedOutput) + [TestCase(ItemPathRoots.Query, "path1", "path2", "[query]/path1/path2")] + [TestCase(ItemPathRoots.Types, "path1", "path2", "[type]/path1/path2")] + [TestCase(ItemPathRoots.Mutation, "path1", "path2", "[mutation]/path1/path2")] + [TestCase(ItemPathRoots.Subscription, "path1", "path2", "[subscription]/path1/path2")] + [TestCase(ItemPathRoots.Unknown, "path1", "path2", "[noop]/path1/path2")] + public void Join_WithRoot_JoinsAsExpected(ItemPathRoots root, string leftSide, string rightSide, string expectedOutput) { // standard join - var fragment = SchemaItemPath.Join(root, leftSide, rightSide); + var fragment = ItemPath.Join(root, leftSide, rightSide); Assert.AreEqual(expectedOutput, fragment); } @@ -32,7 +32,7 @@ public void Join_WithRoot_JoinsAsExpected(SchemaItemCollections root, string lef [TestCase("path1", "path2/path3", "path1/path2/path3")] public void Join_WithNoRoot_JoinsAsExpected(string leftSide, string rightSide, string expectedOutput) { - var fragment = SchemaItemPath.Join(leftSide, rightSide); + var fragment = ItemPath.Join(leftSide, rightSide); Assert.AreEqual(expectedOutput, fragment); } @@ -48,7 +48,7 @@ public void Join_WithNoRoot_JoinsAsExpected(string leftSide, string rightSide, s [TestCase(null, "")] public void NormalizeFragment_CleansUpAsExpected(string input, string expectedOutput) { - var fragment = SchemaItemPath.NormalizeFragment(input); + var fragment = ItemPath.NormalizeFragment(input); Assert.AreEqual(expectedOutput, fragment); } @@ -60,34 +60,34 @@ public void NormalizeFragment_CleansUpAsExpected(string input, string expectedOu [TestCase("path1/path2", false)] public void IsTopLevelField(string input, bool isTopField) { - var fragment = new SchemaItemPath(input); + var fragment = new ItemPath(input); Assert.AreEqual(isTopField, fragment.IsTopLevelField); } [Test] - public void Destructuring_Query_TwoFragmentPathHasADefinedParent() + public void Query_TwoFragmentPathHasADefinedParent() { var fragment = "[query]/path1/path2"; - var route = new SchemaItemPath(fragment); + var itemPath = new ItemPath(fragment); // valid path should be untouched - Assert.IsTrue(route.IsValid); - Assert.AreEqual(fragment, route.Raw); - Assert.AreEqual(fragment, route.Path); - Assert.IsNotNull(route.Parent); - Assert.AreEqual(SchemaItemCollections.Query, route.RootCollection); - Assert.AreEqual("path2", route.Name); - Assert.AreEqual("path1", route.Parent.Name); - Assert.AreEqual("[query]/path1", route.Parent.Path); + Assert.IsTrue(itemPath.IsValid); + Assert.AreEqual(fragment, itemPath.Raw); + Assert.AreEqual(fragment, itemPath.Path); + Assert.IsNotNull(itemPath.Parent); + Assert.AreEqual(ItemPathRoots.Query, itemPath.Root); + Assert.AreEqual("path2", itemPath.Name); + Assert.AreEqual("path1", itemPath.Parent.Name); + Assert.AreEqual("[query]/path1", itemPath.Parent.Path); } [Test] public void GenerateParentPathSegments_LeafPathReturnsParentList() { var fragment = "[query]/path1/path2/path3/path4"; - var route = new SchemaItemPath(fragment); + var itemPath = new ItemPath(fragment); - var parents = route.GenerateParentPathSegments(); + var parents = itemPath.GenerateParentPathSegments(); Assert.IsNotNull(parents); Assert.AreEqual(3, parents.Count); @@ -100,10 +100,10 @@ public void GenerateParentPathSegments_LeafPathReturnsParentList() public void GenerateParentPathSegments_TopLevelFieldReturnsEmptyList() { var fragment = "[query]/path1"; - var route = new SchemaItemPath(fragment); - Assert.IsTrue(route.IsTopLevelField); + var itemPath = new ItemPath(fragment); + Assert.IsTrue(itemPath.IsTopLevelField); - var parents = route.GenerateParentPathSegments(); + var parents = itemPath.GenerateParentPathSegments(); Assert.IsNotNull(parents); Assert.AreEqual(0, parents.Count); } @@ -112,44 +112,44 @@ public void GenerateParentPathSegments_TopLevelFieldReturnsEmptyList() public void GenerateParentPathSegments_InvalidPathSegmentReturnsEmptyList() { var fragment = "pat!$#@%h1"; - var route = new SchemaItemPath(fragment); - Assert.IsFalse(route.IsValid); + var itemPath = new ItemPath(fragment); + Assert.IsFalse(itemPath.IsValid); - var parents = route.GenerateParentPathSegments(); + var parents = itemPath.GenerateParentPathSegments(); Assert.IsNotNull(parents); Assert.AreEqual(0, parents.Count); } - [TestCase("[mutation]/path1/path2", "[mutation]/path1/path2", true, SchemaItemCollections.Mutation, "path2", true, "[mutation]/path1")] - [TestCase("[mutation]/path1///\\path2", "[mutation]/path1/path2", true, SchemaItemCollections.Mutation, "path2", true, "[mutation]/path1")] - [TestCase("[query]/pat$!h1/path2", "", false, SchemaItemCollections.Query, "", false, "")] - [TestCase("/path1/path2", "path1/path2", true, SchemaItemCollections.Unknown, "path2", true, "path1")] + [TestCase("[mutation]/path1/path2", "[mutation]/path1/path2", true, ItemPathRoots.Mutation, "path2", true, "[mutation]/path1")] + [TestCase("[mutation]/path1///\\path2", "[mutation]/path1/path2", true, ItemPathRoots.Mutation, "path2", true, "[mutation]/path1")] + [TestCase("[query]/pat$!h1/path2", "", false, ItemPathRoots.Query, "", false, "")] + [TestCase("/path1/path2", "path1/path2", true, ItemPathRoots.Unknown, "path2", true, "path1")] public void Destructuring( string rawPath, string expectedPath, bool expectedValidState, - SchemaItemCollections expectedRoot, + ItemPathRoots expectedRoot, string expectedName, bool shouldHaveParent, string expectedParentPath) { - var route = new SchemaItemPath(rawPath); + var itemPath = new ItemPath(rawPath); // valid path should be untouched - Assert.AreEqual(expectedValidState, route.IsValid); - Assert.AreEqual(rawPath, route.Raw); - Assert.AreEqual(expectedPath, route.Path); - Assert.AreEqual(expectedRoot, route.RootCollection); - Assert.AreEqual(expectedName, route.Name); + Assert.AreEqual(expectedValidState, itemPath.IsValid); + Assert.AreEqual(rawPath, itemPath.Raw); + Assert.AreEqual(expectedPath, itemPath.Path); + Assert.AreEqual(expectedRoot, itemPath.Root); + Assert.AreEqual(expectedName, itemPath.Name); if (!shouldHaveParent) { - Assert.IsNull(route.Parent); + Assert.IsNull(itemPath.Parent); } else { - Assert.IsNotNull(route.Parent); - Assert.AreEqual(expectedParentPath, route.Parent.Path); + Assert.IsNotNull(itemPath.Parent); + Assert.AreEqual(expectedParentPath, itemPath.Parent.Path); } } @@ -163,13 +163,13 @@ public void Destructuring( [TestCase("[mutation]/path1/path2", "[query]/path1/path2", false)] [TestCase("[query]/path1/path2", "[query]/path1/path2/path3", false)] [TestCase("[query]/path1/path2", "path1/path2", false)] - public void IsSameRoute(string fragment1, string fragment2, bool areTheSame) + public void IsSameitemPath(string fragment1, string fragment2, bool areTheSame) { - var route1 = new SchemaItemPath(fragment1); - var route2 = new SchemaItemPath(fragment2); + var itemPath1 = new ItemPath(fragment1); + var itemPath2 = new ItemPath(fragment2); // valid path should be untouched - Assert.AreEqual(areTheSame, route1.IsSameRoute(route2)); + Assert.AreEqual(areTheSame, itemPath1.IsSamePath(itemPath2)); } [TestCase("[query]/path1/path2", "[query]/path1/path2/path3", true)] @@ -179,28 +179,67 @@ public void IsSameRoute(string fragment1, string fragment2, bool areTheSame) [TestCase("path1/path2", "path1/path2/path3", true)] [TestCase("", "", false)] [TestCase("[query]/path1/path2", "[query]/path1/pathQ/path3", false)] - public void HasChildRoute(string fragment1, string fragment2, bool frag2IsChildof1) + public void HasChilditemPath(string fragment1, string fragment2, bool frag2IsChildof1) { - var route = new SchemaItemPath(fragment1); - var route2 = new SchemaItemPath(fragment2); + var itemPath = new ItemPath(fragment1); + var itemPath2 = new ItemPath(fragment2); - // route2 is a child of route 1, but not the other way around - Assert.AreEqual(frag2IsChildof1, route.HasChildRoute(route2)); + // itemPath2 is a child of itemPath 1, but not the other way around + Assert.AreEqual(frag2IsChildof1, itemPath.HasChildPath(itemPath2)); } [Test] public void FromConstructorParts_YieldsCombinedPaths() { - var route = new SchemaItemPath(SchemaItemCollections.Types, "typeName", "fieldName"); - Assert.AreEqual($"{Constants.Routing.TYPE_ROOT}/typeName/fieldName", route.Path); + var itemPath = new ItemPath(ItemPathRoots.Types, "typeName", "fieldName"); + Assert.AreEqual($"{Constants.Routing.TYPE_ROOT}/typeName/fieldName", itemPath.Path); } [Test] - public void GraphRouteArgumentPath_YieldsAlternatePathString() + public void GraphitemPathArgumentPath_YieldsAlternatePathString() { - var parent = new SchemaItemPath($"{Constants.Routing.TYPE_ROOT}/typeName/fieldName"); - var route = new GraphArgumentFieldPath(parent, "arg1"); - Assert.AreEqual($"{Constants.Routing.TYPE_ROOT}/typeName/fieldName[arg1]", route.Path); + var parent = new ItemPath($"{Constants.Routing.TYPE_ROOT}/typeName/fieldName"); + var itemPath = new GraphArgumentFieldPath(parent, "arg1"); + Assert.AreEqual($"{Constants.Routing.TYPE_ROOT}/typeName/fieldName[arg1]", itemPath.Path); + } + + [TestCase("[query]/path1/path2", ItemPathRoots.Query, "/path1/path2")] + [TestCase("[query]", ItemPathRoots.Query, "/")] + [TestCase("[mutation]/path1/path2", ItemPathRoots.Mutation, "/path1/path2")] + [TestCase("[subscription]/path1/path2", ItemPathRoots.Subscription, "/path1/path2")] + [TestCase("[wrong]/path1/path2", ItemPathRoots.Unknown, "")] + [TestCase("[query]/path1", ItemPathRoots.Query, "/path1")] + [TestCase("[mutation]/path1", ItemPathRoots.Mutation, "/path1")] + [TestCase("[subscription]/path1", ItemPathRoots.Subscription, "/path1")] + [TestCase("[wrong]/path1", ItemPathRoots.Unknown, "")] + public void Destructuring_ToCollectionAndPath( + string input, + ItemPathRoots expectedCollection, + string expectedPath) + { + var itemPath = new ItemPath(input); + var (col, path) = itemPath; + + Assert.AreEqual(expectedCollection, col); + Assert.AreEqual(expectedPath, path); + } + + [Test] + public void TakingParent_OfType_AndMakingitemPath_IsValid() + { + var item = new ItemPath($"{Constants.Routing.TYPE_ROOT}/typeName"); + var newItem = item.Parent.CreateChild("otherType"); + + Assert.AreEqual($"{Constants.Routing.TYPE_ROOT}/otherType", newItem.ToString()); + } + + [Test] + public void TakingParent_OfQuery_AndMakingitemPath_IsValid() + { + var item = new ItemPath($"{Constants.Routing.QUERY_ROOT}/fieldName"); + var newItem = item.Parent.CreateChild("otherField"); + + Assert.AreEqual($"{Constants.Routing.QUERY_ROOT}/otherField", newItem.ToString()); } } } \ 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/DefaultEventLoggerTests.cs b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultEventLoggerTests.cs index 60fc7ed55..6bd2a93c2 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultEventLoggerTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultEventLoggerTests.cs @@ -141,17 +141,17 @@ static DefaultEventLoggerTests() _eventLoggerTestData.Add( new object[] { - (DefaultGraphEventLogger x) => x.SchemaRouteRegistered(null), + (DefaultGraphEventLogger x) => x.SchemaUrlRouteRegistered(null), true, typeof(SchemaRouteRegisteredLogEntry), LogLevel.Debug, - LogEventIds.SchemaRouteRegistered, + LogEventIds.SchemaUrlRouteRegistered, }); _eventLoggerTestData.Add( new object[] { - (DefaultGraphEventLogger x) => x.SchemaRouteRegistered(null), + (DefaultGraphEventLogger x) => x.SchemaUrlRouteRegistered(null), false, null, null, 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..2c16a17ed 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarIncorrectAppliedDirectivesParent.cs +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarIncorrectAppliedDirectivesParent.cs @@ -16,13 +16,15 @@ public class ScalarIncorrectAppliedDirectivesParent : ScalarTestBase { public class SomeItem : ISchemaItem { - public SchemaItemPath Route { get; } + public ItemPath ItemPath { get; } public IAppliedDirectiveCollection AppliedDirectives { get; } 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..68e7e8410 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarTestBase.cs +++ b/src/unit-tests/graphql-aspnet-tests/Engine/DefaultScalarTypeProviderTestData/ScalarTestBase.cs @@ -22,14 +22,13 @@ public abstract class ScalarTestBase : IScalarGraphType { protected ScalarTestBase() { - this.OtherKnownTypes = new TypeCollection(); this.Kind = TypeKind.SCALAR; this.ValueType = ScalarValueType.Number; this.Publish = true; this.IsVirtual = false; this.ObjectType = typeof(ScalarDataType); this.InternalName = "myInternalName"; - this.Route = new SchemaItemPath(AspNet.Execution.SchemaItemCollections.Types, "myScalar"); + this.ItemPath = new ItemPath(AspNet.Execution.ItemPathRoots.Types, "myScalar"); this.Name = "MyScalar"; this.Description = "my description"; this.AppliedDirectives = new AppliedDirectiveCollection(this); @@ -38,7 +37,13 @@ protected ScalarTestBase() this.SourceResolver = Substitute.For(); } - public TypeCollection OtherKnownTypes { get; set; } + public IGraphType Clone(string typeName = null) + { + typeName = typeName ?? this.Name; + var newInstance = GlobalTypes.CreateScalarInstanceOrThrow(this.GetType()) as ScalarTestBase; + newInstance.Name = typeName; + return newInstance; + } public ScalarValueType ValueType { get; set; } @@ -54,7 +59,7 @@ protected ScalarTestBase() public string InternalName { get; set; } - public SchemaItemPath Route { get; set; } + public ItemPath ItemPath { get; set; } public IAppliedDirectiveCollection AppliedDirectives { 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/FieldMaker_StandardFieldTests.cs b/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/FieldMaker_StandardFieldTests.cs deleted file mode 100644 index 579a6441f..000000000 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/FieldMaker_StandardFieldTests.cs +++ /dev/null @@ -1,240 +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.TypeMakers; - using GraphQL.AspNet.Interfaces.Internal; - using GraphQL.AspNet.Internal.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.Framework; - using NSubstitute; - using NUnit.Framework; - - [TestFixture] - public class FieldMaker_StandardFieldTests : GraphTypeMakerTestBase - { - [Test] - public void ActionTemplate_CreateGraphField_WithUnion_UsesUnionNameAsGraphTypeName() - { - var action = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(UnionTestController.TwoTypeUnion)); - var field = this.MakeGraphField(action); - - Assert.IsNotNull(field); - Assert.AreEqual("FragmentData", field.TypeExpression.TypeName); - } - - [Test] - public void ActionTemplate_RetrieveRequiredTypes_WithUnion_ReturnsUnionTypes_NotMethodReturnType() - { - var action = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(UnionTestController.TwoTypeUnion)); - var field = this.MakeGraphField(action); - - var dependentTypes = action.RetrieveRequiredTypes()?.ToList(); - Assert.IsNotNull(dependentTypes); - Assert.AreEqual(2, dependentTypes.Count); - - Assert.IsTrue(dependentTypes.Any(x => x.Type == typeof(UnionDataA) && x.ExpectedKind == TypeKind.OBJECT)); - Assert.IsTrue(dependentTypes.Any(x => x.Type == typeof(UnionDataB) && x.ExpectedKind == TypeKind.OBJECT)); - } - - [Test] - public void Parse_PolicyOnController_IsInheritedByField() - { - var server = new TestServerBuilder().Build(); - var template = GraphQLTemplateHelper.CreateControllerTemplate(); - - // method declares no polciies - // controller declares 1 - var actionMethod = template.Actions.FirstOrDefault(x => x.Name == nameof(SecuredController.DoSomething)); - - 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 group = Enumerable.First(graphField.SecurityGroups); - Assert.AreEqual(template.SecurityPolicies.First(), group.First()); - } - - [Test] - public void Parse_PolicyOnController_AndOnMethod_IsInheritedByField_InCorrectOrder() - { - var server = new TestServerBuilder().Build(); - var template = GraphQLTemplateHelper.CreateControllerTemplate(); - - // controller declares 1 policy - // method declares 1 policy - var actionMethod = template.Actions.FirstOrDefault(x => x.Name == nameof(SecuredController.DoSomethingSecure)); - - Assert.AreEqual(1, template.SecurityPolicies.Count()); - Assert.AreEqual(1, actionMethod.SecurityPolicies.Count()); - - var graphField = new GraphFieldMaker(server.Schema).CreateField(actionMethod).Field; - - Assert.AreEqual(2, Enumerable.Count(graphField.SecurityGroups)); - - // ensure policy order of controller -> method - var controllerTemplateGroup = Enumerable.First(graphField.SecurityGroups); - var fieldTemplateGroup = Enumerable.Skip(graphField.SecurityGroups, 1).First(); - Assert.AreEqual(template.SecurityPolicies.First(), controllerTemplateGroup.First()); - Assert.AreEqual(actionMethod.SecurityPolicies.First(), fieldTemplateGroup.First()); - } - - [Test] - public void Parse_MethodWithNullableEnum_ParsesCorrectly() - { - var server = new TestServerBuilder().Build(); - var template = GraphQLTemplateHelper.CreateControllerTemplate(); - - Assert.AreEqual(1, template.FieldTemplates.Count); - - var field = template.FieldTemplates.FirstOrDefault(); - Assert.AreEqual(nameof(NullableEnumController.ConvertUnit), field.Name); - Assert.AreEqual(typeof(int), field.ObjectType); - - var arg = field.Arguments[0]; - Assert.AreEqual(typeof(NullableEnumController.LengthType), arg.ObjectType); - Assert.AreEqual(NullableEnumController.LengthType.Yards, arg.DefaultValue); - - var graphField = new GraphFieldMaker(server.Schema).CreateField(field).Field; - Assert.IsNotNull(graphField); - - var graphArg = Enumerable.FirstOrDefault(graphField.Arguments); - Assert.IsNotNull(graphArg); - Assert.IsEmpty(graphArg.TypeExpression.Wrappers); - Assert.AreEqual(GraphArgumentModifiers.None, graphArg.ArgumentModifiers); - } - - [Test] - public void PropertyGraphField_DefaultValuesCheck() - { - var server = new TestServerBuilder().Build(); - - var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); - - var parent = obj; - var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.Name)); - var template = new AspNet.Internal.TypeTemplates.PropertyGraphFieldTemplate(parent, propInfo, TypeKind.OBJECT); - template.Parse(); - template.ValidateOrThrow(); - - var field = this.MakeGraphField(template); - Assert.IsNotNull(field); - Assert.AreEqual(Constants.ScalarNames.STRING, field.TypeExpression.TypeName); - Assert.AreEqual(0, field.TypeExpression.Wrappers.Length); - } - - [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"); - - var parent = obj; - var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.Age)); - var template = new AspNet.Internal.TypeTemplates.PropertyGraphFieldTemplate(parent, propInfo, TypeKind.OBJECT); - template.Parse(); - template.ValidateOrThrow(); - - Assert.AreEqual("SuperAge", template.Name); - Assert.AreEqual(typeof(int), template.ObjectType); - - var field = this.MakeGraphField(template); - Assert.IsNotNull(field); - Assert.AreEqual(Constants.ScalarNames.INT, field.TypeExpression.TypeName); - CollectionAssert.AreEqual(new MetaGraphTypes[] { MetaGraphTypes.IsNotNull }, field.TypeExpression.Wrappers); - } - - [Test] - public void PropertyGraphField_DirectivesAreAppliedToCreatedField() - { - var server = new TestServerBuilder().Build(); - var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); - - var parent = obj; - var propInfo = typeof(ObjectDirectiveTestItem).GetProperty(nameof(ObjectDirectiveTestItem.Prop1)); - var template = new AspNet.Internal.TypeTemplates.PropertyGraphFieldTemplate(parent, propInfo, TypeKind.OBJECT); - template.Parse(); - template.ValidateOrThrow(); - - var field = this.MakeGraphField(template); - - Assert.AreEqual(1, field.AppliedDirectives.Count); - Assert.AreEqual(field, field.AppliedDirectives.Parent); - - var appliedDirective = Enumerable.FirstOrDefault(field.AppliedDirectives); - Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); - CollectionAssert.AreEqual(new object[] { 13, "prop field arg" }, appliedDirective.ArgumentValues); - } - - [Test] - public void MethodGraphField_DirectivesAreAppliedToCreatedField() - { - var server = new TestServerBuilder().Build(); - var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); - - var parent = obj; - var methodInfo = typeof(ObjectDirectiveTestItem).GetMethod(nameof(ObjectDirectiveTestItem.Method1)); - var template = new MethodGraphFieldTemplate(parent, methodInfo, TypeKind.OBJECT); - template.Parse(); - template.ValidateOrThrow(); - - var field = this.MakeGraphField(template); - - Assert.AreEqual(1, field.AppliedDirectives.Count); - Assert.AreEqual(field, field.AppliedDirectives.Parent); - - var appliedDirective = Enumerable.FirstOrDefault(field.AppliedDirectives); - Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); - CollectionAssert.AreEqual(new object[] { 14, "method field arg" }, appliedDirective.ArgumentValues); - } - - [Test] - public void Arguments_DirectivesAreApplied() - { - var server = new TestServerBuilder().Build(); - var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); - - var parent = obj; - var methodInfo = typeof(ObjectDirectiveTestItem).GetMethod(nameof(ObjectDirectiveTestItem.MethodWithArgDirectives)); - var template = new MethodGraphFieldTemplate(parent, methodInfo, TypeKind.OBJECT); - template.Parse(); - template.ValidateOrThrow(); - - var field = this.MakeGraphField(template); - - Assert.AreEqual(0, field.AppliedDirectives.Count); - Assert.AreEqual(field, field.AppliedDirectives.Parent); - var arg = field.Arguments["arg1"]; - - Assert.AreEqual(1, arg.AppliedDirectives.Count); - Assert.AreEqual(arg, arg.AppliedDirectives.Parent); - - var appliedDirective = Enumerable.FirstOrDefault(field.Arguments["arg1"].AppliedDirectives); - - Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); - CollectionAssert.AreEqual(new object[] { 15, "arg arg" }, appliedDirective.ArgumentValues); - } - } -} \ 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..d7350dc57 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/ApolloTracingTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/ApolloTracingTests.cs @@ -13,13 +13,14 @@ namespace GraphQL.AspNet.Tests.Execution using System.Linq; using System.Threading.Tasks; using GraphQL.AspNet.Common.Extensions; + using GraphQL.AspNet.Configuration.Formatting; 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; @@ -91,6 +92,11 @@ public async Task StandardExcution_OutputTest() serverBuilder.AddGraphQL(o => { o.ResponseOptions.ExposeMetrics = true; + + // don't apply any formatting to intermediate objects, just name changes + o.DeclarationOptions.SchemaFormatStrategy = SchemaFormatStrategyBuilder + .Create(applyDefaultRules: false) + .Build(); }); var server = serverBuilder.Build(); @@ -178,7 +184,7 @@ public async Task StandardExcution_OutputTest() .Replace("[Property1Offset]", property1.StartOffsetNanoseconds.ToString()) .Replace("[Property1Duration]", property1.DurationNanoSeconds.ToString()); - CommonAssertions.AreEqualJsonStrings(expectedResult, result); + CommonAssertions.AreEqualJsonStrings(expectedResult, result, "Results did not match"); } [Test] @@ -202,6 +208,9 @@ public async Task Tracing_ThroughBatchTypeExtension_WithSingleObjectPerSourceRes serverBuilder.AddGraphQL(o => { o.ResponseOptions.ExposeMetrics = true; + o.DeclarationOptions.SchemaFormatStrategy = SchemaFormatStrategyBuilder + .Create(applyDefaultRules: false) + .Build(); }); var batchService = Substitute.For(); @@ -346,6 +355,9 @@ public async Task Tracing_ThroughBatchTypeExtension_WithMultiObjectPerSourceResu serverBuilder.AddGraphQL(o => { o.ResponseOptions.ExposeMetrics = true; + o.DeclarationOptions.SchemaFormatStrategy = SchemaFormatStrategyBuilder + .Create(applyDefaultRules: false) + .Build(); }); var batchService = Substitute.For(); 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..68eddfc01 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; @@ -22,9 +22,9 @@ public class FieldSourceCollectionTests [Test] public void AllowedFieldIsAdded_CanBeRetrieved() { - var path = new SchemaItemPath(SchemaItemCollections.Subscription, "path1/path2"); + var path = new ItemPath(ItemPathRoots.Subscription, "path1/path2"); var mock = Substitute.For(); - mock.Route.Returns(path); + mock.ItemPath.Returns(path); mock.FieldSource.Returns(GraphFieldSource.Action); var o = new object(); @@ -44,9 +44,9 @@ public void AllowedFieldIsAdded_CanBeRetrieved() [Test] public void UpdatedFieldIsAdded_CanBeRetrieved() { - var path = new SchemaItemPath(SchemaItemCollections.Subscription, "path1/path2"); + var path = new ItemPath(ItemPathRoots.Subscription, "path1/path2"); var mock = Substitute.For(); - mock.Route.Returns(path); + mock.ItemPath.Returns(path); mock.FieldSource.Returns(GraphFieldSource.Action); var o = new object(); @@ -73,9 +73,9 @@ public void UpdatedFieldIsAdded_CanBeRetrieved() [Test] public void DisallowedFieldIsNotAdded_CanNotBeRetrieved() { - var path = new SchemaItemPath(SchemaItemCollections.Subscription, "path1/path2"); + var path = new ItemPath(ItemPathRoots.Subscription, "path1/path2"); var mock = Substitute.For(); - mock.Route.Returns(path); + mock.ItemPath.Returns(path); mock.FieldSource.Returns(GraphFieldSource.Method); var o = new object(); @@ -94,14 +94,14 @@ public void DisallowedFieldIsNotAdded_CanNotBeRetrieved() [Test] public void UnFoundField_IsNotReturned() { - var path = new SchemaItemPath(SchemaItemCollections.Subscription, "path1/path2"); + var path = new ItemPath(ItemPathRoots.Subscription, "path1/path2"); var mock = Substitute.For(); - mock.Route.Returns(path); + mock.ItemPath.Returns(path); mock.FieldSource.Returns(GraphFieldSource.Action); - var path1 = new SchemaItemPath(SchemaItemCollections.Subscription, "path1/path3"); + var path1 = new ItemPath(ItemPathRoots.Subscription, "path1/path3"); var mock1 = Substitute.For(); - mock1.Route.Returns(path1); + mock1.ItemPath.Returns(path1); mock1.FieldSource.Returns(GraphFieldSource.Action); var o = new object(); @@ -119,14 +119,14 @@ public void UnFoundField_IsNotReturned() [Test] public void WhenMultipleAllowedSources_AllCanBeRetrieved() { - var path = new SchemaItemPath(SchemaItemCollections.Subscription, "path1/path2"); + var path = new ItemPath(ItemPathRoots.Subscription, "path1/path2"); var mock = Substitute.For(); - mock.Route.Returns(path); + mock.ItemPath.Returns(path); mock.FieldSource.Returns(GraphFieldSource.Method); - var path1 = new SchemaItemPath(SchemaItemCollections.Subscription, "path1/path3"); + var path1 = new ItemPath(ItemPathRoots.Subscription, "path1/path3"); var mock1 = Substitute.For(); - mock1.Route.Returns(path1); + mock1.ItemPath.Returns(path1); mock1.FieldSource.Returns(GraphFieldSource.Action); var o = new object(); 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 e14f2a47c..f59b4fa42 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/GraphSkipSequencingTests.cs b/src/unit-tests/graphql-aspnet-tests/Execution/GraphSkipSequencingTests.cs index 0e832bf89..57a288a30 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/GraphSkipSequencingTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/GraphSkipSequencingTests.cs @@ -19,8 +19,7 @@ public class GraphSkipSequencingTests [Test] public void SkipFieldWithSameNameAsAnotherField_OBJECT() { - using var restore = new GraphQLGlobalRestorePoint(true); - + // this should not cause an exception to be thrown var server = new TestServerBuilder() .AddGraphQL(o => { @@ -32,8 +31,7 @@ public void SkipFieldWithSameNameAsAnotherField_OBJECT() [Test] public void SkipFieldWithSameNameAsAnotherField_INPUTOBJECT() { - using var restore = new GraphQLGlobalRestorePoint(true); - + // this should not cause an exception to be thrown var server = new TestServerBuilder() .AddGraphQL(o => { 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..6b5ed376b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/IntrospectionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/IntrospectionTests.cs @@ -18,10 +18,11 @@ 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.Execution.TestData.RuntimeFieldTestData; using GraphQL.AspNet.Tests.Framework; - using GraphQL.AspNet.Tests.Framework.CommonHelpers; using NUnit.Framework; [TestFixture] @@ -331,9 +332,9 @@ public void IntrospectedObject_PropertyCheck() Assert.IsNotNull(spected.Fields); Assert.AreEqual(3, spected.Fields.Count); - var expected0 = template.FieldTemplates.First(x => x.Route.Path == $"[type]/{nameof(IntrospectableObject)}/{nameof(IntrospectableObject.Method1)}"); - var expected1 = template.FieldTemplates.First(x => x.Route.Path == $"[type]/{nameof(IntrospectableObject)}/{nameof(IntrospectableObject.Method2)}"); - var expected2 = template.FieldTemplates.First(x => x.Route.Path == $"[type]/{nameof(IntrospectableObject)}/{nameof(IntrospectableObject.Prop1)}"); + var expected0 = template.FieldTemplates.First(x => x.ItemPath.Path == $"[type]/{nameof(IntrospectableObject)}/{nameof(IntrospectableObject.Method1)}"); + var expected1 = template.FieldTemplates.First(x => x.ItemPath.Path == $"[type]/{nameof(IntrospectableObject)}/{nameof(IntrospectableObject.Method2)}"); + var expected2 = template.FieldTemplates.First(x => x.ItemPath.Path == $"[type]/{nameof(IntrospectableObject)}/{nameof(IntrospectableObject.Prop1)}"); var field0 = spected.Fields.FirstOrDefault(x => x.Name == nameof(IntrospectableObject.Method1)); var field1 = spected.Fields.FirstOrDefault(x => x.Name == nameof(IntrospectableObject.Method2)); @@ -1165,7 +1166,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 +1346,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..48c6d4c8c 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; @@ -69,7 +70,7 @@ public async Task SingleField_NoExtras_ValidateFields() // "simple" should contain 1 child field called "simpleQueryMethod" Assert.AreEqual(0, queuedContext.Arguments.Count); Assert.AreEqual(1, queuedContext.ChildContexts.Count); - Assert.IsTrue(queuedContext.Field?.Resolver is GraphControllerRouteFieldResolver); + Assert.IsTrue(queuedContext.Field?.Resolver is GraphControllerVirtualFieldResolver); // simpleQueryMethod should contain 1 property to be resolved var child = queuedContext.ChildContexts[0]; @@ -243,7 +244,7 @@ fragment methodProperties on TwoPropertyObject { // "simple" should contain 1 child field called "simpleQueryMethod" Assert.AreEqual(0, queuedContext.Arguments.Count); Assert.AreEqual(1, queuedContext.ChildContexts.Count); - Assert.IsTrue(queuedContext.Field?.Resolver is GraphControllerRouteFieldResolver); + Assert.IsTrue(queuedContext.Field?.Resolver is GraphControllerVirtualFieldResolver); // simpleQueryMethod should contain 2 properties to be resolved (the two props on the fragment) var child = queuedContext.ChildContexts[0]; 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..9c11876f0 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Execution/RuntimeFieldTests.cs @@ -0,0 +1,741 @@ +// ************************************************************* +// 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.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..9d4656189 --- /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.RuntimeFieldTestData +{ + 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 87% 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 index 14c4257b5..1f3550aa9 100644 --- a/src/unit-tests/graphql-aspnet-tests/Execution/TestData/TestSchemaWithDescription.cs +++ b/src/unit-tests/graphql-aspnet-tests/Execution/TestData/RuntimeFieldTestData/TestSchemaWithDescription.cs @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Execution.TestData +namespace GraphQL.AspNet.Tests.Execution.TestData.RuntimeFieldTestData { using GraphQL.AspNet.Schemas; 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..aa0bb08e2 100644 --- a/src/unit-tests/graphql-aspnet-tests/Logging/GeneralEventLogEntryPropertyChecks.cs +++ b/src/unit-tests/graphql-aspnet-tests/Logging/GeneralEventLogEntryPropertyChecks.cs @@ -116,9 +116,9 @@ public void SchemaRouteRegisteredLogEntry() { var entry = new SchemaRouteRegisteredLogEntry("testRoute"); - Assert.AreEqual(LogEventIds.SchemaRouteRegistered.Id, entry.EventId); + Assert.AreEqual(LogEventIds.SchemaUrlRouteRegistered.Id, entry.EventId); Assert.AreEqual(typeof(GraphSchema).FriendlyName(true), entry.SchemaTypeName); - Assert.AreEqual("testRoute", entry.SchemaRoutePath); + Assert.AreEqual("testRoute", entry.SchemaItemPath); } [Test] @@ -241,7 +241,7 @@ public void FieldResolutionStartedLogEntry() .AddType() .Build(); - var package = server.CreateGraphTypeFieldContextBuilder( + var package = server.CreateFieldContextBuilder( nameof(LogTestController.ExecuteField2)); var resolutionContext = package.CreateResolutionContext(); @@ -250,7 +250,7 @@ public void FieldResolutionStartedLogEntry() Assert.AreEqual(LogEventIds.FieldResolutionStarted.Id, entry.EventId); Assert.AreEqual(resolutionContext.Request.Id.ToString(), entry.PipelineRequestId); Assert.AreEqual(resolutionContext.Request.Field.Mode.ToString(), entry.FieldExecutionMode); - Assert.AreEqual(resolutionContext.Request.Field.Route.Path, entry.FieldPath); + Assert.AreEqual(resolutionContext.Request.Field.ItemPath.Path, entry.FieldPath); Assert.IsNotNull(entry.ToString()); } @@ -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; @@ -271,7 +271,7 @@ public void FieldResolutionCompletedLogEntry() var entry = new FieldResolutionCompletedLogEntry(resolutionContext); Assert.AreEqual(LogEventIds.FieldResolutionCompleted.Id, entry.EventId); Assert.AreEqual(fieldRequest.Id.ToString(), entry.PipelineRequestId); - Assert.AreEqual(fieldRequest.Field.Route.Path, entry.FieldPath); + Assert.AreEqual(fieldRequest.Field.ItemPath.Path, entry.FieldPath); Assert.AreEqual(fieldRequest.Field.TypeExpression.ToString(), entry.TypeExpression); Assert.AreEqual(true, entry.HasData); Assert.AreEqual(true, entry.ResultIsValid); @@ -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; @@ -298,7 +298,7 @@ public void FieldAuthorizationStartedLogEntry() Assert.AreEqual(LogEventIds.SchemaItemAuthorizationStarted.Id, entry.EventId); Assert.AreEqual(fieldRequest.Id.ToString(), entry.PipelineRequestId); - Assert.AreEqual(fieldRequest.Field.Route.Path, entry.SchemaItemPath); + Assert.AreEqual(fieldRequest.Field.ItemPath.Path, entry.SchemaItemPath); Assert.AreEqual(authContext.AuthenticatedUser?.RetrieveUsername(), entry.Username); Assert.IsNotNull(entry.ToString()); } @@ -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(); @@ -323,7 +323,7 @@ public void FieldAuthorizationCompletedLogEntry() Assert.AreEqual(LogEventIds.SchemaItemAuthorizationCompleted.Id, entry.EventId); Assert.AreEqual(fieldRequest.Id.ToString(), entry.PipelineRequestId); - Assert.AreEqual(fieldRequest.Field.Route.Path, entry.SchemaItemPath); + Assert.AreEqual(fieldRequest.Field.ItemPath.Path, entry.SchemaItemPath); Assert.AreEqual(authContext.AuthenticatedUser?.RetrieveUsername(), entry.Username); Assert.AreEqual(authContext.Result.Status.ToString(), entry.AuthorizationStatus); Assert.IsNotNull(entry.ToString()); @@ -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; @@ -350,7 +350,7 @@ public void FieldAuthenticationStartedLogEntry() Assert.AreEqual(LogEventIds.SchemaItemAuthenticationStarted.Id, entry.EventId); Assert.AreEqual(fieldRequest.Id.ToString(), entry.PipelineRequestId); - Assert.AreEqual(fieldRequest.Field.Route.Path, entry.SchemaItemPath); + Assert.AreEqual(fieldRequest.Field.ItemPath.Path, entry.SchemaItemPath); Assert.IsNotNull(entry.ToString()); } @@ -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; @@ -384,7 +384,7 @@ public void FieldAuthenticationCompletedLogEntry() Assert.AreEqual(LogEventIds.SchemaItemAuthenticationCompleted.Id, entry.EventId); Assert.AreEqual(fieldRequest.Id.ToString(), entry.PipelineRequestId); - Assert.AreEqual(fieldRequest.Field.Route.Path, entry.SchemaItemPath); + Assert.AreEqual(fieldRequest.Field.ItemPath.Path, entry.SchemaItemPath); Assert.AreEqual("someOtherUser", entry.Username); // ensure its the user from the authResult Assert.AreEqual("testScheme", entry.AuthenticationScheme); Assert.IsTrue(entry.AuthethenticationSuccess); @@ -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; @@ -511,14 +512,14 @@ public void TypeSystemDirectiveAppliedLogEntry() directive.InternalName.Returns("The Directive Internal"); var item = Substitute.For(); - item.Route.Returns(new AspNet.Schemas.Structural.SchemaItemPath(SchemaItemCollections.Types, "path1")); + item.ItemPath.Returns(new AspNet.Schemas.Structural.ItemPath(ItemPathRoots.Types, "path1")); var entry = new TypeSystemDirectiveAppliedLogEntry(directive, item); Assert.AreEqual(LogEventIds.TypeSystemDirectiveApplied.Id, entry.EventId); Assert.AreEqual(directive.Name, entry.DirectiveName); Assert.AreEqual(directive.InternalName, entry.DirectiveInternalName); - Assert.AreEqual(item.Route.Path, entry.SchemaItemPath); + Assert.AreEqual(item.ItemPath.Path, entry.SchemaItemPath); Assert.AreEqual(typeof(GraphSchema).FriendlyName(true), entry.SchemaTypeName); } @@ -535,7 +536,7 @@ public void ExecutionDirectiveAppliedLogEntry() GraphOperationType.Query, new SourceLocation(999, 33, 5)); - var path = new SchemaItemPath(SchemaItemCollections.Types, "type1"); + var path = new ItemPath(ItemPathRoots.Types, "type1"); var entry = new ExecutionDirectiveAppliedLogEntry(directive, docPart); 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/Middleware/SchemaItemSecurityRequirementsMiddlewareTests.cs b/src/unit-tests/graphql-aspnet-tests/Middleware/SchemaItemSecurityRequirementsMiddlewareTests.cs index ff4818b9d..6a6df66e2 100644 --- a/src/unit-tests/graphql-aspnet-tests/Middleware/SchemaItemSecurityRequirementsMiddlewareTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Middleware/SchemaItemSecurityRequirementsMiddlewareTests.cs @@ -78,7 +78,7 @@ private void SetupFieldMock(List securityGroups) policyGroups = new AppliedSecurityPolicyGroups(securityGroups); field.SecurityGroups.Returns(policyGroups); - field.Route.Returns(new SchemaItemPath(AspNet.Execution.SchemaItemCollections.Query, "some", "path")); + field.ItemPath.Returns(new ItemPath(AspNet.Execution.ItemPathRoots.Query, "some", "path")); _field = field; } 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/DirectiveLocationTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/DirectiveLocationTests.cs index 952555bf9..b755dd68c 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/DirectiveLocationTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/DirectiveLocationTests.cs @@ -19,33 +19,33 @@ namespace GraphQL.AspNet.Tests.Schemas [TestFixture] public class DirectiveLocationTests { - private static List _schemaItemToLocationSource; + public static List _schemaItemToLocationSource; static DirectiveLocationTests() { _schemaItemToLocationSource = new List(); _schemaItemToLocationSource.Add( - new object[] { Substitute.For(), DirectiveLocation.SCHEMA }); + [typeof(ISchema), DirectiveLocation.SCHEMA]); _schemaItemToLocationSource.Add( - new object[] { Substitute.For(), DirectiveLocation.SCALAR }); + [typeof(IScalarGraphType), DirectiveLocation.SCALAR]); _schemaItemToLocationSource.Add( - new object[] { Substitute.For(), DirectiveLocation.OBJECT }); + [typeof(IObjectGraphType), DirectiveLocation.OBJECT]); _schemaItemToLocationSource.Add( - new object[] { Substitute.For(), DirectiveLocation.FIELD_DEFINITION }); + [typeof(IGraphField), DirectiveLocation.FIELD_DEFINITION]); _schemaItemToLocationSource.Add( - new object[] { Substitute.For(), DirectiveLocation.ARGUMENT_DEFINITION }); + [typeof(IGraphArgument), DirectiveLocation.ARGUMENT_DEFINITION]); _schemaItemToLocationSource.Add( - new object[] { Substitute.For(), DirectiveLocation.INTERFACE }); + [typeof(IInterfaceGraphType), DirectiveLocation.INTERFACE]); _schemaItemToLocationSource.Add( - new object[] { Substitute.For(), DirectiveLocation.UNION }); + [typeof(IUnionGraphType), DirectiveLocation.UNION]); _schemaItemToLocationSource.Add( - new object[] { Substitute.For(), DirectiveLocation.ENUM }); + [typeof(IEnumGraphType), DirectiveLocation.ENUM]); _schemaItemToLocationSource.Add( - new object[] { Substitute.For(), DirectiveLocation.ENUM_VALUE }); + [typeof(IEnumValue), DirectiveLocation.ENUM_VALUE]); _schemaItemToLocationSource.Add( - new object[] { Substitute.For(), DirectiveLocation.INPUT_OBJECT }); + [typeof(IInputObjectGraphType), DirectiveLocation.INPUT_OBJECT]); _schemaItemToLocationSource.Add( - new object[] { Substitute.For(), DirectiveLocation.INPUT_FIELD_DEFINITION }); + [typeof(IInputGraphField), DirectiveLocation.INPUT_FIELD_DEFINITION]); } [Test] @@ -122,8 +122,9 @@ public void IsTypeDeclarationLocation(DirectiveLocation location, bool expectedR } [TestCaseSource(nameof(_schemaItemToLocationSource))] - public void AsDirectiveLocation(ISchemaItem testItem, DirectiveLocation expectedLocation) + public void AsDirectiveLocation(Type schemaItemType, DirectiveLocation expectedLocation) { + var testItem = Substitute.For([schemaItemType], []) as ISchemaItem; var result = testItem.AsDirectiveLocation(); Assert.AreEqual(expectedLocation, result); } 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..297c0eacc --- /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.ItemPath.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.ItemPath.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..623323329 --- /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.ItemPath.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.ItemPath.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.ItemPath.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.ItemPath.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..ade2ba98d --- /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.ItemPath.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.ItemPath.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.ItemPath.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..8c619a32a --- /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.ItemPath.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.ItemPath.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.ItemPath.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.ItemPath.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..f46d1fd10 --- /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(ItemPathRoots.Query, field.ItemPath.Root); + 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(ItemPathRoots.Query, field.ItemPath.Root); + + 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.ItemPath.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..4f28f048d --- /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.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.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.ItemPath.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.ItemPath.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.ItemPath.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.ItemPath.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 52% 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..4ba7b1350 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); } @@ -80,22 +86,27 @@ public void CreateGraphType_ParsesAsExpected() var schema = new TestServerBuilder() .AddGraphQL(o => { - o.DeclarationOptions.GraphNamingFormatter = - new GraphNameFormatter(enumValueStrategy: GraphNameFormatStrategy.NoChanges); + o.DeclarationOptions.SchemaFormatStrategy = + SchemaFormatStrategyBuilder + .Create(enumValueNameFormat: TextFormatOptions.NoChanges, applyDefaultRules: false) + .Build(); }) .Build() .Schema; 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); Assert.IsTrue(graphType is EnumGraphType); Assert.AreEqual(4, ((EnumGraphType)graphType).Values.Count); + + foreach (var enumValue in graphType.Values) + Assert.AreEqual(graphType, enumValue.Value.Parent); } [Test] @@ -103,12 +114,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 +132,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 +145,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); @@ -142,11 +157,13 @@ public void DirectivesAreTransferedToGraphType() [TestCase(typeof(EnumWithValueOfFalseKeyword), "FALSE")] public void EnumValueIsKeyword_ButFormattingDoesNotMatchKeyword_WorksAsExpected(Type enumType, string enumValue) { - var schema = new GraphSchema(); + var server = new TestServerBuilder() + .Build(); - var maker = new EnumGraphTypeMaker(schema); + var maker = new EnumGraphTypeMaker(server.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); @@ -159,16 +176,19 @@ public void EnumValueIsKeyword_AndFormattingMatchesKeyword_ThrowsException(Type { var schema = new TestServerBuilder().AddGraphQL(o => { - o.DeclarationOptions.GraphNamingFormatter = new GraphNameFormatter(GraphNameFormatStrategy.LowerCase); + o.DeclarationOptions.SchemaFormatStrategy = SchemaFormatStrategyBuilder + .Create(TextFormatOptions.LowerCase, applyDefaultRules: false) + .Build(); }) .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 +197,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 70% 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..e35a33abb 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("[type]/Input_InputTestObject/NotRequiredValueTypeField", graphField.ItemPath.ToString()); + 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("[type]/Input_InputTestObject/RequiredValueTypeField", graphField.ItemPath.ToString()); + 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("[type]/Input_InputTestObject/NotRequiredReferenceTypeField", graphField.ItemPath.ToString()); + 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("[type]/Input_InputTestObject/RequiredReferenceTypeField", graphField.ItemPath.ToString()); + 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("[type]/Input_InputTestObject/RequiredReferenceExplicitNonNullTypeField", graphField.ItemPath.ToString()); + 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("[type]/Input_InputTestObject/GraphIdRequired", graphField.ItemPath.ToString()); + 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("[type]/Input_InputTestObject/GraphIdNotRequired", graphField.ItemPath.ToString()); + 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("[type]/Input_InputTestObject/GraphIdNullable", graphField.ItemPath.ToString()); + 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/Schemas/Generation/TypeMakers/FieldMaker_StandardFieldTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/FieldMaker_StandardFieldTests.cs new file mode 100644 index 000000000..42900d921 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/FieldMaker_StandardFieldTests.cs @@ -0,0 +1,380 @@ +// ************************************************************* +// 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.Linq; + using GraphQL.AspNet.Configuration; + 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.Structural; + using GraphQL.AspNet.Schemas.TypeSystem; + using GraphQL.AspNet.Security; + 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; + + [TestFixture] + public class FieldMaker_StandardFieldTests : GraphTypeMakerTestBase + { + [Test] + public void ActionTemplate_CreateGraphField_WithUnion_UsesUnionNameAsGraphTypeName() + { + var action = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(UnionTestController.TwoTypeUnion)); + var field = this.MakeGraphField(action); + + Assert.IsNotNull(field); + Assert.AreEqual("FragmentData", field.TypeExpression.TypeName); + } + + [Test] + public void ActionTemplate_RetrieveRequiredTypes_WithUnion_ReturnsUnionTypes_NotMethodReturnType() + { + var action = GraphQLTemplateHelper.CreateActionMethodTemplate(nameof(UnionTestController.TwoTypeUnion)); + var field = this.MakeGraphField(action); + + var dependentTypes = action.RetrieveRequiredTypes()?.ToList(); + Assert.IsNotNull(dependentTypes); + Assert.AreEqual(2, dependentTypes.Count); + + Assert.IsTrue(dependentTypes.Any(x => x.Type == typeof(UnionDataA) && x.ExpectedKind == TypeKind.OBJECT)); + Assert.IsTrue(dependentTypes.Any(x => x.Type == typeof(UnionDataB) && x.ExpectedKind == TypeKind.OBJECT)); + } + + [Test] + public void Parse_PolicyOnController_IsInheritedByField() + { + var server = new TestServerBuilder().Build(); + var template = GraphQLTemplateHelper.CreateControllerTemplate(); + + // method declares no polciies + // controller declares 1 + var actionMethod = template.Actions.FirstOrDefault(x => x.Name == nameof(SecuredController.DoSomething)); + + Assert.AreEqual(1, template.SecurityPolicies.Count()); + Assert.AreEqual(0, actionMethod.SecurityPolicies.Count()); + + var graphField = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)).CreateField(actionMethod).Field; + Assert.AreEqual(1, graphField.SecurityGroups.Count()); + + var group = graphField.SecurityGroups.First(); + Assert.AreEqual(template.SecurityPolicies.First(), group.First()); + } + + [Test] + public void Parse_PolicyOnController_AndOnMethod_IsInheritedByField_InCorrectOrder() + { + var server = new TestServerBuilder().Build(); + var template = GraphQLTemplateHelper.CreateControllerTemplate(); + + // controller declares 1 policy + // method declares 1 policy + var actionMethod = template.Actions.FirstOrDefault(x => x.Name == nameof(SecuredController.DoSomethingSecure)); + + Assert.AreEqual(1, template.SecurityPolicies.Count()); + Assert.AreEqual(1, actionMethod.SecurityPolicies.Count()); + + var graphField = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)).CreateField(actionMethod).Field; + + Assert.AreEqual(2, graphField.SecurityGroups.Count()); + + // ensure policy order of controller -> method + 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()); + } + + [Test] + public void Parse_MethodWithNullableEnum_ParsesCorrectly() + { + var server = new TestServerBuilder().Build(); + var template = GraphQLTemplateHelper.CreateControllerTemplate(); + + Assert.AreEqual(1, template.FieldTemplates.Count); + + var field = template.FieldTemplates.FirstOrDefault(); + Assert.AreEqual(nameof(NullableEnumController.ConvertUnit), field.Name); + Assert.AreEqual(typeof(int), field.ObjectType); + + var arg = field.Arguments[0]; + Assert.AreEqual(typeof(NullableEnumController.LengthType), arg.ObjectType); + Assert.AreEqual(NullableEnumController.LengthType.Yards, arg.DefaultValue); + + var graphField = new GraphFieldMaker(server.Schema, new GraphArgumentMaker(server.Schema)).CreateField(field).Field; + Assert.IsNotNull(graphField); + + var graphArg = graphField.Arguments.FirstOrDefault(); + Assert.IsNotNull(graphArg); + Assert.IsEmpty(graphArg.TypeExpression.Wrappers); + Assert.AreEqual(graphField, graphArg.Parent); + } + + [Test] + public void PropertyGraphField_DefaultValuesCheck() + { + var obj = Substitute.For(); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); + obj.ObjectType.Returns(typeof(SimplePropertyObject)); + + var parent = obj; + var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.Name)); + var template = new PropertyGraphFieldTemplate(parent, propInfo, TypeKind.OBJECT); + template.Parse(); + template.ValidateOrThrow(); + + var field = this.MakeGraphField(template); + + var graphType = Substitute.For(); + graphType.ItemPath.Returns(new ItemPath("[type]/Item0")); + graphType.InternalName.Returns("Item0"); + graphType.ObjectType.Returns(typeof(SimplePropertyObject)); + + field = field.Clone(graphType); + Assert.IsNotNull(field); + Assert.AreEqual(graphType, field.Parent); + Assert.AreEqual(Constants.ScalarNames.STRING, field.TypeExpression.TypeName); + Assert.AreEqual(0, field.TypeExpression.Wrappers.Length); + } + + [Test] + public void PropertyGraphField_GraphName_OverridesDefaultName() + { + var obj = Substitute.For(); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); + obj.ObjectType.Returns(typeof(SimplePropertyObject)); + + var parent = obj; + var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.Age)); + var template = new PropertyGraphFieldTemplate(parent, propInfo, TypeKind.OBJECT); + template.Parse(); + template.ValidateOrThrow(); + + Assert.AreEqual("SuperAge", template.Name); + Assert.AreEqual(typeof(int), template.ObjectType); + + var field = this.MakeGraphField(template); + + var graphType = Substitute.For(); + graphType.ItemPath.Returns(new ItemPath("[type]/Item0")); + graphType.InternalName.Returns("Item0"); + graphType.ObjectType.Returns(typeof(SimplePropertyObject)); + + field = field.Clone(graphType); + Assert.IsNotNull(field); + Assert.AreEqual(Constants.ScalarNames.INT, field.TypeExpression.TypeName); + Assert.AreEqual(graphType, field.Parent); + CollectionAssert.AreEqual(new MetaGraphTypes[] { MetaGraphTypes.IsNotNull }, field.TypeExpression.Wrappers); + } + + [Test] + public void PropertyGraphField_DirectivesAreAppliedToCreatedField() + { + var server = new TestServerBuilder().Build(); + var obj = Substitute.For(); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); + obj.ObjectType.Returns(typeof(SimplePropertyObject)); + + var parent = obj; + var propInfo = typeof(ObjectDirectiveTestItem).GetProperty(nameof(ObjectDirectiveTestItem.Prop1)); + var template = new PropertyGraphFieldTemplate(parent, propInfo, TypeKind.OBJECT); + template.Parse(); + template.ValidateOrThrow(); + + var field = this.MakeGraphField(template); + + Assert.AreEqual(1, field.AppliedDirectives.Count); + Assert.AreEqual(field, field.AppliedDirectives.Parent); + + var appliedDirective = field.AppliedDirectives.FirstOrDefault(); + Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); + CollectionAssert.AreEqual(new object[] { 13, "prop field arg" }, appliedDirective.ArgumentValues); + } + + [Test] + public void MethodGraphField_DirectivesAreAppliedToCreatedField() + { + var server = new TestServerBuilder().Build(); + var obj = Substitute.For(); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); + obj.ObjectType.Returns(typeof(ObjectDirectiveTestItem)); + + var parent = obj; + var methodInfo = typeof(ObjectDirectiveTestItem).GetMethod(nameof(ObjectDirectiveTestItem.Method1)); + var template = new MethodGraphFieldTemplate(parent, methodInfo, TypeKind.OBJECT); + template.Parse(); + template.ValidateOrThrow(); + + var field = this.MakeGraphField(template); + + Assert.AreEqual(1, field.AppliedDirectives.Count); + Assert.AreEqual(field, field.AppliedDirectives.Parent); + + var appliedDirective = field.AppliedDirectives.FirstOrDefault(); + Assert.AreEqual(typeof(DirectiveWithArgs), appliedDirective.DirectiveType); + CollectionAssert.AreEqual(new object[] { 14, "method field arg" }, appliedDirective.ArgumentValues); + } + + [Test] + public void Arguments_DirectivesAreApplied() + { + var server = new TestServerBuilder().Build(); + var obj = Substitute.For(); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); + obj.ObjectType.Returns(typeof(ObjectDirectiveTestItem)); + + var parent = obj; + var methodInfo = typeof(ObjectDirectiveTestItem).GetMethod(nameof(ObjectDirectiveTestItem.MethodWithArgDirectives)); + var template = new MethodGraphFieldTemplate(parent, methodInfo, TypeKind.OBJECT); + template.Parse(); + template.ValidateOrThrow(); + + var field = this.MakeGraphField(template); + + Assert.AreEqual(0, field.AppliedDirectives.Count); + Assert.AreEqual(field, field.AppliedDirectives.Parent); + var arg = field.Arguments["arg1"]; + + Assert.AreEqual(1, arg.AppliedDirectives.Count); + Assert.AreEqual(arg, arg.AppliedDirectives.Parent); + + 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.ItemPath.Returns(new ItemPath("[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.ItemPath.Returns(new ItemPath("[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.ItemPath.Path); + } + + [Test] + public void InternalName_OnMethodField_IsRendered() + { + var server = new TestServerBuilder().Build(); + + var obj = Substitute.For(); + obj.ItemPath.Returns(new ItemPath("[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.ItemPath.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 67% 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..8cc7e99ce 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] @@ -51,30 +53,31 @@ public void InputObject_CreateGraphType_OnlyPropertiesAreRead() var template = GraphQLTemplateHelper.CreateInputObjectTemplate(); var result = this.MakeGraphType(typeof(TypeCreationItem), TypeKind.INPUT_OBJECT); - var objectGraphType = result.GraphType as IInputObjectGraphType; + var inputGraphType = result.GraphType as IInputObjectGraphType; - Assert.IsNotNull(objectGraphType); - Assert.AreEqual($"Input_{nameof(TypeCreationItem)}", objectGraphType.Name); - Assert.AreEqual(template.Description, objectGraphType.Description); - Assert.AreEqual(TypeKind.INPUT_OBJECT, objectGraphType.Kind); + Assert.IsNotNull(inputGraphType); + Assert.AreEqual($"Input_{nameof(TypeCreationItem)}", inputGraphType.Name); + Assert.AreEqual(template.Description, inputGraphType.Description); + Assert.AreEqual(TypeKind.INPUT_OBJECT, inputGraphType.Kind); // Prop1 (there is no __typename on input objects like there is on its object counterpart) - Assert.AreEqual(1, objectGraphType.Fields.Count); + Assert.AreEqual(1, inputGraphType.Fields.Count); // Method1, Method2 should not be parsed on the type at all. - var method1 = objectGraphType.Fields.FirstOrDefault(x => x.Name == nameof(TypeCreationItem.Method1)); + var method1 = inputGraphType.Fields.FirstOrDefault(x => x.Name == nameof(TypeCreationItem.Method1)); Assert.IsNull(method1); - var method2 = objectGraphType.Fields.FirstOrDefault(x => x.Name == nameof(TypeCreationItem.Method2)); + var method2 = inputGraphType.Fields.FirstOrDefault(x => x.Name == nameof(TypeCreationItem.Method2)); Assert.IsNull(method2); - var prop1 = objectGraphType.Fields.FirstOrDefault(x => x.Name == nameof(TypeCreationItem.Prop1)); + var prop1 = inputGraphType.Fields.FirstOrDefault(x => x.Name == nameof(TypeCreationItem.Prop1)); Assert.IsNotNull(prop1); Assert.AreEqual(0, prop1.TypeExpression.Wrappers.Length); Assert.AreEqual(Constants.ScalarNames.STRING, prop1.TypeExpression.TypeName); Assert.IsFalse(prop1.IsRequired); Assert.IsTrue(prop1.HasDefaultValue); Assert.IsNull(prop1.DefaultValue); + Assert.AreEqual(inputGraphType, prop1.Parent); } [Test] @@ -83,45 +86,49 @@ public void InputObject_CreateGraphType_DefaultFieldValuesAreParsed() var template = GraphQLTemplateHelper.CreateInputObjectTemplate(); var result = this.MakeGraphType(typeof(InputTestObjectWithDefaultFieldValues), TypeKind.INPUT_OBJECT); - var objectGraphType = result.GraphType as IInputObjectGraphType; + var inputGraphType = result.GraphType as IInputObjectGraphType; - Assert.IsNotNull(objectGraphType); - Assert.AreEqual($"Input_{nameof(InputTestObjectWithDefaultFieldValues)}", objectGraphType.Name); - Assert.AreEqual(template.Description, objectGraphType.Description); - Assert.AreEqual(TypeKind.INPUT_OBJECT, objectGraphType.Kind); + Assert.IsNotNull(inputGraphType); + Assert.AreEqual($"Input_{nameof(InputTestObjectWithDefaultFieldValues)}", inputGraphType.Name); + Assert.AreEqual(template.Description, inputGraphType.Description); + Assert.AreEqual(TypeKind.INPUT_OBJECT, inputGraphType.Kind); // Prop1-4 (there is no __typename on input objects) - Assert.AreEqual(4, objectGraphType.Fields.Count); + Assert.AreEqual(4, inputGraphType.Fields.Count); - var prop1 = objectGraphType.Fields.FirstOrDefault(x => x.Name == nameof(InputTestObjectWithDefaultFieldValues.Prop1)); + var prop1 = inputGraphType.Fields.FirstOrDefault(x => x.Name == nameof(InputTestObjectWithDefaultFieldValues.Prop1)); Assert.IsNotNull(prop1); Assert.AreEqual("Int!", prop1.TypeExpression.ToString()); Assert.IsFalse(prop1.IsRequired); Assert.IsTrue(prop1.HasDefaultValue); Assert.AreEqual(34, prop1.DefaultValue); + Assert.AreEqual(inputGraphType, prop1.Parent); - var prop2 = objectGraphType.Fields.FirstOrDefault(x => x.Name == nameof(InputTestObjectWithDefaultFieldValues.Prop2)); + var prop2 = inputGraphType.Fields.FirstOrDefault(x => x.Name == nameof(InputTestObjectWithDefaultFieldValues.Prop2)); Assert.IsNotNull(prop2); Assert.AreEqual("String", prop2.TypeExpression.ToString()); Assert.IsFalse(prop2.IsRequired); Assert.IsTrue(prop2.HasDefaultValue); Assert.AreEqual("default prop2 string", prop2.DefaultValue); + Assert.AreEqual(inputGraphType, prop2.Parent); - var prop3 = objectGraphType.Fields.FirstOrDefault(x => x.Name == nameof(InputTestObjectWithDefaultFieldValues.Prop3)); + var prop3 = inputGraphType.Fields.FirstOrDefault(x => x.Name == nameof(InputTestObjectWithDefaultFieldValues.Prop3)); Assert.IsNotNull(prop3); Assert.AreEqual("Int!", prop3.TypeExpression.ToString()); Assert.IsTrue(prop3.IsRequired); Assert.IsFalse(prop3.HasDefaultValue); + Assert.AreEqual(inputGraphType, prop3.Parent); // even though prop3 is an int, defaultValue should still be // null since the field is marked required (has no default value) Assert.IsNull(prop3.DefaultValue); - var prop4 = objectGraphType.Fields.FirstOrDefault(x => x.Name == nameof(InputTestObjectWithDefaultFieldValues.Prop4)); + var prop4 = inputGraphType.Fields.FirstOrDefault(x => x.Name == nameof(InputTestObjectWithDefaultFieldValues.Prop4)); Assert.IsNotNull(prop4); Assert.AreEqual("Input_TwoPropertyObject", prop4.TypeExpression.ToString()); Assert.IsFalse(prop4.IsRequired); Assert.IsTrue(prop4.HasDefaultValue); + Assert.AreEqual(inputGraphType, prop4.Parent); // even though its int, defaultValue should still be // null since the field is marked required @@ -138,8 +145,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 +156,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 +168,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 +180,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 +203,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 +211,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 78% 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..47064a43c 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,22 @@ 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()); + + foreach (var field in graphType.Fields) + Assert.AreEqual(graphType, field.Parent); } [Test] @@ -49,7 +57,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 +73,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 +141,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 81% 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..06d0af54e 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); @@ -60,6 +65,7 @@ public void Interface_CreateGraphType_ParsesCorrectly() var method1 = objectGraphType.Fields.FirstOrDefault(x => x.Name == nameof(TypeCreationItem.Method1)); Assert.IsNotNull(method1); + Assert.AreEqual(objectGraphType, method1.Parent); CollectionAssert.AreEqual(new MetaGraphTypes[] { MetaGraphTypes.IsNotNull }, method1.TypeExpression.Wrappers); // double cant return as null Assert.AreEqual(Constants.ScalarNames.DOUBLE, method1.TypeExpression.TypeName); Assert.AreEqual(3, method1.Arguments.Count); @@ -91,6 +97,7 @@ public void Interface_CreateGraphType_ParsesCorrectly() Assert.IsNotNull(method2); Assert.IsEmpty(method2.TypeExpression.Wrappers); Assert.AreEqual(nameof(TwoPropertyObject), method2.TypeExpression.TypeName); + Assert.AreEqual(objectGraphType, method2.Parent); arg1 = method2.Arguments["arg1"]; arg2 = method2.Arguments["arg2"]; @@ -110,6 +117,7 @@ public void Interface_CreateGraphType_ParsesCorrectly() Assert.IsNotNull(prop1); Assert.IsEmpty(prop1.TypeExpression.Wrappers); Assert.AreEqual(Constants.ScalarNames.STRING, prop1.TypeExpression.TypeName); + Assert.AreEqual(objectGraphType, prop1.Parent); } [Test] @@ -118,8 +126,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 +136,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 +146,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 +156,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 +166,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 +176,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 +186,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 +196,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 +224,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 +289,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..55ce9772e --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/ScalarGraphTypeMakerTests.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.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), TextFormatOptions.UpperCase, "Int")] + [TestCase(typeof(int), TextFormatOptions.LowerCase, "Int")] + [TestCase(typeof(int), TextFormatOptions.ProperCase, "Int")] + [TestCase(typeof(float), TextFormatOptions.UpperCase, "Float")] + [TestCase(typeof(float), TextFormatOptions.LowerCase, "Float")] + [TestCase(typeof(float), TextFormatOptions.ProperCase, "Float")] + [TestCase(typeof(string), TextFormatOptions.UpperCase, "String")] + [TestCase(typeof(string), TextFormatOptions.LowerCase, "String")] + [TestCase(typeof(string), TextFormatOptions.ProperCase, "String")] + [TestCase(typeof(bool), TextFormatOptions.UpperCase, "Boolean")] + [TestCase(typeof(bool), TextFormatOptions.LowerCase, "Boolean")] + [TestCase(typeof(bool), TextFormatOptions.ProperCase, "Boolean")] + [TestCase(typeof(GraphId), TextFormatOptions.UpperCase, "ID")] + [TestCase(typeof(GraphId), TextFormatOptions.LowerCase, "ID")] + [TestCase(typeof(GraphId), TextFormatOptions.ProperCase, "ID")] + + // non-fixed scalars will rename themselves + [TestCase(typeof(decimal), TextFormatOptions.UpperCase, "DECIMAL")] + [TestCase(typeof(decimal), TextFormatOptions.LowerCase, "decimal")] + [TestCase(typeof(decimal), TextFormatOptions.ProperCase, "Decimal")] + [TestCase(typeof(Uri), TextFormatOptions.UpperCase, "URI")] + [TestCase(typeof(Uri), TextFormatOptions.LowerCase, "uri")] + [TestCase(typeof(Uri), TextFormatOptions.ProperCase, "Uri")] + public void BuiltInScalar_ObeysNamingRulesOfConfig(Type builtInScalarType, TextFormatOptions nameFormat, string expectedName) + { + var server = new TestServerBuilder() + .AddGraphQL(o => + { + o.DeclarationOptions.SchemaFormatStrategy = SchemaFormatStrategyBuilder + .Create(nameFormat, applyDefaultRules: false) + .Build(); + }) + .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/Internal/Templating/EnumTestData/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/Internal/Templating/EnumTestData/EnumWithDescriptionOnValues.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/EnumWithDescriptionOnValues.cs index c3edd3e1f..4de779d8d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/EnumTestData/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.Internal.Templating.EnumTestData +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/Engine/TypeMakers/TestData/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/Engine/TypeMakers/TestData/ISimpleInterface.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/ISimpleInterface.cs index c3edf5b92..0a39ea48e 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/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.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/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/Internal/Templating/ActionTestData/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/Internal/Templating/ActionTestData/IUnionTestDataItem.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/IUnionTestDataItem.cs index e53bd61e2..6ac7fdaaa 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/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.Internal.Templating.ActionTestData +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/Engine/TypeMakers/TestData/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/Engine/TypeMakers/TestData/InterfaceWithUndeclaredInterfaceField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/InterfaceWithUndeclaredInterfaceField.cs index 9a044f78d..a9425b8fa 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/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.Engine.TypeMakers.TestData +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/Internal/Templating/ObjectTypeTests/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/Internal/Templating/ObjectTypeTests/OneMarkedProperty.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/OneMarkedProperty.cs index cd01d7bbf..d9834f680 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ObjectTypeTests/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.Internal.Templating.ObjectTypeTests +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/Internal/Templating/ActionTestData/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/Internal/Templating/ActionTestData/UnionTestController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeMakers/TestData/UnionTestController.cs index 3f634e127..08c97963d 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/ActionTestData/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.Internal.Templating.ActionTestData +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 79% 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..32fc65d51 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,15 +30,14 @@ private ControllerActionGraphFieldTemplate CreateActionTemplate where TControllerType : GraphController { var mockController = Substitute.For(); - mockController.InternalFullName.Returns(typeof(TControllerType).Name); - mockController.Route.Returns(new SchemaItemPath("path0")); + mockController.InternalName.Returns(typeof(TControllerType).Name); + mockController.ItemPath.Returns(new ItemPath("path0")); mockController.Name.Returns("path0"); mockController.ObjectType.Returns(typeof(TControllerType)); var methodInfo = typeof(TControllerType).GetMethod(actionName); var action = new ControllerActionGraphFieldTemplate(mockController, methodInfo); action.Parse(); - action.ValidateOrThrow(); return action; } @@ -54,14 +52,14 @@ public void ActionTemplate_Parse_BasicPropertySets() Assert.AreEqual("MethodDescription", action.Description); Assert.AreEqual(typeof(OneMethodController), action.SourceObjectType); 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(ItemPathRoots.Query, action.ItemPath.Root); + Assert.AreEqual("[query]/path0/path1", action.ItemPath.Path); + 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); + Assert.IsFalse(action.ItemPath.IsTopLevelField); Assert.IsFalse(action.IsAsyncField); } @@ -69,19 +67,21 @@ 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(ItemPathRoots.Query, action.ItemPath.Root); Assert.AreEqual(0, action.Arguments.Count); Assert.IsFalse(action.IsAsyncField); - Assert.AreEqual("[query]/path22", action.Route.Path); + Assert.AreEqual("[query]/path22", action.ItemPath.Path); } [Test] public void ActionTemplate_Parse_WithValidDeclaredUnion_ParsesCorrectly() { var action = this.CreateActionTemplate(nameof(UnionTestController.TwoTypeUnion)); + action.ValidateOrThrow(); - Assert.AreEqual(SchemaItemCollections.Query, action.Route.RootCollection); + Assert.AreEqual(ItemPathRoots.Query, action.ItemPath.Root); Assert.IsNotNull(action.UnionProxy); Assert.AreEqual(2, action.UnionProxy.Types.Count); Assert.AreEqual(action.ObjectType, typeof(object)); @@ -96,8 +96,9 @@ 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.AreEqual(ItemPathRoots.Query, action.ItemPath.Root); Assert.IsNotNull(action.UnionProxy); Assert.AreEqual(typeof(UnionTestProxy), action.UnionProxy.GetType()); } @@ -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/Engine/TypeMakers/TestData/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/Engine/TypeMakers/TestData/IUnionTestDataItem.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/IUnionTestDataItem.cs index d12e499a7..8a35eda2b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/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.Engine.TypeMakers.TestData +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/Engine/TypeMakers/TestData/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/Engine/TypeMakers/TestData/UnionTestController.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ActionTestData/UnionTestController.cs index 682179986..84fae65f2 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/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.Engine.TypeMakers.TestData +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 72% 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..dabbc7cc5 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,18 @@ 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.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,17 +36,18 @@ 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, + var route = new ItemPath(ItemPath.Join( + ItemPathRoots.Query, nameof(ParameterTestClass), nameof(ParameterTestClass.TestMethod))); - mockMethod.Route.Returns(route); + mockMethod.ItemPath.Returns(route); - var argTemplate = new AspNet.Internal.TypeTemplates.GraphArgumentTemplate(mockMethod, paramInfo); + var argTemplate = new GraphArgumentTemplate(mockMethod, paramInfo); argTemplate.Parse(); argTemplate.ValidateOrThrow(); @@ -58,10 +61,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 +102,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 +163,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 +172,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 +180,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 +189,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 +198,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 +207,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 +216,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 +225,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 +233,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 +246,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 +255,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 +293,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(ParameterModifiers.ExplicitSchemaItem, template.ArgumentModifier); + } + + [Test] + public void FromServiceDeclaration_SetsParamModifierAppropriately() + { + var template = this.ExtractParameterTemplate("justFromServicesDeclaration", out var paramInfo); + Assert.AreEqual(ParameterModifiers.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..e8884dfb9 --- /dev/null +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ControllerTemplateTests.cs @@ -0,0 +1,232 @@ +// ************************************************************* +// 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.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(ItemPath.Empty, template.ItemPath); + } + + [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.ItemPath.Path == $"[mutation]/TwoMethodsDifferentRoots/{nameof(TwoMethodsDifferentRootsController.ActionMethodNoAttributes)}")); + Assert.IsTrue(template.FieldTemplates.Any(x => x.ItemPath.Path == $"[query]/TwoMethodsDifferentRoots/{nameof(TwoMethodsDifferentRootsController.ActionMethodNoAttributes)}")); + Assert.IsTrue(template.FieldTemplates.Any(x => x.ItemPath.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 81% 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 index 7ff8103da..564b9fbb0 100644 --- 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 @@ -7,12 +7,12 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ControllerTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ControllerTestData { using System.Collections.Generic; using GraphQL.AspNet.Attributes; using GraphQL.AspNet.Controllers; - using GraphQL.AspNet.Tests.Internal.Templating.ExtensionMethodTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ExtensionMethodTestData; public class ControllerWithActionAsTypeExtensionForCustomNamedObject : GraphController { 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 70% 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..1d4350ce9 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,9 +30,9 @@ public void SimpleDescriptor_AllDefaults_GeneralPropertyCheck() .GetMethod(nameof(SimpleExecutableDirective.Execute)); var mock = Substitute.For(); - mock.InternalFullName.Returns("Simple"); - var route = new SchemaItemPath(SchemaItemCollections.Directives, "Simple"); - mock.Route.Returns(route); + mock.InternalName.Returns("Simple"); + var route = new ItemPath(ItemPathRoots.Directives, "Simple"); + mock.ItemPath.Returns(route); var template = new GraphDirectiveMethodTemplate(mock, method); template.Parse(); template.ValidateOrThrow(); @@ -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,9 +67,9 @@ public void MethodWithSkipAttached_ThrowsException() .GetMethod(nameof(TestDirectiveMethodTemplateContainer.SkippedMethod)); var mock = Substitute.For(); - mock.InternalFullName.Returns("Simple"); - var route = new SchemaItemPath(SchemaItemCollections.Directives, "Simple"); - mock.Route.Returns(route); + mock.InternalName.Returns("Simple"); + var route = new ItemPath(ItemPathRoots.Directives, "Simple"); + mock.ItemPath.Returns(route); var template = new GraphDirectiveMethodTemplate(mock, method); Assert.Throws(() => @@ -84,9 +86,9 @@ public void InvalidReturnType_ThrowsException() .GetMethod(nameof(TestDirectiveMethodTemplateContainer.IncorrrectReturnType)); var mock = Substitute.For(); - mock.InternalFullName.Returns("Simple"); - var route = new SchemaItemPath(SchemaItemCollections.Directives, "Simple"); - mock.Route.Returns(route); + mock.InternalName.Returns("Simple"); + var route = new ItemPath(ItemPathRoots.Directives, "Simple"); + mock.ItemPath.Returns(route); Assert.Throws(() => { @@ -103,28 +105,9 @@ 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"); - var route = new SchemaItemPath(SchemaItemCollections.Directives, "Simple"); - mock.Route.Returns(route); + mock.InternalName.Returns("Simple"); + var route = new ItemPath(ItemPathRoots.Directives, "Simple"); + mock.ItemPath.Returns(route); Assert.Throws(() => { 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..a46f9ff3a 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("[directive]/SimpleExecutable", template.ItemPath.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 74% 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..0d668bf80 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,11 +24,11 @@ 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(); - Assert.AreEqual($"{Constants.Routing.ENUM_ROOT}/{nameof(SimpleEnum)}", template.Route.Path); + Assert.AreEqual($"{Constants.Routing.TYPE_ROOT}/{nameof(SimpleEnum)}", template.ItemPath.Path); Assert.AreEqual(nameof(SimpleEnum), template.Name); Assert.AreEqual(null, template.Description); Assert.AreEqual(1, template.Values.Count()); @@ -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/Engine/TypeMakers/TestData/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/Engine/TypeMakers/TestData/EnumWithDescriptionOnValues.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/EnumTestData/EnumWithDescriptionOnValues.cs index 08b50e74c..d1862d987 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/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.Engine.TypeMakers.TestData +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 83% 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 index 5f75fcffd..10e09b265 100644 --- 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 @@ -7,7 +7,7 @@ // License: MIT // ************************************************************* -namespace GraphQL.AspNet.Tests.Internal.Templating.ExtensionMethodTestData +namespace GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ExtensionMethodTestData { using GraphQL.AspNet.Attributes; 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 91% 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..390cd0880 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,15 @@ // 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; public class ExtensionMethodController : GraphController { @@ -91,6 +91,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 80% 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..789f67fd5 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; @@ -25,8 +25,8 @@ public class InputGraphFieldTemplateTests public void Parse_GeneralPropertyCheck() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); obj.Kind.Returns(TypeKind.INPUT_OBJECT); var parent = obj; @@ -36,22 +36,22 @@ public void Parse_GeneralPropertyCheck() template.ValidateOrThrow(); Assert.AreEqual("Name", template.Name); - Assert.AreEqual("[type]/Item0/Name", template.Route.ToString()); + Assert.AreEqual("[type]/Item0/Name", template.ItemPath.ToString()); 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] public void Parse_IsRequired_IsNotSet() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); obj.Kind.Returns(TypeKind.INPUT_OBJECT); var parent = obj; @@ -69,8 +69,8 @@ public void Parse_IsRequired_IsNotSet() public void Parse_IsRequired_IsSet() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); obj.Kind.Returns(TypeKind.INPUT_OBJECT); var parent = obj; @@ -87,8 +87,8 @@ public void Parse_IsRequired_IsSet() public void Parse_NotRequired_NullableType_ButWithExplicitNonNullTypeExpression_IsFlaggedNonNullable() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); obj.Kind.Returns(TypeKind.INPUT_OBJECT); var parent = obj; @@ -98,15 +98,15 @@ 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] public void Parse_InterfaceAsPropertyType_ThrowsException() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); obj.Kind.Returns(TypeKind.INPUT_OBJECT); var parent = obj; @@ -124,8 +124,8 @@ public void Parse_InterfaceAsPropertyType_ThrowsException() public void Parse_TaskAsPropertyType_ThrowsException() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); obj.Kind.Returns(TypeKind.INPUT_OBJECT); var parent = obj; @@ -143,8 +143,8 @@ public void Parse_TaskAsPropertyType_ThrowsException() public void Parse_UnionAsPropertyType_ThrowsException() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); obj.Kind.Returns(TypeKind.INPUT_OBJECT); var parent = obj; @@ -162,8 +162,8 @@ public void Parse_UnionAsPropertyType_ThrowsException() public void Parse_ActionResultAsPropertyType_ThrowsException() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/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 94% 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..58aa583e0 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,18 @@ // 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] @@ -53,13 +54,12 @@ public void Parse_Object_GeneralPropertySettings_SetCorrectly() template.ValidateOrThrow(); Assert.IsNotNull(template); - Assert.AreEqual("[type]/Input_SimpleObjectNoMethods", template.Route.Path); + Assert.AreEqual("[type]/Input_SimpleObjectNoMethods", template.ItemPath.Path); Assert.AreEqual(null, template.Description); Assert.AreEqual(typeof(SimpleObjectNoMethods), template.ObjectType); 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); } @@ -71,7 +71,7 @@ public void Parse_Struct_GeneralPropertySettings_SetCorrectly() template.ValidateOrThrow(); Assert.IsNotNull(template); - Assert.AreEqual("[type]/Input_SimpleStructNoMethods", template.Route.Path); + Assert.AreEqual("[type]/Input_SimpleStructNoMethods", template.ItemPath.Path); Assert.AreEqual(null, template.Description); Assert.AreEqual(typeof(SimpleStructNoMethods), template.ObjectType); Assert.AreEqual(0, template.FieldTemplates.Count()); @@ -174,7 +174,7 @@ public void Parse_FromAnEnum_ThrowsException() { Assert.Throws(() => { - var template = new InputObjectGraphTypeTemplate(typeof(SchemaItemCollections)); + var template = new InputObjectGraphTypeTemplate(typeof(ItemPathRoots)); }); } @@ -375,6 +375,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 66% 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..2caeae366 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,16 @@ // 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.Tests.Internal.Templating.InterfaceTestData; + 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.InterfaceTestData; using NUnit.Framework; [TestFixture] @@ -27,10 +30,10 @@ public void Parse_FromInterface_GeneralPropertySettings_SetCorrectly() template.ValidateOrThrow(); Assert.IsNotNull(template); - Assert.AreEqual("[type]/ISimpleInterface", template.Route.Path); + Assert.AreEqual("[type]/ISimpleInterface", template.ItemPath.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 +52,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); } @@ -64,15 +67,15 @@ public void Parse_ImplementedInterfaces_AreCaptured() template.ValidateOrThrow(); Assert.IsNotNull(template); - Assert.AreEqual("[type]/ITestableInterfaceImplementation", template.Route.Path); + Assert.AreEqual("[type]/ITestableInterfaceImplementation", template.ItemPath.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 +90,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 +105,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 84% 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 index c3850f610..2bec0ccd5 100644 --- 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 @@ -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/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/Internal/Templating/InterfaceTestData/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/Internal/Templating/InterfaceTestData/ISimpleInterface.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/ISimpleInterface.cs index 71dcae0fa..f466adc95 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/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.Internal.Templating.InterfaceTestData +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/Internal/Templating/InterfaceTestData/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/Internal/Templating/InterfaceTestData/InterfaceWithUndeclaredInterfaceField.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/InterfaceTestData/InterfaceWithUndeclaredInterfaceField.cs index c8469182d..9dc6db8c5 100644 --- a/src/unit-tests/graphql-aspnet-tests/Internal/Templating/InterfaceTestData/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.Internal.Templating.InterfaceTestData +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 80% 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..f01e7df62 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.ItemPath.Returns(new ItemPath("[type]/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; @@ -43,19 +44,19 @@ private AspNet.Internal.TypeTemplates.MethodGraphFieldTemplate CreateMethodTempl public void DefaultValuesCheck() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/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($"[type]/Item0/{nameof(MethodClass.SimpleMethodNoAttributes)}", template.Route.Path); - Assert.AreEqual(parent.Route.Path, template.Route.Parent.Path); + Assert.AreEqual($"Item0.{nameof(MethodClass.SimpleMethodNoAttributes)}", template.InternalName); + Assert.AreEqual($"[type]/Item0/{nameof(MethodClass.SimpleMethodNoAttributes)}", template.ItemPath.Path); + Assert.AreEqual(parent.ItemPath.Path, template.ItemPath.Parent.Path); Assert.AreEqual(methodInfo, template.Method); Assert.AreEqual(parent, template.Parent); Assert.AreEqual(0, template.TypeExpression.Wrappers.Length); @@ -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() { @@ -164,11 +156,11 @@ public void PropertyAsATaskOfList_SetsReturnType_AsCoreItemNotTheList() public void ArrayFromMethod_YieldsTemplate() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); var expectedTypeExpression = new GraphTypeExpression( - typeof(TwoPropertyObject).FriendlyName(), + Constants.Other.DEFAULT_TYPE_EXPRESSION_TYPE_NAME, MetaGraphTypes.IsList); var parent = obj; @@ -182,8 +174,8 @@ public void ArrayFromMethod_YieldsTemplate() public void Parse_AssignedDirective_IsTemplatized() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); var expectedTypeExpression = new GraphTypeExpression( typeof(TwoPropertyObject).FriendlyName(), @@ -203,8 +195,8 @@ public void Parse_AssignedDirective_IsTemplatized() public void DefaultNonNullableParameter_NotMarkedRequired() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/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.ItemPath.Returns(new ItemPath("[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 81% 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..3ca099e59 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,21 @@ // 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.Internal.Templating.ObjectTypeTests; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.DirectiveTestData; + using GraphQL.AspNet.Tests.Schemas.Generation.TypeTemplates.ObjectTypeTests; using NUnit.Framework; [TestFixture] @@ -47,7 +46,7 @@ public void Parse_Object_GeneralPropertySettings_SetCorrectly() template.ValidateOrThrow(); Assert.IsNotNull(template); - Assert.AreEqual("[type]/SimpleObjectNoMethods", template.Route.Path); + Assert.AreEqual("[type]/SimpleObjectNoMethods", template.ItemPath.Path); Assert.AreEqual(null, template.Description); Assert.AreEqual(typeof(SimpleObjectNoMethods), template.ObjectType); Assert.AreEqual(0, template.FieldTemplates.Count()); @@ -62,7 +61,7 @@ public void Parse_Struct_GeneralPropertySettings_SetCorrectly() template.ValidateOrThrow(); Assert.IsNotNull(template); - Assert.AreEqual("[type]/SimpleStructNoMethods", template.Route.Path); + Assert.AreEqual("[type]/SimpleStructNoMethods", template.ItemPath.Path); Assert.AreEqual(null, template.Description); Assert.AreEqual(typeof(SimpleStructNoMethods), template.ObjectType); Assert.AreEqual(0, template.FieldTemplates.Count()); @@ -158,8 +157,8 @@ public void Parse_Object_OverloadedMethodsWithNoNameClash_ParsesCorrectly() Assert.IsNotNull(template); Assert.AreEqual(2, template.FieldTemplates.Count()); - Assert.IsTrue(template.FieldTemplates.Any(x => x.Route.Path == $"[type]/{nameof(TwoMethodsWithSameNameWithAttributeDiff)}/{nameof(TwoMethodsWithSameNameWithAttributeDiff.Method1)}")); - Assert.IsTrue(template.FieldTemplates.Any(x => x.Route.Path == $"[type]/{nameof(TwoMethodsWithSameNameWithAttributeDiff)}/MethodA")); + Assert.IsTrue(template.FieldTemplates.Any(x => x.ItemPath.Path == $"[type]/{nameof(TwoMethodsWithSameNameWithAttributeDiff)}/{nameof(TwoMethodsWithSameNameWithAttributeDiff.Method1)}")); + Assert.IsTrue(template.FieldTemplates.Any(x => x.ItemPath.Path == $"[type]/{nameof(TwoMethodsWithSameNameWithAttributeDiff)}/MethodA")); } [Test] @@ -172,8 +171,8 @@ public void Parse_Struct_OverloadedMethodsWithNoNameClash_ParsesCorrectly() Assert.IsNotNull(template); Assert.AreEqual(2, template.FieldTemplates.Count()); - Assert.IsTrue(template.FieldTemplates.Any(x => x.Route.Path == $"[type]/{nameof(StructTwoMethodsWithSameNameWithAttributeDiff)}/{nameof(TwoMethodsWithSameNameWithAttributeDiff.Method1)}")); - Assert.IsTrue(template.FieldTemplates.Any(x => x.Route.Path == $"[type]/{nameof(StructTwoMethodsWithSameNameWithAttributeDiff)}/MethodA")); + Assert.IsTrue(template.FieldTemplates.Any(x => x.ItemPath.Path == $"[type]/{nameof(StructTwoMethodsWithSameNameWithAttributeDiff)}/{nameof(TwoMethodsWithSameNameWithAttributeDiff.Method1)}")); + Assert.IsTrue(template.FieldTemplates.Any(x => x.ItemPath.Path == $"[type]/{nameof(StructTwoMethodsWithSameNameWithAttributeDiff)}/MethodA")); } [Test] @@ -181,7 +180,7 @@ public void Parse_FromAnEnum_ThrowsException() { Assert.Throws(() => { - var template = new ObjectGraphTypeTemplate(typeof(SchemaItemCollections)); + var template = new ObjectGraphTypeTemplate(typeof(ItemPathRoots)); }); } @@ -272,7 +271,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 +318,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 +351,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 +363,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.ItemPath.Name == nameof(ObjectWithStatics.InstanceProperty))); + Assert.IsNotNull(template.FieldTemplates.SingleOrDefault(x => x.ItemPath.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 +423,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 85% 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 index 55d51bfed..608076042 100644 --- 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 @@ -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/ObjectRecord.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectRecord.cs similarity index 85% 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 index b68ee914f..6a4c0d1ef 100644 --- 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 @@ -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/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 94% 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 index a19c38eed..8e5de49bf 100644 --- 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 @@ -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/ObjectWithInternalInheritedFields.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/ObjectWithInternalInheritedFields.cs similarity index 77% 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 index 320d14e4f..24f8adc49 100644 --- 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 @@ -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 ObjectWithInternalInheritedFields : ObjectWithInternalFields { } 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/Engine/TypeMakers/TestData/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/Engine/TypeMakers/TestData/OneMarkedProperty.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/Generation/TypeTemplates/ObjectTypeTests/OneMarkedProperty.cs index f0f52b796..76cfa144b 100644 --- a/src/unit-tests/graphql-aspnet-tests/Engine/TypeMakers/TestData/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.Engine.TypeMakers.TestData +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 84% 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 index 08507efa6..a01660465 100644 --- 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 @@ -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/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 71% 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..01d610bda 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; @@ -32,8 +31,8 @@ public class PropertyGraphFieldTemplateTests public void Parse_DescriptionAttribute_SetsValue() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); var parent = obj; var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.Address1)); @@ -48,8 +47,8 @@ public void Parse_DescriptionAttribute_SetsValue() public void Parse_DepreciationAttribute_SetsValues() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); var parent = obj; var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.Address2)); @@ -62,8 +61,8 @@ public void Parse_DepreciationAttribute_SetsValues() public void Parse_PropertyAsAnObject_SetsReturnTypeCorrectly() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); var parent = obj; var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.Hair)); @@ -78,8 +77,8 @@ public void Parse_PropertyAsAnObject_SetsReturnTypeCorrectly() public void Parse_PropertyAsAList_SetsReturnType_AsCoreItemNotTheList() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); var parent = obj; @@ -96,8 +95,8 @@ public void Parse_PropertyAsAList_SetsReturnType_AsCoreItemNotTheList() public void Parse_SecurityPolices_AreAdded() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); var parent = obj; var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.LastName)); @@ -105,15 +104,15 @@ public void Parse_SecurityPolices_AreAdded() template.Parse(); template.ValidateOrThrow(); - Assert.AreEqual(1, template.SecurityPolicies.Count()); + Assert.AreEqual(1, Enumerable.Count(template.SecurityPolicies)); } [Test] public void Parse_InvalidName_ThrowsException() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); var parent = obj; var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.City)); @@ -130,8 +129,8 @@ public void Parse_InvalidName_ThrowsException() public void Parse_SkipDefined_ThrowsException() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); var parent = obj; var propInfo = typeof(SimplePropertyObject).GetProperty(nameof(SimplePropertyObject.State)); @@ -149,8 +148,8 @@ public void Parse_SkipDefined_ThrowsException() public void Parse_BasicObject_PropertyWithNoGetter_ThrowsException() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); var parent = obj; var propInfo = typeof(NoGetterOnProperty).GetProperty(nameof(NoGetterOnProperty.Prop1)); @@ -158,21 +157,23 @@ 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] public void Parse_BasicObject_PropertyReturnsArray_YieldsTemplate() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/Item0")); + obj.InternalName.Returns("Item0"); var expectedTypeExpression = new GraphTypeExpression( - typeof(TwoPropertyObject).FriendlyName(), + Constants.Other.DEFAULT_TYPE_EXPRESSION_TYPE_NAME, MetaGraphTypes.IsList); var parent = obj; @@ -190,11 +191,11 @@ public void Parse_BasicObject_PropertyReturnsArray_YieldsTemplate() public void Parse_BasicObject_PropertyReturnsArrayOfKeyValuePair_YieldsTemplate() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/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 @@ -213,8 +214,8 @@ public void Parse_BasicObject_PropertyReturnsArrayOfKeyValuePair_YieldsTemplate( public void Parse_AssignedDirective_IsTemplatized() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/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); } @@ -238,8 +239,8 @@ public void Parse_AssignedDirective_IsTemplatized() public void InvalidTypeExpression_ThrowsException() { var obj = Substitute.For(); - obj.Route.Returns(new SchemaItemPath("[type]/Item0")); - obj.InternalFullName.Returns("Item0"); + obj.ItemPath.Returns(new ItemPath("[type]/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.ItemPath.Returns(new ItemPath("[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..6c4c8cbed --- /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.ItemPath.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..c8c5be583 --- /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.ItemPath = new ItemPath(ItemPathRoots.Types, 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 ItemPath ItemPath { get; } + + public IAppliedDirectiveCollection AppliedDirectives { get; } + + public string Name { get; } + + public string Description { get; set; } + + public IGraphType Clone(string typeName = null) + { + 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 76% 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..dcbe39765 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,35 @@ // 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.Internal.Templating.ExtensionMethodTestData; + using GraphQL.AspNet.Tests.Common.CommonHelpers; + using GraphQL.AspNet.Tests.Common.Interfaces; + 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.Route.Returns(new SchemaItemPath("path0")); + mockController.InternalName.Returns(typeof(TControllerType).Name); + mockController.ItemPath.Returns(new ItemPath("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(); @@ -49,24 +49,23 @@ public void ClassTypeExtension_PropertyCheck() var template = this.CreateExtensionTemplate(nameof(ExtensionMethodController.ClassTypeExtension)); Assert.AreEqual("ClassTypeExtensionDescription", template.Description); - Assert.AreEqual(SchemaItemCollections.Types, template.Route.RootCollection); + Assert.AreEqual(ItemPathRoots.Types, template.ItemPath.Root); 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($"[type]/{nameof(TwoPropertyObject)}/Property3", template.ItemPath.Path); + 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); Assert.AreEqual(0, template.TypeExpression.Wrappers.Length); - Assert.IsFalse(template.Route.IsTopLevelField); + Assert.IsFalse(template.ItemPath.IsTopLevelField); Assert.IsFalse(template.IsAsyncField); 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(ParameterModifiers.ParentFieldResult)); } [Test] @@ -76,9 +75,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 +85,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(ParameterModifiers.ParentFieldResult)); } [Test] @@ -97,24 +95,23 @@ public void StructTypeExtension_PropertyCheck() var template = this.CreateExtensionTemplate(nameof(ExtensionMethodController.StructTypeExtension)); Assert.AreEqual("StructTypeExtensionDescription", template.Description); - Assert.AreEqual(SchemaItemCollections.Types, template.Route.RootCollection); + Assert.AreEqual(ItemPathRoots.Types, template.ItemPath.Root); 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($"[type]/{nameof(TwoPropertyStruct)}/Property3", template.ItemPath.Path); + 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); Assert.AreEqual(0, template.TypeExpression.Wrappers.Length); - Assert.IsFalse(template.Route.IsTopLevelField); + Assert.IsFalse(template.ItemPath.IsTopLevelField); Assert.IsFalse(template.IsAsyncField); 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(ParameterModifiers.ParentFieldResult)); } [Test] @@ -124,9 +121,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 +131,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(ParameterModifiers.ParentFieldResult)); } [Test] @@ -145,24 +141,23 @@ public void InterfaceTypeExtension_PropertyCheck() var template = this.CreateExtensionTemplate(nameof(ExtensionMethodController.InterfaceTypeExtension)); Assert.AreEqual("InterfaceTypeExtensionDescription", template.Description); - Assert.AreEqual(SchemaItemCollections.Types, template.Route.RootCollection); + Assert.AreEqual(ItemPathRoots.Types, template.ItemPath.Root); 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($"[type]/TwoPropertyInterface/Property3", template.ItemPath.Path); + 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); Assert.AreEqual(0, template.TypeExpression.Wrappers.Length); - Assert.IsFalse(template.Route.IsTopLevelField); + Assert.IsFalse(template.ItemPath.IsTopLevelField); Assert.IsFalse(template.IsAsyncField); 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(ParameterModifiers.ParentFieldResult)); } [Test] @@ -172,9 +167,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 +177,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(ParameterModifiers.ParentFieldResult)); } [Test] @@ -192,7 +186,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,11 +201,11 @@ 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]/TwoPropertyObject/fieldThree", template.Route.ToString()); + Assert.AreEqual("Type", template.TypeExpression.ToString()); + Assert.AreEqual("[type]/TwoPropertyObject/fieldThree", template.ItemPath.ToString()); Assert.AreEqual(typeof(CustomNamedObject), template.ObjectType); Assert.AreEqual(1, template.Arguments.Count); Assert.AreEqual(FieldResolutionMode.Batch, template.Mode); @@ -223,11 +217,11 @@ 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]/Custom_Named_Object/fieldThree", template.Route.ToString()); + Assert.AreEqual("Type", template.TypeExpression.ToString()); + Assert.AreEqual("[type]/Custom_Named_Object/fieldThree", template.ItemPath.ToString()); Assert.AreEqual(typeof(CustomNamedObject), template.ObjectType); Assert.AreEqual(1, template.Arguments.Count); Assert.AreEqual(FieldResolutionMode.Batch, template.Mode); @@ -286,5 +280,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..2ebb1da39 --- /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 ItemPath ItemPath { get; } + + public IAppliedDirectiveCollection AppliedDirectives { get; } + + public string Name { get; } + + public string Description { get; set; } + + public IGraphType Clone(string typeName = null) + { + 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..5e883c816 --- /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 ItemPath ItemPath { get; } + + public IAppliedDirectiveCollection AppliedDirectives { get; } + + public string Name { get; } + + public string Description { get; set; } + + public IGraphType Clone(string typeName) + { + 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..fa83dae4e --- /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 ItemPath ItemPath { get; } + + public IAppliedDirectiveCollection AppliedDirectives { get; } + + public string Name { get; } + + public string Description { get; set; } + + public IGraphType Clone(string typeName = null) + { + 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..f45e0517c --- /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 ItemPath ItemPath { get; } + + public IAppliedDirectiveCollection AppliedDirectives { get; } + + public string Name { get; } + + public string Description { get; set; } + + public IGraphType Clone(string typeName = null) + { + 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..ada06e51a --- /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 ItemPath ItemPath { get; } + + public IAppliedDirectiveCollection AppliedDirectives { get; } + + public string Name { get; } + + public string Description { get; set; } + + public IGraphType Clone(string typeName) + { + 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..e0337bb9b --- /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 ItemPath ItemPath { get; } + + public IAppliedDirectiveCollection AppliedDirectives { get; } + + public string Name { get; } + + public string Description { get; set; } + + 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(); + } + + public IGraphType Clone(string typeName) + { + 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..a2e46f795 --- /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 ItemPath ItemPath { get; } + + public IAppliedDirectiveCollection AppliedDirectives { get; } + + public string Name { get; } + + public string Description { get; set; } + + public IGraphType Clone(string typeName = null) + { + 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..5ea5b7cb7 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/GraphFieldArgumentCloningTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphFieldArgumentCloningTests.cs @@ -26,17 +26,16 @@ public void ClonedArgument_PropertyCheck() directives.Add(new AppliedDirective("directive1", 3)); var parentField = Substitute.For(); - parentField.Route.Returns(new SchemaItemPath("[type]/GraphType1/Field1")); + parentField.ItemPath.Returns(new ItemPath("[type]/GraphType1/Field1")); parentField.Name.Returns("Field1"); var arg = new GraphFieldArgument( parentField, "argName", - GraphTypeExpression.FromDeclaration("String"), - new SchemaItemPath("[type]/GraphType1/Field1/Arg1"), - GraphArgumentModifiers.Internal, - "paramName", "internalName", + "paramName", + GraphTypeExpression.FromDeclaration("String"), + new ItemPath("[type]/GraphType1/Field1/Arg1"), typeof(string), true, "default value", @@ -44,7 +43,7 @@ public void ClonedArgument_PropertyCheck() directives); var newParentField = Substitute.For(); - newParentField.Route.Returns(new SchemaItemPath("[type]/GraphType2/Field1")); + newParentField.ItemPath.Returns(new ItemPath("[type]/GraphType2/Field1")); newParentField.Name.Returns("Field1"); var clonedArg = arg.Clone(newParentField) as GraphFieldArgument; @@ -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..c3c0059b7 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/GraphFieldCloningTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphFieldCloningTests.cs @@ -13,12 +13,12 @@ namespace GraphQL.AspNet.Tests.Schemas using System.Linq; 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; @@ -31,7 +31,7 @@ public class GraphFieldCloningTests public void MethodField_PropertyCheck() { var originalParent = Substitute.For(); - originalParent.Route.Returns(new SchemaItemPath("[type]/JohnType")); + originalParent.ItemPath.Returns(new ItemPath("[type]/JohnType")); originalParent.Name.Returns("JohnType"); var resolver = Substitute.For(); @@ -43,25 +43,25 @@ public void MethodField_PropertyCheck() var field = new MethodGraphField( "field1", + "internalFieldName", GraphTypeExpression.FromDeclaration("[Int]"), - new SchemaItemPath("[type]/JohnType/field1"), - typeof(TwoPropertyObject), + new ItemPath("[type]/JohnType/field1"), typeof(List), + typeof(TwoPropertyObject), AspNet.Execution.FieldResolutionMode.PerSourceItem, resolver, polices, appliedDirectives); - field.AssignParent(originalParent); + field = field.Clone(originalParent) as MethodGraphField; field.Arguments.AddArgument(new GraphFieldArgument( field, "arg1", - GraphTypeExpression.FromDeclaration("String"), - field.Route.CreateChild("arg1"), - GraphArgumentModifiers.None, "arg1", "arg1", + GraphTypeExpression.FromDeclaration("String"), + field.ItemPath.CreateChild("arg1"), typeof(string), false)); @@ -72,7 +72,7 @@ public void MethodField_PropertyCheck() field.FieldSource = GraphFieldSource.Method; var clonedParent = Substitute.For(); - clonedParent.Route.Returns(new SchemaItemPath("[type]/BobType")); + clonedParent.ItemPath.Returns(new ItemPath("[type]/BobType")); clonedParent.Name.Returns("BobType"); var clonedField = field.Clone(clonedParent); @@ -82,96 +82,13 @@ public void MethodField_PropertyCheck() 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("[type]/BobType/field1", clonedField.ItemPath.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/GraphFieldCollectionTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphFieldCollectionTests.cs index 969fb6a42..2e4ef5687 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/GraphFieldCollectionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/GraphFieldCollectionTests.cs @@ -23,9 +23,6 @@ public class GraphFieldCollectionTests [Test] public void AddField_DuplicateFIeldNameThrowsException() { - var owner = Substitute.For(); - owner.Name.Returns("graphType"); - var field1 = Substitute.For(); field1.Name.Returns("field1"); field1.TypeExpression.Returns(GraphTypeExpression.FromDeclaration("Bob!")); @@ -34,7 +31,7 @@ public void AddField_DuplicateFIeldNameThrowsException() field2.Name.Returns("field1"); field2.TypeExpression.Returns(GraphTypeExpression.FromDeclaration("Bob!")); - var collection = new GraphFieldCollection(owner); + var collection = new GraphFieldCollection(); collection.AddField(field1); Assert.Throws(() => @@ -50,14 +47,11 @@ public void AddField_DuplicateFIeldNameThrowsException() [TestCase("FIELD1", false)] // wrong case public void FindField(string fieldName, bool shouldBeFound) { - var owner = Substitute.For(); - owner.Name.Returns("graphType"); - var field1 = Substitute.For(); field1.Name.Returns("field1"); field1.TypeExpression.Returns(GraphTypeExpression.FromDeclaration("Bob!")); - var collection = new GraphFieldCollection(owner); + var collection = new GraphFieldCollection(); collection.AddField(field1); var result = collection.FindField(fieldName); @@ -75,14 +69,11 @@ public void FindField(string fieldName, bool shouldBeFound) [TestCase("FIELD1", false)] // wrong case public void ContainsKey(string fieldName, bool shouldBeFound) { - var owner = Substitute.For(); - owner.Name.Returns("graphType"); - var field1 = Substitute.For(); field1.Name.Returns("field1"); field1.TypeExpression.Returns(GraphTypeExpression.FromDeclaration("Bob!")); - var collection = new GraphFieldCollection(owner); + var collection = new GraphFieldCollection(); collection.AddField(field1); var result = collection.ContainsKey(fieldName); @@ -97,14 +88,11 @@ public void ContainsKey(string fieldName, bool shouldBeFound) [TestCase("FIELD1", false)] // wrong case public void ThisByName(string fieldName, bool shouldBeFound) { - var owner = Substitute.For(); - owner.Name.Returns("graphType"); - var field1 = Substitute.For(); field1.Name.Returns("field1"); field1.TypeExpression.Returns(GraphTypeExpression.FromDeclaration("Bob!")); - var collection = new GraphFieldCollection(owner); + var collection = new GraphFieldCollection(); collection.AddField(field1); if (shouldBeFound) @@ -126,14 +114,11 @@ public void ThisByName(string fieldName, bool shouldBeFound) [Test] public void Contains_ForReferencedField_IsFound() { - var owner = Substitute.For(); - owner.Name.Returns("graphType"); - var field1 = Substitute.For(); field1.Name.Returns("field1"); field1.TypeExpression.Returns(GraphTypeExpression.FromDeclaration("Bob!")); - var collection = new GraphFieldCollection(owner); + var collection = new GraphFieldCollection(); collection.AddField(field1); var result = collection.Contains(field1); @@ -143,9 +128,6 @@ public void Contains_ForReferencedField_IsFound() [Test] public void Contains_ForUnReferencedField_IsNotFound() { - var owner = Substitute.For(); - owner.Name.Returns("graphType"); - var field1 = Substitute.For(); field1.Name.Returns("field1"); field1.TypeExpression.Returns(GraphTypeExpression.FromDeclaration("Bob!")); @@ -154,7 +136,7 @@ public void Contains_ForUnReferencedField_IsNotFound() field1Other.Name.Returns("field1"); field1Other.TypeExpression.Returns(GraphTypeExpression.FromDeclaration("Bob!")); - var collection = new GraphFieldCollection(owner); + var collection = new GraphFieldCollection(); collection.AddField(field1); var result = collection.Contains(field1Other); 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..ff6ef4ee6 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); } @@ -203,7 +205,9 @@ public void AreCompatiable(string targetExpression, string suppliedExpression, b [TestCase(typeof(IEnumerable), "[TwoPropertyObject]", null)] [TestCase(typeof(TwoPropertyObject[]), "[TwoPropertyObject]", null)] [TestCase(typeof(int[][]), "[[Int!]]", null)] + [TestCase(typeof(int?[][]), "[[Int]]", null)] [TestCase(typeof(string[]), "[String]", null)] + [TestCase(typeof(string[]), "[String!]!", new MGT[] { MGT.IsNotNull, MGT.IsList, MGT.IsNotNull })] [TestCase(typeof(KeyValuePair), "KeyValuePair_string_int_!", null)] [TestCase(typeof(KeyValuePair), "KeyValuePair_string___int_____!", null)] [TestCase(typeof(KeyValuePair), "KeyValuePair_string_int___!", null)] @@ -217,10 +221,62 @@ public void AreCompatiable(string targetExpression, string suppliedExpression, b public void GenerateTypeExpression( Type type, string expectedExpression, - MGT[] wrappers) + MGT[] overridingWrappers) { - var typeExpression = GraphTypeExpression.FromType(type, wrappers); + var typeExpression = GraphTypeExpression.FromType(type, overridingWrappers); Assert.AreEqual(expectedExpression, typeExpression.ToString()); } + + [TestCase("Type", "Type", true)] + [TestCase("[Type]", "[Type]", true)] + [TestCase("[[Type]]", "[[Type]]", true)] + [TestCase("Type!", "Type", true)] + [TestCase("[Type!]!", "[Type]", true)] + [TestCase("[[Type!]]!", "[[Type!]!]!", true)] + [TestCase("[[Type!]!]!", "[[Type]]!", true)] + [TestCase("[[Type!]!]!", "[[Type]]", true)] + [TestCase("[[[[[[[[[[Type]]]]]]]]]]", "[[[[[[[[[[Type]]]]]]]]]]", true)] + [TestCase("[[[[[[[[[[Type!]]!]]!]]!]]!]]!", "[[[[[[[[[[Type]]]]]]]]]]", true)] + [TestCase("[[[[[[[[[[Type!]!]!]!]!]!]!]!]!]!]!", "[[[[[[[[[[Type]]!]]]]]]]]!", true)] + [TestCase("[[[[[[[[[[Type]]]]]]]]]]", "[[[[[[[[[Type]]]]]]]]]", false)] + [TestCase("Type", "[[Type]]", false)] + [TestCase("[[[[[[[[[[Type!]]!]]]]]]!]]", "[[[[[[[[[Type!]!]!]!]!]!]!]!]!]!", false)] + [TestCase("Type!", "[[Type]!]!", false)] + public void IsStructuralMatch(string left, string right, bool isMatch) + { + var leftExpression = GraphTypeExpression.FromDeclaration(left); + var rightExpression = GraphTypeExpression.FromDeclaration(right); + + var result = leftExpression.IsStructruallyCompatiable(rightExpression); + Assert.AreEqual(isMatch, result); + + result = rightExpression.IsStructruallyCompatiable(leftExpression); + Assert.AreEqual(isMatch, result); + } + + [TestCase(GraphTypeExpressionNullabilityStrategies.NonNullType, "String", "String!")] + [TestCase(GraphTypeExpressionNullabilityStrategies.NonNullType, "String!", "String!")] + [TestCase(GraphTypeExpressionNullabilityStrategies.NonNullType, "[String]", "[String!]")] + [TestCase(GraphTypeExpressionNullabilityStrategies.NonNullType, "[String!]", "[String!]")] + [TestCase(GraphTypeExpressionNullabilityStrategies.NonNullType, "[[[String]]]", "[[[String!]]]")] + [TestCase(GraphTypeExpressionNullabilityStrategies.NonNullType, "[[[String!]]]", "[[[String!]]]")] + + [TestCase(GraphTypeExpressionNullabilityStrategies.NonNullLists, "String", "String")] + [TestCase(GraphTypeExpressionNullabilityStrategies.NonNullLists, "[String]", "[String]!")] + [TestCase(GraphTypeExpressionNullabilityStrategies.NonNullLists, "[[[String]]]", "[[[String]!]!]!")] + [TestCase(GraphTypeExpressionNullabilityStrategies.NonNullLists, "[[[String!]]]", "[[[String!]!]!]!")] + [TestCase(GraphTypeExpressionNullabilityStrategies.NonNullLists | GraphTypeExpressionNullabilityStrategies.NonNullType, "[[[String]]]", "[[[String!]!]!]!")] + [TestCase(GraphTypeExpressionNullabilityStrategies.NonNullLists | GraphTypeExpressionNullabilityStrategies.NonNullType, "[[[String!]!]!]!", "[[[String!]!]!]!")] + public void CloneTo_NullabilityTests( + GraphTypeExpressionNullabilityStrategies strategy, + string typeExpression, + string expectedOutput) + { + var expression = GraphTypeExpression.FromDeclaration(typeExpression); + + var result = expression.Clone(strategy); + + Assert.AreEqual(expectedOutput, result.ToString()); + } } } \ No newline at end of file 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/Schemas/InputInputGraphFieldCollectionTests.cs b/src/unit-tests/graphql-aspnet-tests/Schemas/InputInputGraphFieldCollectionTests.cs index e31c8b319..bcc2ae198 100644 --- a/src/unit-tests/graphql-aspnet-tests/Schemas/InputInputGraphFieldCollectionTests.cs +++ b/src/unit-tests/graphql-aspnet-tests/Schemas/InputInputGraphFieldCollectionTests.cs @@ -23,9 +23,6 @@ public class InputInputGraphFieldCollectionTests [Test] public void AddField_DuplicateFIeldNameThrowsException() { - var owner = Substitute.For(); - owner.Name.Returns("graphType"); - var field1 = Substitute.For(); field1.Name.Returns("field1"); field1.TypeExpression.Returns(GraphTypeExpression.FromDeclaration("Bob!")); @@ -34,7 +31,7 @@ public void AddField_DuplicateFIeldNameThrowsException() field2.Name.Returns("field1"); field2.TypeExpression.Returns(GraphTypeExpression.FromDeclaration("Bob!")); - var collection = new InputGraphFieldCollection(owner); + var collection = new InputGraphFieldCollection(); collection.AddField(field1); Assert.Throws(() => @@ -50,14 +47,11 @@ public void AddField_DuplicateFIeldNameThrowsException() [TestCase("FIELD1", false)] // wrong case public void FindField(string fieldName, bool shouldBeFound) { - var owner = Substitute.For(); - owner.Name.Returns("graphType"); - var field1 = Substitute.For(); field1.Name.Returns("field1"); field1.TypeExpression.Returns(GraphTypeExpression.FromDeclaration("Bob!")); - var collection = new InputGraphFieldCollection(owner); + var collection = new InputGraphFieldCollection(); collection.AddField(field1); var result = collection.FindField(fieldName); @@ -75,14 +69,11 @@ public void FindField(string fieldName, bool shouldBeFound) [TestCase("FIELD1", false)] // wrong case public void ContainsKey(string fieldName, bool shouldBeFound) { - var owner = Substitute.For(); - owner.Name.Returns("graphType"); - var field1 = Substitute.For(); field1.Name.Returns("field1"); field1.TypeExpression.Returns(GraphTypeExpression.FromDeclaration("Bob!")); - var collection = new InputGraphFieldCollection(owner); + var collection = new InputGraphFieldCollection(); collection.AddField(field1); var result = collection.ContainsKey(fieldName); @@ -97,14 +88,11 @@ public void ContainsKey(string fieldName, bool shouldBeFound) [TestCase("FIELD1", false)] // wrong case public void ThisByName(string fieldName, bool shouldBeFound) { - var owner = Substitute.For(); - owner.Name.Returns("graphType"); - var field1 = Substitute.For(); field1.Name.Returns("field1"); field1.TypeExpression.Returns(GraphTypeExpression.FromDeclaration("Bob!")); - var collection = new InputGraphFieldCollection(owner); + var collection = new InputGraphFieldCollection(); collection.AddField(field1); if (shouldBeFound) @@ -126,14 +114,11 @@ public void ThisByName(string fieldName, bool shouldBeFound) [Test] public void Contains_ForReferencedField_IsFound() { - var owner = Substitute.For(); - owner.Name.Returns("graphType"); - var field1 = Substitute.For(); field1.Name.Returns("field1"); field1.TypeExpression.Returns(GraphTypeExpression.FromDeclaration("Bob!")); - var collection = new InputGraphFieldCollection(owner); + var collection = new InputGraphFieldCollection(); collection.AddField(field1); var result = collection.Contains(field1); @@ -143,9 +128,6 @@ public void Contains_ForReferencedField_IsFound() [Test] public void Contains_ForUnReferencedField_IsNotFound() { - var owner = Substitute.For(); - owner.Name.Returns("graphType"); - var field1 = Substitute.For(); field1.Name.Returns("field1"); field1.TypeExpression.Returns(GraphTypeExpression.FromDeclaration("Bob!")); @@ -154,7 +136,7 @@ public void Contains_ForUnReferencedField_IsNotFound() field1Other.Name.Returns("field1"); field1Other.TypeExpression.Returns(GraphTypeExpression.FromDeclaration("Bob!")); - var collection = new InputGraphFieldCollection(owner); + var collection = new InputGraphFieldCollection(); collection.AddField(field1); var result = collection.Contains(field1Other); 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 95% rename from src/unit-tests/graphql-aspnet-tests/Internal/IntrospectionDefaultValueValidationTests.cs rename to src/unit-tests/graphql-aspnet-tests/Schemas/IntrospectionDefaultValueValidationTests.cs index bf03456e8..d2d141d77 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; @@ -124,7 +124,7 @@ public void InputGraphField_ValidateDefaultValue( var introspectedType = new IntrospectedType(graphType); var inputField = Substitute.For(); - inputField.Route.Returns(new SchemaItemPath(SchemaItemCollections.Types, "inputobject", "field1")); + inputField.ItemPath.Returns(new ItemPath(ItemPathRoots.Types, "inputobject", "field1")); inputField.TypeExpression.Returns(GraphTypeExpression.FromDeclaration(typeExpression)); var introspectedField = new IntrospectedInputValueType(inputField, introspectedType, defaultValue); @@ -164,7 +164,7 @@ public void GraphArgument_ValidateDefaultValue( var introspectedType = new IntrospectedType(graphType); var argument = Substitute.For(); - argument.Route.Returns(new SchemaItemPath(SchemaItemCollections.Types, "inputobject", "field1")); + argument.ItemPath.Returns(new ItemPath(ItemPathRoots.Types, "inputobject", "field1")); argument.DefaultValue.Returns(defaultValue); argument.TypeExpression.Returns(GraphTypeExpression.FromDeclaration(typeExpression)); argument.HasDefaultValue.Returns(hasDefaultValue); @@ -212,7 +212,7 @@ public void InputGraphField_WithNoDefaultValueCOnstructor_ValidateDefaultValue( var introspectedType = new IntrospectedType(graphType); var inputField = Substitute.For(); - inputField.Route.Returns(new SchemaItemPath(SchemaItemCollections.Types, "inputobject", "field1")); + inputField.ItemPath.Returns(new ItemPath(ItemPathRoots.Types, "inputobject", "field1")); inputField.TypeExpression.Returns(GraphTypeExpression.FromDeclaration(typeExpression)); var introspectedField = new IntrospectedInputValueType(inputField, introspectedType); 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 { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy