From 2f3c203c9282de82c5c54f16c5c1c18363188b79 Mon Sep 17 00:00:00 2001 From: Pekka Heikura Date: Tue, 25 Apr 2023 12:50:53 +0300 Subject: [PATCH 1/2] Source generator samples --- ...amples-basic.md => 01-samples-sg-basic.md} | 4 +- .../02-samples-sg-arguments.md | 19 + .../03-samples-sg-namespace.md | 16 + .../04-samples-sg-services.md | 14 + docs/4-code-generator/nav.md | 5 +- docs/4-code-generator/tanka-docs-section.yml | 2 +- .../GraphQL.Samples.Authorization/Program.cs | 2 +- samples/GraphQL.Samples.Http/Program.cs | 2 +- .../GraphQL.Samples.SG.Arguments.csproj} | 0 .../GraphQL.Samples.SG.Arguments/Program.cs | 61 +++ .../Properties/launchSettings.json | 2 +- .../appsettings.Development.json | 0 .../appsettings.json | 0 .../GraphQL.Samples.SG.Basic.csproj | 19 + .../Program.cs | 13 +- .../Properties/launchSettings.json | 14 + .../appsettings.Development.json | 8 + .../GraphQL.Samples.SG.Basic/appsettings.json | 9 + .../GraphQL.Samples.SG.InputType.csproj | 19 + .../GraphQL.Samples.SG.InputType/Program.cs | 93 +++++ .../Properties/launchSettings.json | 14 + .../appsettings.Development.json | 8 + .../appsettings.json | 9 + .../GraphQL.Samples.SG.Namespace.csproj | 19 + .../GraphQL.Samples.SG.Namespace/Program.cs | 60 +++ .../Properties/launchSettings.json | 14 + .../appsettings.Development.json | 8 + .../appsettings.json | 9 + .../GraphQL.Samples.SG.Services.csproj | 19 + .../GraphQL.Samples.SG.Services/Program.cs | 62 +++ .../Properties/launchSettings.json | 14 + .../appsettings.Development.json | 8 + .../appsettings.json | 9 + .../InputTypeDefinition.cs | 46 +++ .../InputTypeEmitter.cs | 80 ++++ .../InputTypeParser.cs | 66 ++++ .../ObjectControllerDefinition.cs | 48 +++ .../ObjectGenerator.cs | 56 ++- .../ObjectMethodDefinition.cs | 16 + .../ObjectPropertyDefinition.cs | 11 + .../ObjectTypeEmitter.cs | 68 +++- .../ObjectTypeParser.cs | 353 +----------------- .../ParameterDefinition.cs | 17 + .../TypeHelper.cs | 239 ++++++++++++ tanka-graphql.sln | 92 ++++- .../InputTypeFacts.cs | 23 ++ .../ObjectGeneratorFacts.cs | 22 ++ ...utType#InputMessageInputType.g.verified.cs | 29 ++ ...Generate_InputType#InputType.g.verified.cs | 12 + ...enerate_InputType#ObjectType.g.verified.cs | 34 ++ ...jectType_type_name#InputType.g.verified.cs | 12 + ...name#TestsControllerExtensions.verified.cs | 19 + ...ace#GlobalControllerExtensions.verified.cs | 18 + ..._name_no_namespace#InputType.g.verified.cs | 12 + ...name_no_namespace#ObjectType.g.verified.cs | 34 ++ ...o_namespace#PersonController.g.verified.cs | 41 ++ ...no_namespace#QueryController.g.verified.cs | 43 +++ ...te_method_resolver#InputType.g.verified.cs | 12 + ...lver#TestsControllerExtensions.verified.cs | 18 + ..._property_resolver#InputType.g.verified.cs | 12 + ...lver#TestsControllerExtensions.verified.cs | 18 + 61 files changed, 1627 insertions(+), 379 deletions(-) rename docs/4-code-generator/{1-samples-basic.md => 01-samples-sg-basic.md} (77%) create mode 100644 docs/4-code-generator/02-samples-sg-arguments.md create mode 100644 docs/4-code-generator/03-samples-sg-namespace.md create mode 100644 docs/4-code-generator/04-samples-sg-services.md rename samples/{GraphQL.Samples.SourceGenerators/GraphQL.Samples.SourceGenerators.csproj => GraphQL.Samples.SG.Arguments/GraphQL.Samples.SG.Arguments.csproj} (100%) create mode 100644 samples/GraphQL.Samples.SG.Arguments/Program.cs rename samples/{GraphQL.Samples.SourceGenerators => GraphQL.Samples.SG.Arguments}/Properties/launchSettings.json (88%) rename samples/{GraphQL.Samples.SourceGenerators => GraphQL.Samples.SG.Arguments}/appsettings.Development.json (100%) rename samples/{GraphQL.Samples.SourceGenerators => GraphQL.Samples.SG.Arguments}/appsettings.json (100%) create mode 100644 samples/GraphQL.Samples.SG.Basic/GraphQL.Samples.SG.Basic.csproj rename samples/{GraphQL.Samples.SourceGenerators => GraphQL.Samples.SG.Basic}/Program.cs (78%) create mode 100644 samples/GraphQL.Samples.SG.Basic/Properties/launchSettings.json create mode 100644 samples/GraphQL.Samples.SG.Basic/appsettings.Development.json create mode 100644 samples/GraphQL.Samples.SG.Basic/appsettings.json create mode 100644 samples/GraphQL.Samples.SG.InputType/GraphQL.Samples.SG.InputType.csproj create mode 100644 samples/GraphQL.Samples.SG.InputType/Program.cs create mode 100644 samples/GraphQL.Samples.SG.InputType/Properties/launchSettings.json create mode 100644 samples/GraphQL.Samples.SG.InputType/appsettings.Development.json create mode 100644 samples/GraphQL.Samples.SG.InputType/appsettings.json create mode 100644 samples/GraphQL.Samples.SG.Namespace/GraphQL.Samples.SG.Namespace.csproj create mode 100644 samples/GraphQL.Samples.SG.Namespace/Program.cs create mode 100644 samples/GraphQL.Samples.SG.Namespace/Properties/launchSettings.json create mode 100644 samples/GraphQL.Samples.SG.Namespace/appsettings.Development.json create mode 100644 samples/GraphQL.Samples.SG.Namespace/appsettings.json create mode 100644 samples/GraphQL.Samples.SG.Services/GraphQL.Samples.SG.Services.csproj create mode 100644 samples/GraphQL.Samples.SG.Services/Program.cs create mode 100644 samples/GraphQL.Samples.SG.Services/Properties/launchSettings.json create mode 100644 samples/GraphQL.Samples.SG.Services/appsettings.Development.json create mode 100644 samples/GraphQL.Samples.SG.Services/appsettings.json create mode 100644 src/GraphQL.Server.SourceGenerators/InputTypeDefinition.cs create mode 100644 src/GraphQL.Server.SourceGenerators/InputTypeEmitter.cs create mode 100644 src/GraphQL.Server.SourceGenerators/InputTypeParser.cs create mode 100644 src/GraphQL.Server.SourceGenerators/ObjectControllerDefinition.cs create mode 100644 src/GraphQL.Server.SourceGenerators/ObjectMethodDefinition.cs create mode 100644 src/GraphQL.Server.SourceGenerators/ObjectPropertyDefinition.cs create mode 100644 src/GraphQL.Server.SourceGenerators/ParameterDefinition.cs create mode 100644 tests/GraphQL.Server.SourceGenerators.Tests/InputTypeFacts.cs create mode 100644 tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#InputMessageInputType.g.verified.cs create mode 100644 tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#InputType.g.verified.cs create mode 100644 tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#ObjectType.g.verified.cs create mode 100644 tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name#InputType.g.verified.cs create mode 100644 tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name#TestsControllerExtensions.verified.cs create mode 100644 tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#GlobalControllerExtensions.verified.cs create mode 100644 tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#InputType.g.verified.cs create mode 100644 tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#ObjectType.g.verified.cs create mode 100644 tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#PersonController.g.verified.cs create mode 100644 tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#QueryController.g.verified.cs create mode 100644 tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_method_resolver#InputType.g.verified.cs create mode 100644 tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_method_resolver#TestsControllerExtensions.verified.cs create mode 100644 tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_property_resolver#InputType.g.verified.cs create mode 100644 tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_property_resolver#TestsControllerExtensions.verified.cs diff --git a/docs/4-code-generator/1-samples-basic.md b/docs/4-code-generator/01-samples-sg-basic.md similarity index 77% rename from docs/4-code-generator/1-samples-basic.md rename to docs/4-code-generator/01-samples-sg-basic.md index d78f03338..1e417ac3b 100644 --- a/docs/4-code-generator/1-samples-basic.md +++ b/docs/4-code-generator/01-samples-sg-basic.md @@ -10,9 +10,9 @@ Install package dotnet add Tanka.GraphQL.Server.SourceGenerators ``` -### Tanka.GraphQL.Samples.SourceGenerators +### Tanka.GraphQL.Samples.SG.Basic ```csharp -#include::xref://samples:GraphQL.Samples.SourceGenerators/Program.cs +#include::xref://samples:GraphQL.Samples.SG.Basic/Program.cs ``` diff --git a/docs/4-code-generator/02-samples-sg-arguments.md b/docs/4-code-generator/02-samples-sg-arguments.md new file mode 100644 index 000000000..76511de08 --- /dev/null +++ b/docs/4-code-generator/02-samples-sg-arguments.md @@ -0,0 +1,19 @@ +## Arguments + +Code generator will extract the arguments from the resolver and generate calls +to bind the arguments from the resolver context. It will also generate the +correct field definition for the field. Arguments can be primitive types or +input object types. + +### Tanka.GraphQL.Samples.SG.Arguments + +```csharp +#include::xref://samples:GraphQL.Samples.SG.Arguments/Program.cs +``` + +### Tanka.GraphQL.Samples.SG.InputType + +```csharp +#include::xref://samples:GraphQL.Samples.SG.InputType/Program.cs +``` + diff --git a/docs/4-code-generator/03-samples-sg-namespace.md b/docs/4-code-generator/03-samples-sg-namespace.md new file mode 100644 index 000000000..e6066c599 --- /dev/null +++ b/docs/4-code-generator/03-samples-sg-namespace.md @@ -0,0 +1,16 @@ +## Namespaces + +When generating code the generator will place the generates +types into same namespace as the annotated classes. + +> WARN: Using of namespaces is supported in C# but the generated +> types can conflict in the SDL when their names are the same. + +Generator will generate a namespace specific add method to add +all types from the namespace to the schema. + +### Tanka.GraphQL.Samples.SG.Namespace + +```csharp +#include::xref://samples:GraphQL.Samples.SG.Namespace/Program.cs +``` diff --git a/docs/4-code-generator/04-samples-sg-services.md b/docs/4-code-generator/04-samples-sg-services.md new file mode 100644 index 000000000..78e5cb2c5 --- /dev/null +++ b/docs/4-code-generator/04-samples-sg-services.md @@ -0,0 +1,14 @@ +## Services + +Generator supports injecting services into the generated resolvers. + +By default the generated code will first check the field arguments for +the argument and then fallback to the DI. It's recommended to use +[FromServices] attribute to make the intent clear and simplify the generated +code. + +### Tanka.GraphQL.Samples.SG.Services + +```csharp +#include::xref://samples:GraphQL.Samples.SG.Services/Program.cs +``` diff --git a/docs/4-code-generator/nav.md b/docs/4-code-generator/nav.md index 8c7dcf5de..68fed406f 100644 --- a/docs/4-code-generator/nav.md +++ b/docs/4-code-generator/nav.md @@ -1 +1,4 @@ -- [Installation](xref://1-samples-basic.md) \ No newline at end of file +- [Installation & basics](xref://01-samples-sg-basic.md) +- [Arguments](xref://02-samples-sg-arguments.md) +- [Namespace](xref://03-samples-sg-namespace.md) +- [Dependency injection](xref://04-samples-sg-services.md) \ No newline at end of file diff --git a/docs/4-code-generator/tanka-docs-section.yml b/docs/4-code-generator/tanka-docs-section.yml index 9f0863f80..1dbf4713e 100644 --- a/docs/4-code-generator/tanka-docs-section.yml +++ b/docs/4-code-generator/tanka-docs-section.yml @@ -1,5 +1,5 @@ id: codegen title: "Code Generation" -index_page: xref://codegen:1-samples-basic.md +index_page: xref://codegen:01-samples-sg-basic.md nav: - xref://nav.md diff --git a/samples/GraphQL.Samples.Authorization/Program.cs b/samples/GraphQL.Samples.Authorization/Program.cs index f3ffde26b..8a31e0754 100644 --- a/samples/GraphQL.Samples.Authorization/Program.cs +++ b/samples/GraphQL.Samples.Authorization/Program.cs @@ -63,7 +63,7 @@ static async IAsyncEnumerable Hello( builder.Services.AddAuthorization(); -WebApplication? app = builder.Build(); +WebApplication app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); diff --git a/samples/GraphQL.Samples.Http/Program.cs b/samples/GraphQL.Samples.Http/Program.cs index f8a84a743..bbac6d43a 100644 --- a/samples/GraphQL.Samples.Http/Program.cs +++ b/samples/GraphQL.Samples.Http/Program.cs @@ -46,7 +46,7 @@ }); }); -WebApplication? app = builder.Build(); +WebApplication app = builder.Build(); // this is required by the websocket transport app.UseWebSockets(); diff --git a/samples/GraphQL.Samples.SourceGenerators/GraphQL.Samples.SourceGenerators.csproj b/samples/GraphQL.Samples.SG.Arguments/GraphQL.Samples.SG.Arguments.csproj similarity index 100% rename from samples/GraphQL.Samples.SourceGenerators/GraphQL.Samples.SourceGenerators.csproj rename to samples/GraphQL.Samples.SG.Arguments/GraphQL.Samples.SG.Arguments.csproj diff --git a/samples/GraphQL.Samples.SG.Arguments/Program.cs b/samples/GraphQL.Samples.SG.Arguments/Program.cs new file mode 100644 index 000000000..b6f5f41e0 --- /dev/null +++ b/samples/GraphQL.Samples.SG.Arguments/Program.cs @@ -0,0 +1,61 @@ +using Tanka.GraphQL.Server; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +// Add tanka graphql +builder.AddTankaGraphQL() + .AddHttp() + .AddWebSockets() + .AddSchemaOptions("Default", options => + { + options.AddGeneratedTypes(types => + { + // add object type controllers from namespace + types.AddGlobalControllers(); + + // Add generated input type to schema + //todo: make a namespace add all method for this + types.AddQueryOptionsInputType(); + }); + }); + +WebApplication app = builder.Build(); +app.UseWebSockets(); + +app.MapTankaGraphQL("/graphql", "Default"); +app.Run(); + + +[ObjectType] +public static class Query +{ + /// + /// Resolver with two primitive arguments + /// + /// Bound automatically from args + /// Bound automatically from args + /// + public static int[] Range(int start, int count) + { + return Enumerable.Range(start, count).ToArray(); + } + + /// + /// Resolver with input object argument + /// + /// todo: bug - requires [FromArguments] in combination with [InputObject] + /// + public static int[] RangeWithInputObject([FromArguments]QueryOptions options) + { + return Enumerable.Range(options.Start, options.Count).ToArray(); + } +} + + +[InputType] +public class QueryOptions +{ + public int Start { get; set; } = 0; + + public int Count { get; set; } = 100; +} \ No newline at end of file diff --git a/samples/GraphQL.Samples.SourceGenerators/Properties/launchSettings.json b/samples/GraphQL.Samples.SG.Arguments/Properties/launchSettings.json similarity index 88% rename from samples/GraphQL.Samples.SourceGenerators/Properties/launchSettings.json rename to samples/GraphQL.Samples.SG.Arguments/Properties/launchSettings.json index 569d06f58..db888ddd2 100644 --- a/samples/GraphQL.Samples.SourceGenerators/Properties/launchSettings.json +++ b/samples/GraphQL.Samples.SG.Arguments/Properties/launchSettings.json @@ -1,6 +1,6 @@ { "profiles": { - "GraphQL.Samples.SourceGenerators": { + "GraphQL.Samples.SG.Arguments": { "commandName": "Project", "launchBrowser": true, "launchUrl": "https://localhost:7239/graphql/ui", diff --git a/samples/GraphQL.Samples.SourceGenerators/appsettings.Development.json b/samples/GraphQL.Samples.SG.Arguments/appsettings.Development.json similarity index 100% rename from samples/GraphQL.Samples.SourceGenerators/appsettings.Development.json rename to samples/GraphQL.Samples.SG.Arguments/appsettings.Development.json diff --git a/samples/GraphQL.Samples.SourceGenerators/appsettings.json b/samples/GraphQL.Samples.SG.Arguments/appsettings.json similarity index 100% rename from samples/GraphQL.Samples.SourceGenerators/appsettings.json rename to samples/GraphQL.Samples.SG.Arguments/appsettings.json diff --git a/samples/GraphQL.Samples.SG.Basic/GraphQL.Samples.SG.Basic.csproj b/samples/GraphQL.Samples.SG.Basic/GraphQL.Samples.SG.Basic.csproj new file mode 100644 index 000000000..c8a66494f --- /dev/null +++ b/samples/GraphQL.Samples.SG.Basic/GraphQL.Samples.SG.Basic.csproj @@ -0,0 +1,19 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + diff --git a/samples/GraphQL.Samples.SourceGenerators/Program.cs b/samples/GraphQL.Samples.SG.Basic/Program.cs similarity index 78% rename from samples/GraphQL.Samples.SourceGenerators/Program.cs rename to samples/GraphQL.Samples.SG.Basic/Program.cs index fbdc78fbf..8fdd6899d 100644 --- a/samples/GraphQL.Samples.SourceGenerators/Program.cs +++ b/samples/GraphQL.Samples.SG.Basic/Program.cs @@ -11,17 +11,14 @@ // type controllers options.AddGeneratedTypes(types => { - // This method is generated by the generator from Query class - types.AddQueryController(); - - // This method is generated by the generator from World class - types.AddWorldController(); - - //todo: generate namespace specific method for adding all types in namespace + // Add generated controllers + types + .AddWorldController() + .AddQueryController(); }); }); -WebApplication? app = builder.Build(); +WebApplication app = builder.Build(); app.UseWebSockets(); app.MapTankaGraphQL("/graphql", "Default"); diff --git a/samples/GraphQL.Samples.SG.Basic/Properties/launchSettings.json b/samples/GraphQL.Samples.SG.Basic/Properties/launchSettings.json new file mode 100644 index 000000000..be7a83b83 --- /dev/null +++ b/samples/GraphQL.Samples.SG.Basic/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "profiles": { + "GraphQL.Samples.SG.Basic": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "https://localhost:7239/graphql/ui", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:7239", + "dotnetRunMessages": true + } + } +} \ No newline at end of file diff --git a/samples/GraphQL.Samples.SG.Basic/appsettings.Development.json b/samples/GraphQL.Samples.SG.Basic/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/samples/GraphQL.Samples.SG.Basic/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/GraphQL.Samples.SG.Basic/appsettings.json b/samples/GraphQL.Samples.SG.Basic/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/samples/GraphQL.Samples.SG.Basic/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/samples/GraphQL.Samples.SG.InputType/GraphQL.Samples.SG.InputType.csproj b/samples/GraphQL.Samples.SG.InputType/GraphQL.Samples.SG.InputType.csproj new file mode 100644 index 000000000..c8a66494f --- /dev/null +++ b/samples/GraphQL.Samples.SG.InputType/GraphQL.Samples.SG.InputType.csproj @@ -0,0 +1,19 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + diff --git a/samples/GraphQL.Samples.SG.InputType/Program.cs b/samples/GraphQL.Samples.SG.InputType/Program.cs new file mode 100644 index 000000000..304adc607 --- /dev/null +++ b/samples/GraphQL.Samples.SG.InputType/Program.cs @@ -0,0 +1,93 @@ +using System.Collections.Concurrent; +using Microsoft.AspNetCore.Mvc; +using Tanka.GraphQL.Server; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +// Simple in memory db for messages +builder.Services.AddSingleton(); + +// Add tanka graphql +builder.AddTankaGraphQL() + .AddHttp() + .AddWebSockets() + .AddSchemaOptions("Default", options => + { + options.AddGeneratedTypes(types => + { + // add object type controllers from namespace + types.AddGlobalControllers(); + + // Add generated input type to schema + //todo: make a namespace add all method for this + types.AddInputMessageInputType(); + }); + }); + +WebApplication app = builder.Build(); +app.UseWebSockets(); + +app.MapTankaGraphQL("/graphql", "Default"); +app.Run(); + + +[ObjectType] +public static class Query +{ + /// + /// Simple query with one dependency resolved from DI + /// + /// + /// [FromServices] is provided by Microsoft.AspNetCore.Mvc + /// + /// + /// + public static Message[] Messages([FromServices]Db db) + { + return db.Messages.ToArray(); + } +} + +[ObjectType] +public static class Mutation +{ + /// + /// Simple mutation with one InputObject argument and one dependency resolved from DI + /// + /// + /// [FromArguments} is provided by Tanka.GraphQL.Server + /// + /// + /// + /// + public static Message Post([FromArguments]InputMessage input, [FromServices]Db db) + { + var message = new Message + { + Id = Guid.NewGuid().ToString(), + Text = input.Text + }; + + db.Messages.Add(message); + return message; + } +} + +[ObjectType] +public class Message +{ + public required string Id { get; set; } + + public required string Text { get; set; } +} + +[InputType] +public class InputMessage +{ + public string Text { get; set; } = string.Empty; +} + +public class Db +{ + public ConcurrentBag Messages { get; } = new(); +} \ No newline at end of file diff --git a/samples/GraphQL.Samples.SG.InputType/Properties/launchSettings.json b/samples/GraphQL.Samples.SG.InputType/Properties/launchSettings.json new file mode 100644 index 000000000..0b850ad09 --- /dev/null +++ b/samples/GraphQL.Samples.SG.InputType/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "profiles": { + "GraphQL.Samples.SG.InputType": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "https://localhost:7239/graphql/ui", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:7239", + "dotnetRunMessages": true + } + } +} \ No newline at end of file diff --git a/samples/GraphQL.Samples.SG.InputType/appsettings.Development.json b/samples/GraphQL.Samples.SG.InputType/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/samples/GraphQL.Samples.SG.InputType/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/GraphQL.Samples.SG.InputType/appsettings.json b/samples/GraphQL.Samples.SG.InputType/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/samples/GraphQL.Samples.SG.InputType/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/samples/GraphQL.Samples.SG.Namespace/GraphQL.Samples.SG.Namespace.csproj b/samples/GraphQL.Samples.SG.Namespace/GraphQL.Samples.SG.Namespace.csproj new file mode 100644 index 000000000..c8a66494f --- /dev/null +++ b/samples/GraphQL.Samples.SG.Namespace/GraphQL.Samples.SG.Namespace.csproj @@ -0,0 +1,19 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + diff --git a/samples/GraphQL.Samples.SG.Namespace/Program.cs b/samples/GraphQL.Samples.SG.Namespace/Program.cs new file mode 100644 index 000000000..441ff2d93 --- /dev/null +++ b/samples/GraphQL.Samples.SG.Namespace/Program.cs @@ -0,0 +1,60 @@ +using Tanka.GraphQL.Samples.SG.Namespace; +using Tanka.GraphQL.Server; + + +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +builder.AddTankaGraphQL() + .AddHttp() + .AddWebSockets() + .AddSchemaOptions("Default", options => + { + // This extension point is used by the generator to add + // type controllers + options.AddGeneratedTypes(types => + { + // Add all controllers in namespace + types.AddTankaGraphQLSamplesSGNamespaceControllers(); + }); + }); + +WebApplication app = builder.Build(); +app.UseWebSockets(); + +app.MapTankaGraphQL("/graphql", "Default"); +app.Run(); + + +namespace Tanka.GraphQL.Samples.SG.Namespace +{ + /// + /// Root query type by naming convention + /// + /// We define it as static class so that the generator does not try + /// to use the initialValue as the source of it. + /// + /// + [ObjectType] + public static class Query + { + public static World World() => new(); + } + + [ObjectType] + public class World + { + /// + /// Simple field with one string argument and string return type + /// + /// name: String! + /// String! + public string Hello(string name) => $"Hello {name}"; + + /// + /// This is the async version of the Hello method + /// + /// + /// + public async Task HelloAsync(string name) => await Task.FromResult($"Hello {name}"); + } +} \ No newline at end of file diff --git a/samples/GraphQL.Samples.SG.Namespace/Properties/launchSettings.json b/samples/GraphQL.Samples.SG.Namespace/Properties/launchSettings.json new file mode 100644 index 000000000..e135e50ee --- /dev/null +++ b/samples/GraphQL.Samples.SG.Namespace/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "profiles": { + "GraphQL.Samples.SG.Namespace": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "https://localhost:7239/graphql/ui", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:7239", + "dotnetRunMessages": true + } + } +} \ No newline at end of file diff --git a/samples/GraphQL.Samples.SG.Namespace/appsettings.Development.json b/samples/GraphQL.Samples.SG.Namespace/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/samples/GraphQL.Samples.SG.Namespace/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/GraphQL.Samples.SG.Namespace/appsettings.json b/samples/GraphQL.Samples.SG.Namespace/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/samples/GraphQL.Samples.SG.Namespace/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/samples/GraphQL.Samples.SG.Services/GraphQL.Samples.SG.Services.csproj b/samples/GraphQL.Samples.SG.Services/GraphQL.Samples.SG.Services.csproj new file mode 100644 index 000000000..c8a66494f --- /dev/null +++ b/samples/GraphQL.Samples.SG.Services/GraphQL.Samples.SG.Services.csproj @@ -0,0 +1,19 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + diff --git a/samples/GraphQL.Samples.SG.Services/Program.cs b/samples/GraphQL.Samples.SG.Services/Program.cs new file mode 100644 index 000000000..4a39d667d --- /dev/null +++ b/samples/GraphQL.Samples.SG.Services/Program.cs @@ -0,0 +1,62 @@ +using Microsoft.AspNetCore.Mvc; +using Tanka.GraphQL.Server; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +// Add HttpContext accessor as service +builder.Services.AddHttpContextAccessor(); + +// Add tanka graphql +builder.AddTankaGraphQL() + .AddHttp() + .AddWebSockets() + .AddSchemaOptions("Default", options => + { + options.AddGeneratedTypes(types => + { + // add object type controllers from namespace + types.AddGlobalControllers(); + }); + }); + +WebApplication app = builder.Build(); +app.UseWebSockets(); + +app.MapTankaGraphQL("/graphql", "Default"); +app.Run(); + + +[ObjectType] +public static class Query +{ + public static HttpContextResponse? HttpContext( + // Use [FromServices] to inject service from DI to the resolver; + // without the attribute the generated code would first check if there's an + // GraphQL argument with the same name and use it instead and if there's + // no argument then it would try to resolve the service from the DI. + // Services can be registered as scoped, singleton or transient. + [FromServices]IHttpContextAccessor httpContextAccessor) + { + if (httpContextAccessor.HttpContext == null) + return null; + + return new HttpContextResponse(httpContextAccessor.HttpContext); + } +} + +[ObjectType] +public class HttpContextResponse +{ + private readonly HttpContext _httpContext; + + public HttpContextResponse(HttpContext httpContext) + { + _httpContext = httpContext; + } + + public string Path => _httpContext.Request.Path; + + public string Method => _httpContext.Request.Method; + + public string Protocol => _httpContext.Request.Protocol; +} \ No newline at end of file diff --git a/samples/GraphQL.Samples.SG.Services/Properties/launchSettings.json b/samples/GraphQL.Samples.SG.Services/Properties/launchSettings.json new file mode 100644 index 000000000..6116d90fa --- /dev/null +++ b/samples/GraphQL.Samples.SG.Services/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "profiles": { + "GraphQL.Samples.SG.Services": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "https://localhost:7239/graphql/ui", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:7239", + "dotnetRunMessages": true + } + } +} \ No newline at end of file diff --git a/samples/GraphQL.Samples.SG.Services/appsettings.Development.json b/samples/GraphQL.Samples.SG.Services/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/samples/GraphQL.Samples.SG.Services/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/samples/GraphQL.Samples.SG.Services/appsettings.json b/samples/GraphQL.Samples.SG.Services/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/samples/GraphQL.Samples.SG.Services/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/GraphQL.Server.SourceGenerators/InputTypeDefinition.cs b/src/GraphQL.Server.SourceGenerators/InputTypeDefinition.cs new file mode 100644 index 000000000..e722476a6 --- /dev/null +++ b/src/GraphQL.Server.SourceGenerators/InputTypeDefinition.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Tanka.GraphQL.Server.SourceGenerators; + +public class InputTypeDefinition: IEquatable +{ + public string? Namespace { get; init; } + + public string TargetType { get; init; } + + public List Properties { get; set; } = new List(); + + public ParentClass? ParentClass { get; set; } + + public bool Equals(InputTypeDefinition? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Namespace == other.Namespace + && TargetType == other.TargetType + && Properties.SequenceEqual(other.Properties) + && Equals(ParentClass, other.ParentClass); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((InputTypeDefinition)obj); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = (Namespace != null ? Namespace.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ TargetType.GetHashCode(); + hashCode = (hashCode * 397) ^ Properties.GetHashCode(); + hashCode = (hashCode * 397) ^ (ParentClass != null ? ParentClass.GetHashCode() : 0); + return hashCode; + } + } +} \ No newline at end of file diff --git a/src/GraphQL.Server.SourceGenerators/InputTypeEmitter.cs b/src/GraphQL.Server.SourceGenerators/InputTypeEmitter.cs new file mode 100644 index 000000000..1302dbebf --- /dev/null +++ b/src/GraphQL.Server.SourceGenerators/InputTypeEmitter.cs @@ -0,0 +1,80 @@ +using Microsoft.CodeAnalysis; +using System.Text; +using System.Text.Json; + +namespace Tanka.GraphQL.Server.SourceGenerators; + +public class InputTypeEmitter +{ + public const string ObjectTypeTemplate = """ + using System; + using System.Threading.Tasks; + using Microsoft.Extensions.Options; + using Tanka.GraphQL.Server; + using Tanka.GraphQL.Executable; + using Tanka.GraphQL.ValueResolution; + using Tanka.GraphQL.Fields; + + {{namespace}} + + public static class {{name}}InputTypeExtensions + { + public static SourceGeneratedTypesBuilder Add{{name}}InputType( + this SourceGeneratedTypesBuilder builder) + { + builder.Builder.Configure(options => options.Builder.Add( + {{typeSDL}} + ) + ); + + return builder; + } + } + """; + + public SourceProductionContext Context { get; } + + public InputTypeEmitter(SourceProductionContext context) + { + Context = context; + } + + public void Emit(InputTypeDefinition definition) + { + var typeSDL = BuildTypeSdl(definition); + + var builder = new StringBuilder(); + string ns = string.IsNullOrEmpty(definition.Namespace) ? "" : $"{definition.Namespace}"; + builder.AppendLine(ObjectTypeTemplate + .Replace("{{namespace}}", string.IsNullOrEmpty(ns) ? "" : $"namespace {ns};") + .Replace("{{name}}", definition.TargetType) + .Replace("{{typeSDL}}", typeSDL) + ); + + Context.AddSource($"{ns}{definition.TargetType}InputType.g.cs", builder.ToString()); + } + + private string BuildTypeSdl(InputTypeDefinition definition) + { + var builder = new IndentedStringBuilder(); + builder.AppendLine("\"\"\""); + builder.IndentCount = 4; + builder.AppendLine($"input {definition.TargetType}"); + + builder.AppendLine("{"); + + using (builder.Indent()) + { + foreach (var field in definition.Properties) + { + var fieldName = JsonNamingPolicy.CamelCase.ConvertName(field.Name); + var fieldType = field.ClosestMatchingGraphQLTypeName; + builder.AppendLine($"{fieldName}: {fieldType}"); + } + } + + builder.AppendLine("}"); + builder.Append("\"\"\""); + return builder.ToString(); + } +} \ No newline at end of file diff --git a/src/GraphQL.Server.SourceGenerators/InputTypeParser.cs b/src/GraphQL.Server.SourceGenerators/InputTypeParser.cs new file mode 100644 index 000000000..dba7b81bd --- /dev/null +++ b/src/GraphQL.Server.SourceGenerators/InputTypeParser.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using CSharpExtensions = Microsoft.CodeAnalysis.CSharpExtensions; + +namespace Tanka.GraphQL.Server.SourceGenerators; + +public class InputTypeParser +{ + public GeneratorAttributeSyntaxContext Context { get; } + + public InputTypeParser(GeneratorAttributeSyntaxContext context) + { + Context = context; + } + + public InputTypeDefinition ParseInputTypeDefinition(ClassDeclarationSyntax classDeclaration) + { + var properties = ParseMembers(classDeclaration); + + return new InputTypeDefinition() + { + Namespace = TypeHelper.GetNamespace(classDeclaration), + TargetType = classDeclaration.Identifier.Text, + Properties = properties, + ParentClass = TypeHelper.GetParentClasses(classDeclaration) + }; + } + + private List ParseMembers(ClassDeclarationSyntax classDeclaration) + { + var properties = new List(); + + foreach (MemberDeclarationSyntax memberDeclarationSyntax in classDeclaration + .Members + .Where(m => CSharpExtensions.Any((SyntaxTokenList)m.Modifiers, SyntaxKind.PublicKeyword))) + { + if (memberDeclarationSyntax.IsKind(SyntaxKind.PropertyDeclaration)) + { + var propertyDeclaration = (PropertyDeclarationSyntax)memberDeclarationSyntax; + var propertyDefinition = new ObjectPropertyDefinition() + { + Name = propertyDeclaration.Identifier.Text, + ReturnType = propertyDeclaration.Type.ToString(), + ClosestMatchingGraphQLTypeName = GetClosestMatchingGraphQLTypeName(TypeHelper.UnwrapTaskType(propertyDeclaration.Type)), + IsNullable = TypeHelper.IsTypeNullable(propertyDeclaration.Type), + }; + properties.Add(propertyDefinition); + } + } + + return properties; + } + + private string GetClosestMatchingGraphQLTypeName(TypeSyntax typeSyntax) + { + var typeSymbol = Context.SemanticModel.GetTypeInfo(typeSyntax).Type; + + if (typeSymbol is null) + return typeSyntax.ToString(); + + return TypeHelper.GetGraphQLTypeName(typeSymbol); + } +} \ No newline at end of file diff --git a/src/GraphQL.Server.SourceGenerators/ObjectControllerDefinition.cs b/src/GraphQL.Server.SourceGenerators/ObjectControllerDefinition.cs new file mode 100644 index 000000000..fe045a773 --- /dev/null +++ b/src/GraphQL.Server.SourceGenerators/ObjectControllerDefinition.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Tanka.GraphQL.Server.SourceGenerators; + +public class ObjectControllerDefinition: IEquatable +{ + public string? Namespace { get; init; } + + public string TargetType { get; init; } + + public List Properties { get; set; } = new List(); + + public List Methods { get; set; } = new List(); + + public ParentClass? ParentClass { get; set; } + + public bool IsStatic { get; init; } + + public List Usings { get; set; } = new List(); + + public virtual bool Equals(ObjectControllerDefinition? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return Namespace == other.Namespace + && TargetType == other.TargetType + && Properties.SequenceEqual(other.Properties, EqualityComparer.Default) + && Methods.SequenceEqual(other.Methods, EqualityComparer.Default) + && Usings.SequenceEqual(other.Usings, EqualityComparer.Default); + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = (Namespace != null ? Namespace.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ TargetType.GetHashCode(); + hashCode = (hashCode * 397) ^ Properties.GetHashCode(); + hashCode = (hashCode * 397) ^ Methods.GetHashCode(); + hashCode = (hashCode * 397) ^ Usings.GetHashCode(); + hashCode = (hashCode * 397) ^ (ParentClass != null ? ParentClass.GetHashCode() : 0); + return hashCode; + } + } +} \ No newline at end of file diff --git a/src/GraphQL.Server.SourceGenerators/ObjectGenerator.cs b/src/GraphQL.Server.SourceGenerators/ObjectGenerator.cs index 0f585c7b7..bb9544159 100644 --- a/src/GraphQL.Server.SourceGenerators/ObjectGenerator.cs +++ b/src/GraphQL.Server.SourceGenerators/ObjectGenerator.cs @@ -1,6 +1,9 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Tanka.GraphQL.Server.SourceGenerators; @@ -46,6 +49,28 @@ public SourceGeneratedTypesBuilder(OptionsBuilder builder) public class {{ObjectTypeAttributeName}}: Attribute { } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public class FromArgumentsAttribute: Attribute + { + } + """; + + public static string InputTypeAttributeName = "InputTypeAttribute"; + public static string InputTypeFullyQualifiedAttributeName = $"Tanka.GraphQL.Server.{InputTypeAttributeName}"; + + public static string InputTypeSources = $$""" + using System; + using System.Threading.Tasks; + using Microsoft.Extensions.Options; + using Tanka.GraphQL.Executable; + + namespace Tanka.GraphQL.Server; + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public class {{InputTypeAttributeName}}: Attribute + { + } """; public void Initialize(IncrementalGeneratorInitializationContext context) @@ -55,10 +80,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.RegisterPostInitializationOutput(c => { c.AddSource("ObjectType.g.cs", ObjectTypeSources); + c.AddSource("InputType.g.cs", InputTypeSources); }); - IncrementalValuesProvider - schemaNodes = context.SyntaxProvider + IncrementalValuesProvider objectDefinitions = context.SyntaxProvider .ForAttributeWithMetadataName( ObjectTypeFullyQualifiedAttributeName, (node, ct) => node is ClassDeclarationSyntax, @@ -67,9 +92,32 @@ public void Initialize(IncrementalGeneratorInitializationContext context) ) ) .WithComparer(EqualityComparer.Default) - .WithTrackingName("ObjectType"); + .WithTrackingName("ObjectTypes"); + + context.RegisterSourceOutput(objectDefinitions, ObjectTypeEmitter.Emit); + context.RegisterSourceOutput(objectDefinitions.Collect(), ObjectTypeEmitter.EmitNamespaceAddMethod); - context.RegisterSourceOutput(schemaNodes, ObjectTypeEmitter.Emit); + IncrementalValuesProvider inputDefinitions = context + .SyntaxProvider + .ForAttributeWithMetadataName( + InputTypeFullyQualifiedAttributeName, + (node, ct) => node is ClassDeclarationSyntax, + (syntaxContext, ct) => new InputTypeParser(syntaxContext).ParseInputTypeDefinition( + (ClassDeclarationSyntax)syntaxContext.TargetNode + ) + ) + .WithComparer(EqualityComparer.Default) + .WithTrackingName("InputTypes"); + + context.RegisterSourceOutput(inputDefinitions, (spc, inputDefinition) => new InputTypeEmitter(spc).Emit(inputDefinition)); + + var inputDefinitionsByNamespace = inputDefinitions + .Collect() + .SelectMany((ns, _) => + { + var group = ns.GroupBy(n => n.Namespace); + return group; + }); } } \ No newline at end of file diff --git a/src/GraphQL.Server.SourceGenerators/ObjectMethodDefinition.cs b/src/GraphQL.Server.SourceGenerators/ObjectMethodDefinition.cs new file mode 100644 index 000000000..aa55e1981 --- /dev/null +++ b/src/GraphQL.Server.SourceGenerators/ObjectMethodDefinition.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Tanka.GraphQL.Server.SourceGenerators; + +public record ObjectMethodDefinition +{ + public string Name { get; set; } + + public bool IsAsync { get; set; } + + public List Parameters { get; set; } = new List(); + + public string ReturnType { get; init; } + + public string ClosestMatchingGraphQLTypeName { get; set; } +} \ No newline at end of file diff --git a/src/GraphQL.Server.SourceGenerators/ObjectPropertyDefinition.cs b/src/GraphQL.Server.SourceGenerators/ObjectPropertyDefinition.cs new file mode 100644 index 000000000..fe26b8851 --- /dev/null +++ b/src/GraphQL.Server.SourceGenerators/ObjectPropertyDefinition.cs @@ -0,0 +1,11 @@ +namespace Tanka.GraphQL.Server.SourceGenerators; + +public record ObjectPropertyDefinition +{ + public string Name { get; init; } + + public string ReturnType { get; init; } + + public bool IsNullable { get; init; } + public string ClosestMatchingGraphQLTypeName { get; set; } +} \ No newline at end of file diff --git a/src/GraphQL.Server.SourceGenerators/ObjectTypeEmitter.cs b/src/GraphQL.Server.SourceGenerators/ObjectTypeEmitter.cs index fee505846..97af75c1a 100644 --- a/src/GraphQL.Server.SourceGenerators/ObjectTypeEmitter.cs +++ b/src/GraphQL.Server.SourceGenerators/ObjectTypeEmitter.cs @@ -1,5 +1,7 @@ -using System.Linq; +using System.Collections.Immutable; +using System.Linq; using System.Text; +using System.Text.Json; using Microsoft.CodeAnalysis; namespace Tanka.GraphQL.Server.SourceGenerators; @@ -45,6 +47,17 @@ public static class {{name}}ControllerExtensions { "{{fieldName}}: {{fieldType}}", {{resolverMethod}} } """; + public const string NamespaceAddTemplate = """ + using System; + using System.Threading.Tasks; + using Microsoft.Extensions.Options; + using Tanka.GraphQL.Server; + using Tanka.GraphQL.Executable; + using Tanka.GraphQL.ValueResolution; + using Tanka.GraphQL.Fields; + + """; + public static void Emit( SourceProductionContext context, @@ -68,6 +81,50 @@ public static void Emit( context.AddSource($"{ns}{definition.TargetType}Controller.g.cs", builder.ToString()); } + + public static void EmitNamespaceAddMethod( + SourceProductionContext context, + ImmutableArray objectControllerDefinitions) + { + var grouped = objectControllerDefinitions + .GroupBy(x => x.Namespace) + .ToImmutableArray(); + + foreach (IGrouping? group in grouped) + { + string? nsName = group.Key; + var classNames = group.Select(x => x.TargetType).ToList(); + + + var builder = new IndentedStringBuilder(); + builder.AppendLine(NamespaceAddTemplate); + + if (!string.IsNullOrEmpty(nsName)) + builder.AppendLine($"namespace {nsName};"); + else + nsName = "Global"; + + string nsClassName = nsName.Replace(".", ""); + + builder.AppendLine($"public static class {nsClassName}SourceGeneratedTypesExtensions"); + builder.AppendLine("{"); + builder.IncrementIndent(); + + builder.AppendLine( + $"public static SourceGeneratedTypesBuilder Add{nsClassName}Controllers(this SourceGeneratedTypesBuilder builder)"); + builder.AppendLine("{"); + builder.IncrementIndent(); + foreach (string className in classNames) builder.AppendLine($"builder.Add{className}Controller();"); + builder.AppendLine("return builder;"); + builder.DecrementIndent(); + builder.AppendLine("}"); + builder.DecrementIndent(); + builder.AppendLine("}"); + + context.AddSource($"{nsClassName}ControllerExtensions.cs", builder.ToString()); + } + } + private static string EmitFieldsWithResolvers(ObjectControllerDefinition definition) { var builder = new IndentedStringBuilder(); @@ -75,7 +132,7 @@ private static string EmitFieldsWithResolvers(ObjectControllerDefinition definit { ObjectPropertyDefinition property = definition.Properties[index]; builder.Append(FieldWithResolverTemplate - .Replace("{{fieldName}}", System.Text.Json.JsonNamingPolicy.CamelCase.ConvertName(property.Name)) + .Replace("{{fieldName}}", JsonNamingPolicy.CamelCase.ConvertName(property.Name)) .Replace("{{fieldType}}", property.ClosestMatchingGraphQLTypeName) .Replace("{{resolverMethod}}", $"{definition.TargetType}Controller.{property.Name}") ); @@ -100,14 +157,15 @@ private static string EmitFieldsWithResolvers(ObjectControllerDefinition definit { ObjectMethodDefinition method = definition.Methods[index]; - var fieldName = System.Text.Json.JsonNamingPolicy.CamelCase.ConvertName(method.Name); - var fieldType = method.ClosestMatchingGraphQLTypeName; + string fieldName = JsonNamingPolicy.CamelCase.ConvertName(method.Name); + string fieldType = method.ClosestMatchingGraphQLTypeName; var fieldArguments = method.Parameters .Where(p => p.FromArguments == true || p.IsPrimitive) .Select(a => $"{a.Name}: {a.ClosestMatchingGraphQLTypeName}") .ToList(); - string fieldDefinition = fieldArguments.Any() ? $"{fieldName}({string.Join(", ", fieldArguments)})" : fieldName; + string fieldDefinition = + fieldArguments.Any() ? $"{fieldName}({string.Join(", ", fieldArguments)})" : fieldName; builder.Append(FieldWithResolverTemplate .Replace("{{fieldName}}", fieldDefinition) diff --git a/src/GraphQL.Server.SourceGenerators/ObjectTypeParser.cs b/src/GraphQL.Server.SourceGenerators/ObjectTypeParser.cs index 891e92053..216613c6b 100644 --- a/src/GraphQL.Server.SourceGenerators/ObjectTypeParser.cs +++ b/src/GraphQL.Server.SourceGenerators/ObjectTypeParser.cs @@ -1,10 +1,10 @@ -using System; -using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using System.Reflection; namespace Tanka.GraphQL.Server.SourceGenerators { @@ -28,11 +28,11 @@ public static ObjectControllerDefinition ParseObjectControllerDefinition( { IsStatic = classDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword), Usings = classDeclaration.SyntaxTree.GetCompilationUnitRoot().Usings.Select(u => u.Name.ToString()).ToList(), - Namespace = GetNamespace(classDeclaration), + Namespace = TypeHelper.GetNamespace(classDeclaration), TargetType = classDeclaration.Identifier.Text, Properties = properties, Methods = methods, - ParentClass = GetParentClasses(classDeclaration) + ParentClass = TypeHelper.GetParentClasses(classDeclaration) }; } @@ -54,8 +54,8 @@ private static (List Properties, List Properties, List p.Type is not null) .Select(p => new ParameterDefinition() { Name = p.Identifier.Text, Type = p.Type!.ToString(), - ClosestMatchingGraphQLTypeName = GetClosestMatchingGraphQLTypeName(context.SemanticModel, UnwrapTaskType(p.Type)), - IsNullable = IsTypeNullable(p.Type!), - IsPrimitive = IsPrimitiveType(p.Type!), - FromArguments = HasAttribute(p.AttributeLists, "FromArguments"), - FromServices = HasAttribute(p.AttributeLists, "FromServices") + ClosestMatchingGraphQLTypeName = GetClosestMatchingGraphQLTypeName(context.SemanticModel, TypeHelper.UnwrapTaskType(p.Type)), + IsNullable = TypeHelper.IsTypeNullable(p.Type!), + IsPrimitive = TypeHelper.IsPrimitiveType(p.Type!), + FromArguments = TypeHelper.HasAttribute(p.AttributeLists, "FromArguments"), + FromServices = TypeHelper.HasAttribute(p.AttributeLists, "FromServices") }).ToList() }; methods.Add(methodDefinition); @@ -97,330 +97,5 @@ private static string GetClosestMatchingGraphQLTypeName(SemanticModel model, Typ return TypeHelper.GetGraphQLTypeName(typeSymbol); } - - public static bool HasAttribute( - SyntaxList attributeLists, - string attributeName) - { - // Check if the parameter has any attributes - if (!attributeLists.Any()) - { - return false; - } - - foreach (var attributeList in attributeLists) - { - foreach (var attribute in attributeList.Attributes) - { - // should probably check fully qualified name - if (attribute.Name.ToString() == attributeName) - { - return true; - } - } - } - - return false; - } - - - public static bool IsPrimitiveType(TypeSyntax typeSyntax) - { - // Define a list of C# primitive types - string[] primitiveTypes = new string[] - { - "bool", - "byte", - "sbyte", - "char", - "decimal", - "double", - "float", - "int", - "uint", - "long", - "ulong", - "short", - "ushort", - "string" - }; - - // Get the name of the type syntax - string typeName = UnwrapNullable(typeSyntax).ToString(); - - // Check if the type name is a primitive type - return primitiveTypes.Contains(typeName); - } - - - public static bool IsTypeNullable(TypeSyntax typeSyntax) - { - return typeSyntax.IsKind(SyntaxKind.NullableType); - } - - public static TypeSyntax UnwrapTaskType(TypeSyntax typeSyntax) - { - // Check if the type is a named type syntax - if (typeSyntax is GenericNameSyntax namedTypeSyntax) - { - // Check if the name of the type is "Task" or "ValueTask" - if (namedTypeSyntax.Identifier.ValueText is "Task" or "ValueTask") - { - // Get the type argument list syntax for the named type syntax - var typeArgumentListSyntax = namedTypeSyntax.TypeArgumentList; - - // If the type argument list syntax exists and contains an argument, return the type syntax for the argument - if (typeArgumentListSyntax?.Arguments.Any() == true) - { - return typeArgumentListSyntax.Arguments[0]; - } - } - } - - // If the type is not Task or ValueTask, return the type syntax itself - return typeSyntax; - } - - public static TypeSyntax UnwrapNullable(TypeSyntax typeSyntax) - { - if (typeSyntax is NullableTypeSyntax namedTypeSyntax) - { - return namedTypeSyntax.ElementType; - } - - return typeSyntax; - } - - public static bool IsValueTaskOrTask(TypeSyntax typeSyntax) - { - // Check if the type is a named type syntax - if (typeSyntax is GenericNameSyntax namedTypeSyntax) - { - // Check if the name of the type is "ValueTask" or "Task" - if (namedTypeSyntax.Identifier.ValueText is "ValueTask" or "Task") - { - // Check if the named type syntax has type arguments - var typeArgumentListSyntax = namedTypeSyntax.TypeArgumentList; - if (typeArgumentListSyntax?.Arguments.Any() == true) - { - return true; - } - } - } - - // If the type is not a named type syntax for ValueTask or Task, return false - return false; - } - - - //source: https://andrewlock.net/creating-a-source-generator-part-5-finding-a-type-declarations-namespace-and-type-hierarchy/ - // determine the namespace the class/enum/struct is declared in, if any - private static string GetNamespace(BaseTypeDeclarationSyntax syntax) - { - // If we don't have a namespace at all we'll return an empty string - // This accounts for the "default namespace" case - string nameSpace = string.Empty; - - // Get the containing syntax node for the type declaration - // (could be a nested type, for example) - SyntaxNode? potentialNamespaceParent = syntax.Parent; - - // Keep moving "out" of nested classes etc until we get to a namespace - // or until we run out of parents - while (potentialNamespaceParent != null && - potentialNamespaceParent is not NamespaceDeclarationSyntax - && potentialNamespaceParent is not FileScopedNamespaceDeclarationSyntax) - { - potentialNamespaceParent = potentialNamespaceParent.Parent; - } - - // Build up the final namespace by looping until we no longer have a namespace declaration - if (potentialNamespaceParent is BaseNamespaceDeclarationSyntax namespaceParent) - { - // We have a namespace. Use that as the type - nameSpace = namespaceParent.Name.ToString(); - - // Keep moving "out" of the namespace declarations until we - // run out of nested namespace declarations - while (true) - { - if (namespaceParent.Parent is not NamespaceDeclarationSyntax parent) - { - break; - } - - // Add the outer namespace as a prefix to the final namespace - nameSpace = $"{namespaceParent.Name}.{nameSpace}"; - namespaceParent = parent; - } - } - - // return the final namespace - return nameSpace; - } - - public static ParentClass? GetParentClasses(BaseTypeDeclarationSyntax typeSyntax) - { - // Try and get the parent syntax. If it isn't a type like class/struct, this will be null - var parentSyntax = typeSyntax.Parent as TypeDeclarationSyntax; - ParentClass? parentClassInfo = null; - - // Keep looping while we're in a supported nested type - while (parentSyntax != null && IsAllowedKind(parentSyntax.Kind())) - { - // Record the parent type keyword (class/struct etc), name, and constraints - parentClassInfo = new ParentClass( - parentSyntax.Keyword.ValueText, - parentSyntax.Identifier.ToString() + parentSyntax.TypeParameterList, - parentSyntax.ConstraintClauses.ToString(), - parentClassInfo); // set the child link (null initially) - - // Move to the next outer type - parentSyntax = parentSyntax.Parent as TypeDeclarationSyntax; - } - - // return a link to the outermost parent type - return parentClassInfo; - } - - public static string GetResource(string nameSpace, ParentClass? parentClass) - { - var sb = new StringBuilder(); - - // If we don't have a namespace, generate the code in the "default" - // namespace, either global:: or a different - bool hasNamespace = !string.IsNullOrEmpty(nameSpace); - if (hasNamespace) - // We could use a file-scoped namespace here which would be a little impler, - // but that requires C# 10, which might not be available. - // Depends what you want to support! - sb - .Append("namespace ") - .Append(nameSpace) - .AppendLine(@" - {"); - - var parentsCount = 0; - // Loop through the full parent type hiearchy, starting with the outermost - while (parentClass is not null) - { - sb - .Append(" partial ") - .Append(parentClass.Keyword) // e.g. class/struct/record - .Append(' ') - .Append(parentClass.Name) // e.g. Outer/Generic - .Append(' ') - .Append(parentClass.Constraints) // e.g. where T: new() - .AppendLine(@" - {"); - parentsCount++; // keep track of how many layers deep we are - parentClass = parentClass.Child; // repeat with the next child - } - - // Write the actual target generation code here. Not shown for brevity - sb.AppendLine(@"public partial readonly struct TestId - { - }"); - - // We need to "close" each of the parent types, so write - // the required number of '}' - for (var i = 0; i < parentsCount; i++) sb.AppendLine(@" }"); - - // Close the namespace, if we had one - if (hasNamespace) sb.Append('}').AppendLine(); - - return sb.ToString(); - } - - public static bool IsAllowedKind(SyntaxKind kind) - { - return kind == SyntaxKind.ClassDeclaration || - kind == SyntaxKind.StructDeclaration || - kind == SyntaxKind.RecordDeclaration; - } } - - public class ObjectControllerDefinition: IEquatable - { - public string? Namespace { get; init; } - - public string TargetType { get; init; } - - public List Properties { get; set; } = new List(); - - public List Methods { get; set; } = new List(); - - public ParentClass? ParentClass { get; set; } - - public bool IsStatic { get; init; } - - public List Usings { get; set; } = new List(); - - public virtual bool Equals(ObjectControllerDefinition? other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - - return Namespace == other.Namespace - && TargetType == other.TargetType - && Properties.SequenceEqual(other.Properties, EqualityComparer.Default) - && Methods.SequenceEqual(other.Methods, EqualityComparer.Default) - && Usings.SequenceEqual(other.Usings, EqualityComparer.Default); - } - - public override int GetHashCode() - { - unchecked - { - int hashCode = (Namespace != null ? Namespace.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ TargetType.GetHashCode(); - hashCode = (hashCode * 397) ^ Properties.GetHashCode(); - hashCode = (hashCode * 397) ^ Methods.GetHashCode(); - hashCode = (hashCode * 397) ^ Usings.GetHashCode(); - hashCode = (hashCode * 397) ^ (ParentClass != null ? ParentClass.GetHashCode() : 0); - return hashCode; - } - } - } - - public record ObjectPropertyDefinition - { - public string Name { get; init; } - - public string ReturnType { get; init; } - - public bool IsNullable { get; init; } - public string ClosestMatchingGraphQLTypeName { get; set; } - } - - public record ObjectMethodDefinition - { - public string Name { get; set; } - - public bool IsAsync { get; set; } - - public List Parameters { get; set; } = new List(); - - public string ReturnType { get; init; } - - public string ClosestMatchingGraphQLTypeName { get; set; } - } - - public record ParameterDefinition - { - public string Name { get; init; } - - public string Type { get; init; } - - public bool IsNullable { get; set; } = false; - - public bool? FromServices { get; set; } - - public bool? FromArguments { get; set; } - - public bool IsPrimitive { get; init; } - public string ClosestMatchingGraphQLTypeName { get; set; } - } - - } diff --git a/src/GraphQL.Server.SourceGenerators/ParameterDefinition.cs b/src/GraphQL.Server.SourceGenerators/ParameterDefinition.cs new file mode 100644 index 000000000..140050d22 --- /dev/null +++ b/src/GraphQL.Server.SourceGenerators/ParameterDefinition.cs @@ -0,0 +1,17 @@ +namespace Tanka.GraphQL.Server.SourceGenerators; + +public record ParameterDefinition +{ + public string Name { get; init; } + + public string Type { get; init; } + + public bool IsNullable { get; set; } = false; + + public bool? FromServices { get; set; } + + public bool? FromArguments { get; set; } + + public bool IsPrimitive { get; init; } + public string ClosestMatchingGraphQLTypeName { get; set; } +} \ No newline at end of file diff --git a/src/GraphQL.Server.SourceGenerators/TypeHelper.cs b/src/GraphQL.Server.SourceGenerators/TypeHelper.cs index 3d5d3904a..fc2199caa 100644 --- a/src/GraphQL.Server.SourceGenerators/TypeHelper.cs +++ b/src/GraphQL.Server.SourceGenerators/TypeHelper.cs @@ -1,5 +1,8 @@ using Microsoft.CodeAnalysis; using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Tanka.GraphQL.Server.SourceGenerators; @@ -121,4 +124,240 @@ public static bool ValidateReturnType(ITypeSymbol returnTypeSymbol) return returnTypeSymbol.SpecialType != SpecialType.System_Void && !IsTaskOrValueTask(returnTypeSymbol); } + + public static bool HasAttribute( + SyntaxList attributeLists, + string attributeName) + { + // Check if the parameter has any attributes + if (!attributeLists.Any()) + { + return false; + } + + foreach (var attributeList in attributeLists) + { + foreach (var attribute in attributeList.Attributes) + { + // should probably check fully qualified name + if (attribute.Name.ToString() == attributeName) + { + return true; + } + } + } + + return false; + } + + public static bool IsPrimitiveType(TypeSyntax typeSyntax) + { + // Define a list of C# primitive types + string[] primitiveTypes = new string[] + { + "bool", + "byte", + "sbyte", + "char", + "decimal", + "double", + "float", + "int", + "uint", + "long", + "ulong", + "short", + "ushort", + "string" + }; + + // Get the name of the type syntax + string typeName = UnwrapNullable(typeSyntax).ToString(); + + // Check if the type name is a primitive type + return primitiveTypes.Contains(typeName); + } + + public static bool IsTypeNullable(TypeSyntax typeSyntax) + { + return typeSyntax.IsKind(SyntaxKind.NullableType); + } + + public static TypeSyntax UnwrapTaskType(TypeSyntax typeSyntax) + { + // Check if the type is a named type syntax + if (typeSyntax is GenericNameSyntax namedTypeSyntax) + { + // Check if the name of the type is "Task" or "ValueTask" + if (namedTypeSyntax.Identifier.ValueText is "Task" or "ValueTask") + { + // Get the type argument list syntax for the named type syntax + var typeArgumentListSyntax = namedTypeSyntax.TypeArgumentList; + + // If the type argument list syntax exists and contains an argument, return the type syntax for the argument + if (typeArgumentListSyntax?.Arguments.Any() == true) + { + return typeArgumentListSyntax.Arguments[0]; + } + } + } + + // If the type is not Task or ValueTask, return the type syntax itself + return typeSyntax; + } + + public static TypeSyntax UnwrapNullable(TypeSyntax typeSyntax) + { + if (typeSyntax is NullableTypeSyntax namedTypeSyntax) + { + return namedTypeSyntax.ElementType; + } + + return typeSyntax; + } + + public static bool IsValueTaskOrTask(TypeSyntax typeSyntax) + { + // Check if the type is a named type syntax + if (typeSyntax is GenericNameSyntax namedTypeSyntax) + { + // Check if the name of the type is "ValueTask" or "Task" + if (namedTypeSyntax.Identifier.ValueText is "ValueTask" or "Task") + { + // Check if the named type syntax has type arguments + var typeArgumentListSyntax = namedTypeSyntax.TypeArgumentList; + if (typeArgumentListSyntax?.Arguments.Any() == true) + { + return true; + } + } + } + + // If the type is not a named type syntax for ValueTask or Task, return false + return false; + } + + public static string GetNamespace(BaseTypeDeclarationSyntax syntax) + { + // If we don't have a namespace at all we'll return an empty string + // This accounts for the "default namespace" case + string nameSpace = string.Empty; + + // Get the containing syntax node for the type declaration + // (could be a nested type, for example) + SyntaxNode? potentialNamespaceParent = syntax.Parent; + + // Keep moving "out" of nested classes etc until we get to a namespace + // or until we run out of parents + while (potentialNamespaceParent != null && + potentialNamespaceParent is not NamespaceDeclarationSyntax + && potentialNamespaceParent is not FileScopedNamespaceDeclarationSyntax) + { + potentialNamespaceParent = potentialNamespaceParent.Parent; + } + + // Build up the final namespace by looping until we no longer have a namespace declaration + if (potentialNamespaceParent is BaseNamespaceDeclarationSyntax namespaceParent) + { + // We have a namespace. Use that as the type + nameSpace = namespaceParent.Name.ToString(); + + // Keep moving "out" of the namespace declarations until we + // run out of nested namespace declarations + while (true) + { + if (namespaceParent.Parent is not NamespaceDeclarationSyntax parent) + { + break; + } + + // Add the outer namespace as a prefix to the final namespace + nameSpace = $"{namespaceParent.Name}.{nameSpace}"; + namespaceParent = parent; + } + } + + // return the final namespace + return nameSpace; + } + + public static ParentClass? GetParentClasses(BaseTypeDeclarationSyntax typeSyntax) + { + // Try and get the parent syntax. If it isn't a type like class/struct, this will be null + var parentSyntax = typeSyntax.Parent as TypeDeclarationSyntax; + ParentClass? parentClassInfo = null; + + // Keep looping while we're in a supported nested type + while (parentSyntax != null && IsAllowedKind(parentSyntax.Kind())) + { + // Record the parent type keyword (class/struct etc), name, and constraints + parentClassInfo = new ParentClass( + parentSyntax.Keyword.ValueText, + parentSyntax.Identifier.ToString() + parentSyntax.TypeParameterList, + parentSyntax.ConstraintClauses.ToString(), + parentClassInfo); // set the child link (null initially) + + // Move to the next outer type + parentSyntax = parentSyntax.Parent as TypeDeclarationSyntax; + } + + // return a link to the outermost parent type + return parentClassInfo; + } + + public static string GetResource(string nameSpace, ParentClass? parentClass) + { + var sb = new StringBuilder(); + + // If we don't have a namespace, generate the code in the "default" + // namespace, either global:: or a different + bool hasNamespace = !string.IsNullOrEmpty(nameSpace); + if (hasNamespace) + // We could use a file-scoped namespace here which would be a little impler, + // but that requires C# 10, which might not be available. + // Depends what you want to support! + sb + .Append("namespace ") + .Append(nameSpace) + .AppendLine(@" + {"); + + var parentsCount = 0; + // Loop through the full parent type hiearchy, starting with the outermost + while (parentClass is not null) + { + sb + .Append(" partial ") + .Append(parentClass.Keyword) // e.g. class/struct/record + .Append(' ') + .Append(parentClass.Name) // e.g. Outer/Generic + .Append(' ') + .Append(parentClass.Constraints) // e.g. where T: new() + .AppendLine(@" + {"); + parentsCount++; // keep track of how many layers deep we are + parentClass = parentClass.Child; // repeat with the next child + } + + // Write the actual target generation code here. Not shown for brevity + sb.AppendLine(@"public partial readonly struct TestId + { + }"); + + // We need to "close" each of the parent types, so write + // the required number of '}' + for (var i = 0; i < parentsCount; i++) sb.AppendLine(@" }"); + + // Close the namespace, if we had one + if (hasNamespace) sb.Append('}').AppendLine(); + + return sb.ToString(); + } + + public static bool IsAllowedKind(SyntaxKind kind) + { + return kind == SyntaxKind.ClassDeclaration || + kind == SyntaxKind.StructDeclaration || + kind == SyntaxKind.RecordDeclaration; + } } \ No newline at end of file diff --git a/tanka-graphql.sln b/tanka-graphql.sln index 9dd9f9723..a5bc95234 100644 --- a/tanka-graphql.sln +++ b/tanka-graphql.sln @@ -58,11 +58,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Samples.Authorization", "samples\GraphQL.Samples.Authorization\GraphQL.Samples.Authorization.csproj", "{79ECE521-99C5-4BC9-BE15-716EB4939FD1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GraphQL.Server.SourceGenerators", "src\GraphQL.Server.SourceGenerators\GraphQL.Server.SourceGenerators.csproj", "{0C62080A-C453-44C6-93FB-EDF7FC521DDB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Server.SourceGenerators", "src\GraphQL.Server.SourceGenerators\GraphQL.Server.SourceGenerators.csproj", "{0C62080A-C453-44C6-93FB-EDF7FC521DDB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GraphQL.Server.SourceGenerators.Tests", "tests\GraphQL.Server.SourceGenerators.Tests\GraphQL.Server.SourceGenerators.Tests.csproj", "{253829A2-E939-43A9-AAA5-14127A45ABC6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Server.SourceGenerators.Tests", "tests\GraphQL.Server.SourceGenerators.Tests\GraphQL.Server.SourceGenerators.Tests.csproj", "{253829A2-E939-43A9-AAA5-14127A45ABC6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Samples.SourceGenerators", "samples\GraphQL.Samples.SourceGenerators\GraphQL.Samples.SourceGenerators.csproj", "{E78F66D9-83E0-4DAB-A22B-F1886D1C36C0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Samples.SG.Namespace", "samples\GraphQL.Samples.SG.Namespace\GraphQL.Samples.SG.Namespace.csproj", "{C5EC3E3C-3A7B-4BFC-8F52-A3574C7A47C8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Samples.SG.Basic", "samples\GraphQL.Samples.SG.Basic\GraphQL.Samples.SG.Basic.csproj", "{7B1D50A0-CEC8-49BA-B40B-09A72A76B0DE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Samples.SG.InputType", "samples\GraphQL.Samples.SG.InputType\GraphQL.Samples.SG.InputType.csproj", "{AA03AB46-FF43-49AC-BEE2-FEC5A7BB8CDE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Samples.SG.Services", "samples\GraphQL.Samples.SG.Services\GraphQL.Samples.SG.Services.csproj", "{E136499A-90C7-47A9-8A25-EF2FCB1C0A24}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Samples.SG.Arguments", "samples\GraphQL.Samples.SG.Arguments\GraphQL.Samples.SG.Arguments.csproj", "{4A12194D-8289-462C-94B8-8ABDE2D8283A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -290,18 +298,66 @@ Global {253829A2-E939-43A9-AAA5-14127A45ABC6}.Release|x64.Build.0 = Release|Any CPU {253829A2-E939-43A9-AAA5-14127A45ABC6}.Release|x86.ActiveCfg = Release|Any CPU {253829A2-E939-43A9-AAA5-14127A45ABC6}.Release|x86.Build.0 = Release|Any CPU - {E78F66D9-83E0-4DAB-A22B-F1886D1C36C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E78F66D9-83E0-4DAB-A22B-F1886D1C36C0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E78F66D9-83E0-4DAB-A22B-F1886D1C36C0}.Debug|x64.ActiveCfg = Debug|Any CPU - {E78F66D9-83E0-4DAB-A22B-F1886D1C36C0}.Debug|x64.Build.0 = Debug|Any CPU - {E78F66D9-83E0-4DAB-A22B-F1886D1C36C0}.Debug|x86.ActiveCfg = Debug|Any CPU - {E78F66D9-83E0-4DAB-A22B-F1886D1C36C0}.Debug|x86.Build.0 = Debug|Any CPU - {E78F66D9-83E0-4DAB-A22B-F1886D1C36C0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E78F66D9-83E0-4DAB-A22B-F1886D1C36C0}.Release|Any CPU.Build.0 = Release|Any CPU - {E78F66D9-83E0-4DAB-A22B-F1886D1C36C0}.Release|x64.ActiveCfg = Release|Any CPU - {E78F66D9-83E0-4DAB-A22B-F1886D1C36C0}.Release|x64.Build.0 = Release|Any CPU - {E78F66D9-83E0-4DAB-A22B-F1886D1C36C0}.Release|x86.ActiveCfg = Release|Any CPU - {E78F66D9-83E0-4DAB-A22B-F1886D1C36C0}.Release|x86.Build.0 = Release|Any CPU + {C5EC3E3C-3A7B-4BFC-8F52-A3574C7A47C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5EC3E3C-3A7B-4BFC-8F52-A3574C7A47C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5EC3E3C-3A7B-4BFC-8F52-A3574C7A47C8}.Debug|x64.ActiveCfg = Debug|Any CPU + {C5EC3E3C-3A7B-4BFC-8F52-A3574C7A47C8}.Debug|x64.Build.0 = Debug|Any CPU + {C5EC3E3C-3A7B-4BFC-8F52-A3574C7A47C8}.Debug|x86.ActiveCfg = Debug|Any CPU + {C5EC3E3C-3A7B-4BFC-8F52-A3574C7A47C8}.Debug|x86.Build.0 = Debug|Any CPU + {C5EC3E3C-3A7B-4BFC-8F52-A3574C7A47C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5EC3E3C-3A7B-4BFC-8F52-A3574C7A47C8}.Release|Any CPU.Build.0 = Release|Any CPU + {C5EC3E3C-3A7B-4BFC-8F52-A3574C7A47C8}.Release|x64.ActiveCfg = Release|Any CPU + {C5EC3E3C-3A7B-4BFC-8F52-A3574C7A47C8}.Release|x64.Build.0 = Release|Any CPU + {C5EC3E3C-3A7B-4BFC-8F52-A3574C7A47C8}.Release|x86.ActiveCfg = Release|Any CPU + {C5EC3E3C-3A7B-4BFC-8F52-A3574C7A47C8}.Release|x86.Build.0 = Release|Any CPU + {7B1D50A0-CEC8-49BA-B40B-09A72A76B0DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B1D50A0-CEC8-49BA-B40B-09A72A76B0DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B1D50A0-CEC8-49BA-B40B-09A72A76B0DE}.Debug|x64.ActiveCfg = Debug|Any CPU + {7B1D50A0-CEC8-49BA-B40B-09A72A76B0DE}.Debug|x64.Build.0 = Debug|Any CPU + {7B1D50A0-CEC8-49BA-B40B-09A72A76B0DE}.Debug|x86.ActiveCfg = Debug|Any CPU + {7B1D50A0-CEC8-49BA-B40B-09A72A76B0DE}.Debug|x86.Build.0 = Debug|Any CPU + {7B1D50A0-CEC8-49BA-B40B-09A72A76B0DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B1D50A0-CEC8-49BA-B40B-09A72A76B0DE}.Release|Any CPU.Build.0 = Release|Any CPU + {7B1D50A0-CEC8-49BA-B40B-09A72A76B0DE}.Release|x64.ActiveCfg = Release|Any CPU + {7B1D50A0-CEC8-49BA-B40B-09A72A76B0DE}.Release|x64.Build.0 = Release|Any CPU + {7B1D50A0-CEC8-49BA-B40B-09A72A76B0DE}.Release|x86.ActiveCfg = Release|Any CPU + {7B1D50A0-CEC8-49BA-B40B-09A72A76B0DE}.Release|x86.Build.0 = Release|Any CPU + {AA03AB46-FF43-49AC-BEE2-FEC5A7BB8CDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA03AB46-FF43-49AC-BEE2-FEC5A7BB8CDE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA03AB46-FF43-49AC-BEE2-FEC5A7BB8CDE}.Debug|x64.ActiveCfg = Debug|Any CPU + {AA03AB46-FF43-49AC-BEE2-FEC5A7BB8CDE}.Debug|x64.Build.0 = Debug|Any CPU + {AA03AB46-FF43-49AC-BEE2-FEC5A7BB8CDE}.Debug|x86.ActiveCfg = Debug|Any CPU + {AA03AB46-FF43-49AC-BEE2-FEC5A7BB8CDE}.Debug|x86.Build.0 = Debug|Any CPU + {AA03AB46-FF43-49AC-BEE2-FEC5A7BB8CDE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA03AB46-FF43-49AC-BEE2-FEC5A7BB8CDE}.Release|Any CPU.Build.0 = Release|Any CPU + {AA03AB46-FF43-49AC-BEE2-FEC5A7BB8CDE}.Release|x64.ActiveCfg = Release|Any CPU + {AA03AB46-FF43-49AC-BEE2-FEC5A7BB8CDE}.Release|x64.Build.0 = Release|Any CPU + {AA03AB46-FF43-49AC-BEE2-FEC5A7BB8CDE}.Release|x86.ActiveCfg = Release|Any CPU + {AA03AB46-FF43-49AC-BEE2-FEC5A7BB8CDE}.Release|x86.Build.0 = Release|Any CPU + {E136499A-90C7-47A9-8A25-EF2FCB1C0A24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E136499A-90C7-47A9-8A25-EF2FCB1C0A24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E136499A-90C7-47A9-8A25-EF2FCB1C0A24}.Debug|x64.ActiveCfg = Debug|Any CPU + {E136499A-90C7-47A9-8A25-EF2FCB1C0A24}.Debug|x64.Build.0 = Debug|Any CPU + {E136499A-90C7-47A9-8A25-EF2FCB1C0A24}.Debug|x86.ActiveCfg = Debug|Any CPU + {E136499A-90C7-47A9-8A25-EF2FCB1C0A24}.Debug|x86.Build.0 = Debug|Any CPU + {E136499A-90C7-47A9-8A25-EF2FCB1C0A24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E136499A-90C7-47A9-8A25-EF2FCB1C0A24}.Release|Any CPU.Build.0 = Release|Any CPU + {E136499A-90C7-47A9-8A25-EF2FCB1C0A24}.Release|x64.ActiveCfg = Release|Any CPU + {E136499A-90C7-47A9-8A25-EF2FCB1C0A24}.Release|x64.Build.0 = Release|Any CPU + {E136499A-90C7-47A9-8A25-EF2FCB1C0A24}.Release|x86.ActiveCfg = Release|Any CPU + {E136499A-90C7-47A9-8A25-EF2FCB1C0A24}.Release|x86.Build.0 = Release|Any CPU + {4A12194D-8289-462C-94B8-8ABDE2D8283A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A12194D-8289-462C-94B8-8ABDE2D8283A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A12194D-8289-462C-94B8-8ABDE2D8283A}.Debug|x64.ActiveCfg = Debug|Any CPU + {4A12194D-8289-462C-94B8-8ABDE2D8283A}.Debug|x64.Build.0 = Debug|Any CPU + {4A12194D-8289-462C-94B8-8ABDE2D8283A}.Debug|x86.ActiveCfg = Debug|Any CPU + {4A12194D-8289-462C-94B8-8ABDE2D8283A}.Debug|x86.Build.0 = Debug|Any CPU + {4A12194D-8289-462C-94B8-8ABDE2D8283A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A12194D-8289-462C-94B8-8ABDE2D8283A}.Release|Any CPU.Build.0 = Release|Any CPU + {4A12194D-8289-462C-94B8-8ABDE2D8283A}.Release|x64.ActiveCfg = Release|Any CPU + {4A12194D-8289-462C-94B8-8ABDE2D8283A}.Release|x64.Build.0 = Release|Any CPU + {4A12194D-8289-462C-94B8-8ABDE2D8283A}.Release|x86.ActiveCfg = Release|Any CPU + {4A12194D-8289-462C-94B8-8ABDE2D8283A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -311,7 +367,11 @@ Global {60709A73-4981-4973-8AF7-752B24E425B7} = {56733FE3-0AB2-491B-9514-AE59DCC94428} {8B3C3296-8AE2-47F0-93B8-9CFF929EBAA4} = {B9BE1B74-A36A-4A10-9BCE-E5EFDF6D15A8} {79ECE521-99C5-4BC9-BE15-716EB4939FD1} = {B9BE1B74-A36A-4A10-9BCE-E5EFDF6D15A8} - {E78F66D9-83E0-4DAB-A22B-F1886D1C36C0} = {B9BE1B74-A36A-4A10-9BCE-E5EFDF6D15A8} + {C5EC3E3C-3A7B-4BFC-8F52-A3574C7A47C8} = {B9BE1B74-A36A-4A10-9BCE-E5EFDF6D15A8} + {7B1D50A0-CEC8-49BA-B40B-09A72A76B0DE} = {B9BE1B74-A36A-4A10-9BCE-E5EFDF6D15A8} + {AA03AB46-FF43-49AC-BEE2-FEC5A7BB8CDE} = {B9BE1B74-A36A-4A10-9BCE-E5EFDF6D15A8} + {E136499A-90C7-47A9-8A25-EF2FCB1C0A24} = {B9BE1B74-A36A-4A10-9BCE-E5EFDF6D15A8} + {4A12194D-8289-462C-94B8-8ABDE2D8283A} = {B9BE1B74-A36A-4A10-9BCE-E5EFDF6D15A8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6EC1BBAB-620C-44FB-A12E-E68F69D689B6} diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/InputTypeFacts.cs b/tests/GraphQL.Server.SourceGenerators.Tests/InputTypeFacts.cs new file mode 100644 index 000000000..39b5f99b8 --- /dev/null +++ b/tests/GraphQL.Server.SourceGenerators.Tests/InputTypeFacts.cs @@ -0,0 +1,23 @@ +namespace Tanka.GraphQL.Server.SourceGenerators.Tests; + +[UsesVerify] +public class InputTypeFacts +{ + [Fact] + public Task Generate_InputType() + { + var source = """ + using Tanka.GraphQL.Server; + + [InputType] + public class InputMessage + { + public string Id { get; set; } + public string Content { get; set; } + } + """; + + return TestHelper.Verify(source); + } + +} \ No newline at end of file diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/ObjectGeneratorFacts.cs b/tests/GraphQL.Server.SourceGenerators.Tests/ObjectGeneratorFacts.cs index 76129137d..fbcc5ddea 100644 --- a/tests/GraphQL.Server.SourceGenerators.Tests/ObjectGeneratorFacts.cs +++ b/tests/GraphQL.Server.SourceGenerators.Tests/ObjectGeneratorFacts.cs @@ -45,6 +45,28 @@ public class Person return TestHelper.Verify(source); } + [Fact] + public Task Generate_ObjectType_type_name_no_namespace() + { + var source = """ + using Tanka.GraphQL.Server; + + [ObjectType] + public static class Query + { + public static Person Person(int id) = new Person(); + } + + [ObjectType] + public class Person + { + public string Name { get; set;} + } + """; + + return TestHelper.Verify(source); + } + [Fact] public Task StaticClass_Generate_property_resolver() { diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#InputMessageInputType.g.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#InputMessageInputType.g.verified.cs new file mode 100644 index 000000000..034ff29a7 --- /dev/null +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#InputMessageInputType.g.verified.cs @@ -0,0 +1,29 @@ +//HintName: InputMessageInputType.g.cs +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Tanka.GraphQL.Server; +using Tanka.GraphQL.Executable; +using Tanka.GraphQL.ValueResolution; +using Tanka.GraphQL.Fields; + + + +public static class InputMessageInputTypeExtensions +{ + public static SourceGeneratedTypesBuilder AddInputMessageInputType( + this SourceGeneratedTypesBuilder builder) + { + builder.Builder.Configure(options => options.Builder.Add( + """ + input InputMessage + { + id: String! + content: String! + } + """ + ); + + return builder; + } +} diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#InputType.g.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#InputType.g.verified.cs new file mode 100644 index 000000000..7f21cfdc9 --- /dev/null +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#InputType.g.verified.cs @@ -0,0 +1,12 @@ +//HintName: InputType.g.cs +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Tanka.GraphQL.Executable; + +namespace Tanka.GraphQL.Server; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class InputTypeAttribute: Attribute +{ +} \ No newline at end of file diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#ObjectType.g.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#ObjectType.g.verified.cs new file mode 100644 index 000000000..5403dc4b3 --- /dev/null +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#ObjectType.g.verified.cs @@ -0,0 +1,34 @@ +//HintName: ObjectType.g.cs +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Tanka.GraphQL.Executable; + +namespace Tanka.GraphQL.Server; + +public static class SourceGeneratedExecutableSchemaExtensions +{ + public static OptionsBuilder AddGeneratedTypes( + this OptionsBuilder builder, + Action configureTypes) + { + var typesBuilder = new SourceGeneratedTypesBuilder(builder); + configureTypes(typesBuilder); + return builder; + } +} + +public class SourceGeneratedTypesBuilder +{ + public OptionsBuilder Builder { get; } + + public SourceGeneratedTypesBuilder(OptionsBuilder builder) + { + Builder = builder; + } +} + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class ObjectTypeAttribute: Attribute +{ +} \ No newline at end of file diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name#InputType.g.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name#InputType.g.verified.cs new file mode 100644 index 000000000..7f21cfdc9 --- /dev/null +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name#InputType.g.verified.cs @@ -0,0 +1,12 @@ +//HintName: InputType.g.cs +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Tanka.GraphQL.Executable; + +namespace Tanka.GraphQL.Server; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class InputTypeAttribute: Attribute +{ +} \ No newline at end of file diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name#TestsControllerExtensions.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name#TestsControllerExtensions.verified.cs new file mode 100644 index 000000000..041341e5a --- /dev/null +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name#TestsControllerExtensions.verified.cs @@ -0,0 +1,19 @@ +//HintName: TestsControllerExtensions.cs +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Tanka.GraphQL.Server; +using Tanka.GraphQL.Executable; +using Tanka.GraphQL.ValueResolution; +using Tanka.GraphQL.Fields; + +namespace Tests; +public static class TestsSourceGeneratedTypesExtensions +{ + public static SourceGeneratedTypesBuilder AddTestsControllers(this SourceGeneratedTypesBuilder builder) + { + builder.AddQueryController(); + builder.AddPersonController(); + return builder; + } +} diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#GlobalControllerExtensions.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#GlobalControllerExtensions.verified.cs new file mode 100644 index 000000000..6c518924c --- /dev/null +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#GlobalControllerExtensions.verified.cs @@ -0,0 +1,18 @@ +//HintName: GlobalControllerExtensions.cs +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Tanka.GraphQL.Server; +using Tanka.GraphQL.Executable; +using Tanka.GraphQL.ValueResolution; +using Tanka.GraphQL.Fields; + +public static class GlobalSourceGeneratedTypesExtensions +{ + public static SourceGeneratedTypesBuilder AddGlobalControllers(this SourceGeneratedTypesBuilder builder) + { + builder.AddQueryController(); + builder.AddPersonController(); + return builder; + } +} diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#InputType.g.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#InputType.g.verified.cs new file mode 100644 index 000000000..7f21cfdc9 --- /dev/null +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#InputType.g.verified.cs @@ -0,0 +1,12 @@ +//HintName: InputType.g.cs +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Tanka.GraphQL.Executable; + +namespace Tanka.GraphQL.Server; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class InputTypeAttribute: Attribute +{ +} \ No newline at end of file diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#ObjectType.g.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#ObjectType.g.verified.cs new file mode 100644 index 000000000..5403dc4b3 --- /dev/null +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#ObjectType.g.verified.cs @@ -0,0 +1,34 @@ +//HintName: ObjectType.g.cs +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Tanka.GraphQL.Executable; + +namespace Tanka.GraphQL.Server; + +public static class SourceGeneratedExecutableSchemaExtensions +{ + public static OptionsBuilder AddGeneratedTypes( + this OptionsBuilder builder, + Action configureTypes) + { + var typesBuilder = new SourceGeneratedTypesBuilder(builder); + configureTypes(typesBuilder); + return builder; + } +} + +public class SourceGeneratedTypesBuilder +{ + public OptionsBuilder Builder { get; } + + public SourceGeneratedTypesBuilder(OptionsBuilder builder) + { + Builder = builder; + } +} + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class ObjectTypeAttribute: Attribute +{ +} \ No newline at end of file diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#PersonController.g.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#PersonController.g.verified.cs new file mode 100644 index 000000000..04976e764 --- /dev/null +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#PersonController.g.verified.cs @@ -0,0 +1,41 @@ +//HintName: PersonController.g.cs +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Tanka.GraphQL.Server; +using Tanka.GraphQL.Executable; +using Tanka.GraphQL.ValueResolution; +using Tanka.GraphQL.Fields; + + + +public static class PersonController +{ + public static ValueTask Name(ResolverContext context) + { + var objectValue = (Person)context.ObjectValue; + context.ResolvedValue = objectValue.Name; + return default; + } + + + + +} + +public static class PersonControllerExtensions +{ + public static SourceGeneratedTypesBuilder AddPersonController( + this SourceGeneratedTypesBuilder builder) + { + builder.Builder.Configure(options => options.Builder.Add( + "Person", + new FieldsWithResolvers() + { + { "name: String!", PersonController.Name } + + })); + + return builder; + } +} diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#QueryController.g.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#QueryController.g.verified.cs new file mode 100644 index 000000000..04a8e674e --- /dev/null +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#QueryController.g.verified.cs @@ -0,0 +1,43 @@ +//HintName: QueryController.g.cs +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Tanka.GraphQL.Server; +using Tanka.GraphQL.Executable; +using Tanka.GraphQL.ValueResolution; +using Tanka.GraphQL.Fields; + + + +public static class QueryController +{ + + + public static ValueTask Person(ResolverContext context) + { + context.ResolvedValue = Query.Person( + context.GetArgument("id") + ); + + return default; + } + + +} + +public static class QueryControllerExtensions +{ + public static SourceGeneratedTypesBuilder AddQueryController( + this SourceGeneratedTypesBuilder builder) + { + builder.Builder.Configure(options => options.Builder.Add( + "Query", + new FieldsWithResolvers() + { + { "person(id: Int!): Person!", QueryController.Person } + + })); + + return builder; + } +} diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_method_resolver#InputType.g.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_method_resolver#InputType.g.verified.cs new file mode 100644 index 000000000..7f21cfdc9 --- /dev/null +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_method_resolver#InputType.g.verified.cs @@ -0,0 +1,12 @@ +//HintName: InputType.g.cs +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Tanka.GraphQL.Executable; + +namespace Tanka.GraphQL.Server; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class InputTypeAttribute: Attribute +{ +} \ No newline at end of file diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_method_resolver#TestsControllerExtensions.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_method_resolver#TestsControllerExtensions.verified.cs new file mode 100644 index 000000000..fa3efa3ab --- /dev/null +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_method_resolver#TestsControllerExtensions.verified.cs @@ -0,0 +1,18 @@ +//HintName: TestsControllerExtensions.cs +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Tanka.GraphQL.Server; +using Tanka.GraphQL.Executable; +using Tanka.GraphQL.ValueResolution; +using Tanka.GraphQL.Fields; + +namespace Tests; +public static class TestsSourceGeneratedTypesExtensions +{ + public static SourceGeneratedTypesBuilder AddTestsControllers(this SourceGeneratedTypesBuilder builder) + { + builder.AddQueryController(); + return builder; + } +} diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_property_resolver#InputType.g.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_property_resolver#InputType.g.verified.cs new file mode 100644 index 000000000..7f21cfdc9 --- /dev/null +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_property_resolver#InputType.g.verified.cs @@ -0,0 +1,12 @@ +//HintName: InputType.g.cs +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Tanka.GraphQL.Executable; + +namespace Tanka.GraphQL.Server; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class InputTypeAttribute: Attribute +{ +} \ No newline at end of file diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_property_resolver#TestsControllerExtensions.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_property_resolver#TestsControllerExtensions.verified.cs new file mode 100644 index 000000000..fa3efa3ab --- /dev/null +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_property_resolver#TestsControllerExtensions.verified.cs @@ -0,0 +1,18 @@ +//HintName: TestsControllerExtensions.cs +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Tanka.GraphQL.Server; +using Tanka.GraphQL.Executable; +using Tanka.GraphQL.ValueResolution; +using Tanka.GraphQL.Fields; + +namespace Tests; +public static class TestsSourceGeneratedTypesExtensions +{ + public static SourceGeneratedTypesBuilder AddTestsControllers(this SourceGeneratedTypesBuilder builder) + { + builder.AddQueryController(); + return builder; + } +} From 661b867890dc938dc435d43ae238eede7b9862ac Mon Sep 17 00:00:00 2001 From: Pekka Heikura Date: Tue, 25 Apr 2023 14:12:44 +0300 Subject: [PATCH 2/2] Update snaps --- ...ts.Generate_InputType#InputMessageInputType.g.verified.cs | 1 + ...nputTypeFacts.Generate_InputType#ObjectType.g.verified.cs | 5 +++++ ...ts.Generate_ObjectType_type_name#ObjectType.g.verified.cs | 5 +++++ ...bjectType_type_name_no_namespace#ObjectType.g.verified.cs | 5 +++++ ...icClass_Generate_method_resolver#ObjectType.g.verified.cs | 5 +++++ ...Class_Generate_property_resolver#ObjectType.g.verified.cs | 5 +++++ 6 files changed, 26 insertions(+) diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#InputMessageInputType.g.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#InputMessageInputType.g.verified.cs index 034ff29a7..67aea8547 100644 --- a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#InputMessageInputType.g.verified.cs +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#InputMessageInputType.g.verified.cs @@ -22,6 +22,7 @@ input InputMessage content: String! } """ + ) ); return builder; diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#ObjectType.g.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#ObjectType.g.verified.cs index 5403dc4b3..c39935439 100644 --- a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#ObjectType.g.verified.cs +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/InputTypeFacts.Generate_InputType#ObjectType.g.verified.cs @@ -31,4 +31,9 @@ public SourceGeneratedTypesBuilder(OptionsBuilder builder) [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public class ObjectTypeAttribute: Attribute { +} + +[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class FromArgumentsAttribute: Attribute +{ } \ No newline at end of file diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name#ObjectType.g.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name#ObjectType.g.verified.cs index 5403dc4b3..c39935439 100644 --- a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name#ObjectType.g.verified.cs +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name#ObjectType.g.verified.cs @@ -31,4 +31,9 @@ public SourceGeneratedTypesBuilder(OptionsBuilder builder) [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public class ObjectTypeAttribute: Attribute { +} + +[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class FromArgumentsAttribute: Attribute +{ } \ No newline at end of file diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#ObjectType.g.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#ObjectType.g.verified.cs index 5403dc4b3..c39935439 100644 --- a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#ObjectType.g.verified.cs +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.Generate_ObjectType_type_name_no_namespace#ObjectType.g.verified.cs @@ -31,4 +31,9 @@ public SourceGeneratedTypesBuilder(OptionsBuilder builder) [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public class ObjectTypeAttribute: Attribute { +} + +[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class FromArgumentsAttribute: Attribute +{ } \ No newline at end of file diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_method_resolver#ObjectType.g.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_method_resolver#ObjectType.g.verified.cs index 5403dc4b3..c39935439 100644 --- a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_method_resolver#ObjectType.g.verified.cs +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_method_resolver#ObjectType.g.verified.cs @@ -31,4 +31,9 @@ public SourceGeneratedTypesBuilder(OptionsBuilder builder) [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public class ObjectTypeAttribute: Attribute { +} + +[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class FromArgumentsAttribute: Attribute +{ } \ No newline at end of file diff --git a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_property_resolver#ObjectType.g.verified.cs b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_property_resolver#ObjectType.g.verified.cs index 5403dc4b3..c39935439 100644 --- a/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_property_resolver#ObjectType.g.verified.cs +++ b/tests/GraphQL.Server.SourceGenerators.Tests/Snapshots/ObjectGeneratorFacts.StaticClass_Generate_property_resolver#ObjectType.g.verified.cs @@ -31,4 +31,9 @@ public SourceGeneratedTypesBuilder(OptionsBuilder builder) [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public class ObjectTypeAttribute: Attribute { +} + +[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public class FromArgumentsAttribute: Attribute +{ } \ No newline at end of file