├── .gitignore ├── .gitmodules ├── Client ├── Fireflies.GraphQL.Client.Console │ ├── ConsoleLogger.cs │ ├── Fireflies.GraphQL.Client.Console.csproj │ ├── Fireflies.GraphQL.Client.Console.nuspec │ ├── Generate │ │ ├── GenerateHandler.cs │ │ └── GenerateOptions.cs │ ├── Program.cs │ ├── ProjectInitHandler.cs │ ├── ProjectInitOptions.cs │ ├── Properties │ │ ├── Resources.Designer.cs │ │ ├── Resources.resx │ │ └── launchSettings.json │ ├── README.md │ ├── ResultCode.cs │ ├── Schema │ │ ├── ClientInitOptions.cs │ │ ├── ClientUpdateOptions.cs │ │ ├── ISchemaOptions.cs │ │ └── SchemaHandler.cs │ └── build.sh └── Fireflies.GraphQL.Client.Generator │ ├── ASTNodeExtensions.cs │ ├── Builders │ ├── ClientBuilder.cs │ ├── FieldMatch.cs │ ├── FragmentTypeBuilder.cs │ ├── ITypeBuilder.cs │ ├── OperationResultTypeBuilder.cs │ ├── RawTypeBuilder.cs │ ├── ResultTypeBuilderBase.cs │ ├── SchemaFieldExtensions.cs │ ├── SubResultTypeBuilder.cs │ └── TypeBuilder.cs │ ├── ClientGenerator.cs │ ├── ClientSettings.cs │ ├── Fireflies.GraphQL.Client.Generator.csproj │ ├── FragmentAccessor.cs │ ├── GeneratorSettings.cs │ ├── GraphQLGeneratorContext.cs │ ├── GraphQLGeneratorException.cs │ ├── GraphQLRootGeneratorContext.cs │ ├── QueryCreator.cs │ ├── Schema │ ├── SchemaEnumValue.cs │ ├── SchemaField.cs │ ├── SchemaHelper.cs │ ├── SchemaInputValue.cs │ ├── SchemaType.cs │ └── SchemaTypeKind.cs │ ├── Shared │ ├── Builder │ │ ├── HttpBuilder.cs │ │ └── WebSocketBuilder.cs │ ├── Error │ │ ├── ClientError.cs │ │ ├── IClientError.cs │ │ └── Location.cs │ ├── GraphQLGlobalContext.cs │ ├── GraphQLInt.cs │ ├── GraphQLIntConverter.cs │ ├── IGraphQLClient.cs │ ├── IGraphQLGlobalContext.cs │ ├── IOperationResult.cs │ └── Subscription │ │ ├── ConnectionNotAcceptedException.cs │ │ ├── GraphQLSubscriber.cs │ │ ├── GraphQLSubscription.cs │ │ ├── GraphQLWsClient.cs │ │ ├── SocketClosedException.cs │ │ └── SubscriptionException.cs │ ├── SharedGenerator.cs │ └── TypeMapper.cs ├── Demos ├── Client │ └── Fireflies.GraphQL.Demos.Client.Demo │ │ ├── Fireflies.GraphQL.ClientDemo.csproj.DotSettings │ │ ├── Fireflies.GraphQL.Demos.Client.Demo.csproj │ │ ├── GraphQL │ │ ├── GraphQLDemo │ │ │ ├── GraphQLDemoClient.g.cs │ │ │ ├── Queries.graphql │ │ │ ├── Schema.json │ │ │ └── Settings.json │ │ ├── GraphQLShared.g.cs │ │ └── Settings.json │ │ └── Program.cs └── Server │ ├── Fireflies.GraphQL.Demos.Server.Api │ ├── Fireflies.GraphQL.Demos.Server.Api.csproj │ ├── Program.cs │ └── Properties │ │ └── launchSettings.json │ ├── Fireflies.GraphQL.Demos.Server.Authors │ ├── Authors │ │ ├── AuthorOperations.cs │ │ ├── AuthorsFilter.cs │ │ ├── IAuthor.cs │ │ ├── IBook.cs │ │ ├── MustBeAllowedToUpdateAuthorAttribute.cs │ │ ├── PseudnonymAuthor.cs │ │ ├── RealAuthor.cs │ │ └── UpdateAuthorInput.cs │ ├── Fireflies.GraphQL.Demos.Server.Authors.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── RequestContainerBuilder.cs │ ├── User.cs │ ├── appsettings.Development.json │ └── appsettings.json │ └── Fireflies.GraphQL.Demos.Server.Books │ ├── AddBookInput.cs │ ├── Blogs │ ├── Blog.cs │ ├── BlogOperations.cs │ ├── BloggingContext.cs │ └── Post.cs │ ├── Books │ ├── BookOperations.cs │ ├── Edition.cs │ ├── IBook.cs │ ├── InventoryBook.cs │ └── RemoteBook.cs │ ├── Fireflies.GraphQL.Demos.Server.Books.csproj │ ├── Migrations │ ├── 20230302101823_InitialCreate.Designer.cs │ ├── 20230302101823_InitialCreate.cs │ ├── BloggingContextModelSnapshot.cs │ └── InitialCreate.cs │ ├── MustBeSalesmanAttribute.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── RequestContainerBuilder.cs │ ├── StringFilterOperatorInput.cs │ ├── User.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── Extensions └── Fireflies.GraphQL.Extensions.EntityFrameworkCore │ ├── EntityFrameworkCoreExtensionsBuilder.cs │ ├── EntityFrameworkCoreGraphQLOptionsBuilder.cs │ ├── EntityFrameworkCoreMethodExtender.cs │ ├── Fireflies.GraphQL.Extensions.EntityFrameworkCore.csproj │ ├── Fireflies.GraphQL.Extensions.EntityFrameworkCore.nuspec │ └── README.md ├── Fireflies.GraphQL.Abstractions ├── Authorization │ ├── GraphQLAuthorizationAttribute.cs │ └── GraphQLAuthorizationBaseAttribute.cs ├── Connection │ └── GraphQlPaginationAttribute.cs ├── Fireflies.GraphQL.Abstractions.csproj ├── Fireflies.GraphQL.Abstractions.nuspec ├── Generator │ ├── GraphQLGeneratorAttribute.cs │ ├── GraphQLNoWrapperAttribute.cs │ └── GraphQLNullable.cs ├── GraphQLAttribute.cs ├── GraphQLId.cs ├── GraphQLInternalAttribute.cs ├── GraphQLMutationAttribute.cs ├── GraphQLOperationAttribute.cs ├── GraphQLParallel.cs ├── GraphQLQueryAttribute.cs ├── GraphQLSubscriptionAttribute.cs ├── GraphQlIdAttribute.cs ├── README.md ├── Schema │ ├── GraphQLDeprecatedAttribute.cs │ ├── GraphQLDescriptionAttribute.cs │ └── GraphQLUnionAttribute.cs ├── Sorting │ ├── GraphQLSortAttribute.cs │ └── SortOrder.cs └── Where │ ├── BooleanWhere.cs │ ├── CollectionWhere.cs │ ├── DateTimeWhere.cs │ ├── DecimalWhere.cs │ ├── GraphQLWhereAttribute.cs │ ├── IntWhere.cs │ ├── NumberWhere.cs │ ├── StringWhere.cs │ └── Where.cs ├── Fireflies.GraphQL.AspNet ├── AspNetConnectionContext.cs ├── Fireflies.GraphQL.AspNet.csproj ├── Fireflies.GraphQL.AspNet.nuspec ├── GraphQLMiddleware.cs ├── GraphQLMiddlewareExtensions.cs ├── GraphQLWsProtocolHandler.cs ├── README.md └── WsProtocolHandlerBase.cs ├── Fireflies.GraphQL.Core ├── ArgumentBuilder.cs ├── AuthorizationHelper.cs ├── Exceptions │ ├── DuplicateNameException.cs │ ├── GraphQLException.cs │ ├── GraphQLTypeException.cs │ └── GraphQLUnauthorizedException.cs ├── ExtensionRegistry.cs ├── Extensions │ ├── AsyncParallelData.cs │ ├── EmitExtensions.cs │ ├── EnumerableExtensions.cs │ ├── QueryableExtensions.cs │ └── ReflectionExtensions.cs ├── Federation │ ├── FederatedQuery.cs │ ├── FederationBase.cs │ ├── FederationClient.cs │ ├── FederationException.cs │ ├── FederationExecutionException.cs │ ├── FederationGenerator.cs │ ├── FederationHelper.cs │ ├── FederationQueryBuilder.cs │ ├── FederationWebsocket.cs │ ├── GraphQLFederatedAttribute.cs │ └── Schema │ │ ├── FederationField.cs │ │ ├── FederationFieldBase.cs │ │ ├── FederationInputValue.cs │ │ ├── FederationSchema.cs │ │ └── FederationType.cs ├── Fireflies.GraphQL.Core.csproj ├── Fireflies.GraphQL.Core.nuspec ├── FragmentAccessor.cs ├── Generators │ ├── BaseDescriptor.cs │ ├── Connection │ │ ├── ConnectionBase.cs │ │ ├── ConnectionGenerator.cs │ │ ├── EdgeBase.cs │ │ └── PageInfo.cs │ ├── GeneratorRegistry.cs │ ├── IGenerator.cs │ ├── IMethodExtenderGenerator.cs │ ├── ITypeExtenderGenerator.cs │ ├── MethodExtenderDescriptor.cs │ ├── MethodExtenderStep.cs │ ├── Sorting │ │ ├── SortingGenerator.cs │ │ └── SortingHelper.cs │ └── Where │ │ ├── BooleanWhereExpressionHelper.cs │ │ ├── CollectionWhereExpressionBuilder.cs │ │ ├── EnumerableWhereBuilder.cs │ │ ├── NumberWhereExpressionHelper.cs │ │ ├── QueryableWhereBuilder.cs │ │ ├── StringWhereExpressionHelper.cs │ │ ├── WhereExpressionBuilder.cs │ │ ├── WhereExpressionBuilderHelper.cs │ │ ├── WhereGenerator.cs │ │ └── WhereHelper.cs ├── GraphQLEngine.cs ├── GraphQLOptions.cs ├── GraphQLOptionsBuilder.cs ├── GraphQLPath.cs ├── GraphQLRequest.cs ├── IConnectionContext.cs ├── IExtensionBuilder.cs ├── IGraphQLPath.cs ├── IRequestContainerBuilder.cs ├── IRequestContext.cs ├── IResultBuilder.cs ├── IWsProtocolHandler.cs ├── Json │ ├── DefaultJsonSerializerSettings.cs │ ├── GraphQLError.cs │ ├── GraphQLRawError.cs │ ├── IErrorCollection.cs │ ├── IGraphQLError.cs │ ├── JsonWriter.cs │ ├── JsonWriterFactory.cs │ ├── JsonWriterMetadata.cs │ └── ResultJsonWriter.cs ├── NullabilityChecker.cs ├── OperationContext.cs ├── OperationDescriptor.cs ├── OperationVisitor.cs ├── README.md ├── ReflectionCache.cs ├── RequestContext.cs ├── RequestValidator.cs ├── ResolvedAttribute.cs ├── ResultBuilder.cs ├── ResultContext.cs ├── ResultVisitor.cs ├── Scalar │ ├── BooleanScalarHandler.cs │ ├── DateTimeOffsetScalarHandler.cs │ ├── DateTimeScalarHandler.cs │ ├── FloatScalarHandler.cs │ ├── IScalarHandler.cs │ ├── IntScalarHandler.cs │ ├── ObjectScalarHandler.cs │ ├── ScalarRegistry.cs │ ├── StringScalarHandler.cs │ ├── TimeSpanScalarHandler.cs │ ├── UIntScalarHandler.cs │ └── VersionScalarHandler.cs ├── Schema │ ├── SchemaBuilder.cs │ ├── TypeRegistry.cs │ ├── __Directive.cs │ ├── __DirectiveLocation.cs │ ├── __EnumValue.cs │ ├── __Field.cs │ ├── __InputValue.cs │ ├── __Schema.cs │ ├── __SchemaQuery.cs │ ├── __Type.cs │ └── __TypeKind.cs ├── SchemaValidator.cs ├── ValueAccessor.cs ├── WrapperGenerator.cs ├── WrapperHelper.cs └── WrapperRegistry.cs ├── Fireflies.GraphQL.sln ├── Fireflies.GraphQL.sln.DotSettings ├── Images ├── GraphQL.ai ├── Icon-128x128.png ├── Icon.ai └── Organization.ai ├── LoadTest ├── LoadTest.csproj └── Program.cs ├── README.md └── build.sh /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Fireflies.Shared"] 2 | path = Fireflies.Shared 3 | url = git@github.com:firefliestech/Fireflies.Shared.git 4 | -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Console/ConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Client.Console; 2 | 3 | public static class ConsoleLogger { 4 | public static void WriteInfo(string message) { 5 | System.Console.WriteLine(message); 6 | } 7 | 8 | public static void WriteWarning(string message) { 9 | var originalColor = System.Console.ForegroundColor; 10 | System.Console.ForegroundColor = ConsoleColor.Yellow; 11 | System.Console.WriteLine(message); 12 | System.Console.ForegroundColor = originalColor; 13 | } 14 | 15 | public static void WriteSuccess(string message) { 16 | var originalColor = System.Console.ForegroundColor; 17 | System.Console.ForegroundColor = ConsoleColor.Green; 18 | System.Console.WriteLine(message); 19 | System.Console.ForegroundColor = originalColor; 20 | } 21 | 22 | public static void WriteError(Exception exception, string message) { 23 | var originalColor = System.Console.ForegroundColor; 24 | System.Console.ForegroundColor = ConsoleColor.Red; 25 | System.Console.WriteLine($"{message}\r\n{exception}"); 26 | System.Console.ForegroundColor = originalColor; 27 | } 28 | 29 | public static void WriteError(string message) { 30 | var originalColor = System.Console.ForegroundColor; 31 | System.Console.ForegroundColor = ConsoleColor.Red; 32 | System.Console.WriteLine(message); 33 | System.Console.ForegroundColor = originalColor; 34 | } 35 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Console/Fireflies.GraphQL.Client.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | true 12 | fireflies-graphql 13 | 14 | 15 | 16 | Fireflies.Tech 17 | Fireflies Client Generator 18 | 2023 Fireflies.tech 19 | MIT 20 | README.md 21 | Icon-128x128.png 22 | https://github.com/firefliestech/fireflies.graphql 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | True 41 | True 42 | Resources.resx 43 | 44 | 45 | 46 | 47 | 48 | ResXFileCodeGenerator 49 | Resources.Designer.cs 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Console/Fireflies.GraphQL.Client.Console.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | false 9 | $description$ 10 | $copyright$ 11 | fireflies graphql client generator 12 | 13 | -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Console/Generate/GenerateOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | namespace Fireflies.GraphQL.Client.Console.Generate; 4 | 5 | [Verb("generate", HelpText = "Generates the client")] 6 | public class GenerateOptions { 7 | [Option('p', "path", Required = true, HelpText = "Path to project")] 8 | public string Path { get; set; } 9 | 10 | [Option('f', "force", Required = false, HelpText = "Force generation even if no changes were detected")] 11 | public bool Force { get; set; } 12 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using CommandLine.Text; 3 | using Fireflies.GraphQL.Client.Console; 4 | using Fireflies.GraphQL.Client.Console.Generate; 5 | using Fireflies.GraphQL.Client.Console.Schema; 6 | 7 | var parser = new Parser(with => with.HelpWriter = null); 8 | var parseResult = parser.ParseArguments(args); 9 | 10 | DisplayHelp(parseResult); 11 | 12 | parseResult.WithParsed(_ => { 13 | parseResult.MapResult((ProjectInitOptions o) => RunProjectInit(o), 14 | (ClientInitOptions o) => RunClientInit(o), 15 | (ClientUpdateOptions o) => RunClientUpdate(o), 16 | (GenerateOptions o) => RunGenerate(o), 17 | _ => 1); 18 | }); 19 | 20 | int RunProjectInit(ProjectInitOptions options) { 21 | var handler = new ProjectInitHandler(); 22 | return (int)handler.Init(options).GetAwaiter().GetResult(); 23 | } 24 | 25 | int RunClientUpdate(ClientUpdateOptions options) { 26 | var handler = new SchemaHandler(); 27 | return (int)handler.Update(options).GetAwaiter().GetResult(); 28 | } 29 | 30 | int RunClientInit(ClientInitOptions options) { 31 | var handler = new SchemaHandler(); 32 | return (int)handler.Init(options).GetAwaiter().GetResult(); 33 | } 34 | 35 | int RunGenerate(GenerateOptions options) { 36 | var handler = new GenerateHandler(); 37 | return (int)handler.Generate(options).GetAwaiter().GetResult(); 38 | } 39 | 40 | static void DisplayHelp(ParserResult result) { 41 | if(!result.Errors.Any()) { 42 | ConsoleLogger.WriteInfo("Fireflies GraphQL Client Generator"); 43 | ConsoleLogger.WriteInfo("=================================="); 44 | } else { 45 | var helpText = HelpText.AutoBuild(result, h => { 46 | h.AdditionalNewLineAfterOption = false; 47 | h.Heading = "Fireflies GraphQL Client Generator"; 48 | h.Copyright = "=================================="; 49 | return HelpText.DefaultParsingErrorsHandler(result, h); 50 | }, e => e); 51 | 52 | ConsoleLogger.WriteInfo(helpText); 53 | } 54 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Console/ProjectInitHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Nodes; 2 | 3 | namespace Fireflies.GraphQL.Client.Console; 4 | 5 | public class ProjectInitHandler { 6 | public async Task Init(ProjectInitOptions options) { 7 | ConsoleLogger.WriteInfo($"Initializing project\r\n- Namespace. {options.Namespace}\r\n- Path: {options.Path}\r\n"); 8 | 9 | var rootPath = Path.Combine(options.Path, "GraphQL"); 10 | if(!Directory.Exists(rootPath)) { 11 | Directory.CreateDirectory(rootPath); 12 | } else if(!options.Force) { 13 | ConsoleLogger.WriteError($"Project is already initialized. Use --force to reinitialize"); 14 | return ResultCode.AlreadyInitialized; 15 | } 16 | 17 | var generatorSettingsFile = Path.Combine(rootPath, "Settings.json"); 18 | var generatorSettings = new JsonObject { 19 | ["namespace"] = options.Namespace 20 | }; 21 | 22 | if(options.ServiceCollection) 23 | generatorSettings["service-collection"] = true; 24 | 25 | await File.WriteAllTextAsync(generatorSettingsFile, generatorSettings.ToJsonString()); 26 | 27 | return ResultCode.Success; 28 | } 29 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Console/ProjectInitOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | namespace Fireflies.GraphQL.Client.Console; 4 | 5 | [Verb("init", HelpText = "Initializes a project")] 6 | public class ProjectInitOptions { 7 | [Option('p', "path", Required = true, HelpText = "Path to project")] 8 | public string Path { get; set; } 9 | 10 | [Option('n', "namespace", Required = true, HelpText = "Namespace of generated client")] 11 | public string Namespace { get; set; } 12 | 13 | [Option("service-collection", Required = false, HelpText = "Generates ServiceCollection.Add{Name}Client() extension method")] 14 | public bool ServiceCollection { get; set; } 15 | 16 | [Option('f', "force", Required = false, HelpText = "Forces reinitialization")] 17 | public bool Force { get; set; } 18 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Console/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Fireflies.GraphQL.Client.Console.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Fireflies.GraphQL.Client.Console.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Console/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Fireflies.GraphQL.Client.Console": { 4 | "commandName": "Project", 5 | "commandLineArgs": "generate --path C:\\Projects\\Fireflies\\Fireflies.GraphQL\\Demos\\Fireflies.GraphQL.ClientDemo\\ --force" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Console/README.md: -------------------------------------------------------------------------------- 1 | # Fireflies GraphQL client generator 2 | 3 | The generated generates files from a schema and does not add external dependencies to your project. 4 | 5 | ## Install the tool 6 | 7 | ### Globally 8 | ``` 9 | dotnet tool install --global Fireflies.GraphQL.Client.Console 10 | ``` 11 | 12 | ### Locally 13 | ``` 14 | dotnet tool install Fireflies.GraphQL.Client.Console 15 | ``` 16 | 17 | ## Init a project 18 | Basically creates a GraphQL in your project and add general generation settings to Settings.json. 19 | ``` 20 | fireflies-graphql init --path path-to-root-of-project --namespace MyProject.Api.GraphQL --service-collection 21 | ``` 22 | 23 | ## Init a client 24 | Downloads the schema and stores it into a sub-folder inside the GraphQL. 25 | ``` 26 | fireflies-graphql add --name MyProject --path path-to-project --uri https://localhost:7273/graphql 27 | ``` 28 | 29 | ## Update the schema 30 | ``` 31 | fireflies-graphql update --name MyProject --path path-to-project 32 | ``` 33 | 34 | ## Add your GraphQL queries 35 | Under the GraphQL\MyProject folder you can now add .graphql files that will be used to generate the client. 36 | ``` 37 | query GetBook($bookId: Int!) { 38 | getBook(bookId: $bookId) { 39 | ...TitleFragment 40 | } 41 | } 42 | 43 | fragment TitleFragment on IBook { 44 | title 45 | ... on InventoryBook { 46 | calculatePrice 47 | editions { 48 | name 49 | released 50 | } 51 | } 52 | ... TestFragment 53 | } 54 | 55 | fragment TestFragment on RemoteBook { 56 | test 57 | } 58 | 59 | subscription BookUpdated($bookId: Int!) { 60 | bookUpdated(bookId: $bookId) { 61 | title 62 | bookId 63 | __typename 64 | } 65 | } 66 | ``` 67 | 68 | ## Generate all clients 69 | ``` 70 | fireflies-graphql generate --path path-to-project 71 | ``` 72 | 73 | ## Using the client 74 | 75 | ### Setup the client 76 | ``` 77 | var client = new MyProjectClient(b => { 78 | b.Uri = new Uri("https://localhost:7273/graphql"); 79 | }, b => { 80 | b.Uri = new Uri("wss://localhost:7273/graphql"); 81 | b.ReconnectDelay = TimeSpan.FromSeconds(5); 82 | }); 83 | ``` 84 | 85 | ### Performing a query 86 | ``` 87 | var bookResult = await client.GetBook(123); 88 | var book = bookResult.Data; 89 | ``` 90 | 91 | ### Subscribe to a subscription 92 | ``` 93 | var watcher = await client.BookUpdated(123).Watch(m => { 94 | Console.WriteLine($"{m.Data.BookUpdated.BookId} was updated {m.Data.BookUpdated.Title}"); 95 | }); 96 | ``` 97 | 98 | Once you´re done with your subscription make sure to dispose it. 99 | ``` 100 | await watcher.DisposeAsync(); 101 | ``` 102 | 103 | _Logo by freepik_ -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Console/ResultCode.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Client.Console; 2 | 3 | public enum ResultCode { 4 | Success, 5 | GenerationFailed, 6 | PathDoesNotExist, 7 | ProjectFileNotFound, 8 | ClientAlreadyExists, 9 | FailedToDownloadSchema, 10 | GraphQLDirectoryNotFound, 11 | ClientDirectoryNotFound, 12 | NotNeeded, 13 | AlreadyInitialized 14 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Console/Schema/ClientInitOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | namespace Fireflies.GraphQL.Client.Console.Schema; 4 | 5 | [Verb("add", HelpText = "Adds a new client")] 6 | public class ClientInitOptions : ISchemaOptions { 7 | [Option('n', "name", Required = true, HelpText = "Client name")] 8 | public string Name { get; set; } 9 | 10 | [Option('p', "path", Required = true, HelpText = "Path to project")] 11 | public string Path { get; set; } 12 | 13 | [Option('u', "uri", Required = true, HelpText = "Url to GraphQL service")] 14 | public string Uri { get; set; } 15 | 16 | [Option('f', "force", Required = false, HelpText = "Forces reinitialization")] 17 | public bool Force { get; set; } 18 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Console/Schema/ClientUpdateOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | namespace Fireflies.GraphQL.Client.Console.Schema; 4 | 5 | [Verb("update", HelpText = "Updates the schema")] 6 | public class ClientUpdateOptions : ISchemaOptions { 7 | [Option('n', "name", Required = true, HelpText = "Client name")] 8 | public string Name { get; set; } 9 | 10 | [Option('p', "path", Required = true, HelpText = "Path to project")] 11 | public string Path { get; set; } 12 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Console/Schema/ISchemaOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Client.Console.Schema; 2 | 3 | public interface ISchemaOptions { 4 | string Name { get; set; } 5 | string Path { get; set; } 6 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Console/build.sh: -------------------------------------------------------------------------------- 1 | VERSION=$1 2 | ROOT_PATH=$(pwd) 3 | 4 | dotnet pack -p:PackageVersion=$VERSION --output $ROOT_PATH/nupkgs 5 | -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/ASTNodeExtensions.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | 3 | namespace Fireflies.GraphQL.Client.Generator; 4 | 5 | public static class ASTNodeExtensions { 6 | public static ICollection GetSelections(this ASTNode node) { 7 | if(node is IHasSelectionSetNode selectionNode) 8 | return selectionNode.SelectionSet?.Selections ?? new List(); 9 | 10 | return new List(); 11 | } 12 | 13 | public static string Capitalize(this string name) { 14 | return char.ToUpper(name[0]) + name[1..]; 15 | } 16 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Builders/FieldMatch.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Client.Generator.Schema; 2 | using GraphQLParser.AST; 3 | 4 | namespace Fireflies.GraphQL.Client.Generator.Builders; 5 | 6 | public record struct FieldMatch(bool IsSelected, ASTNode? SelectedByNode, GraphQLFragmentDefinition? DefinedByFragment, SchemaType? ConditionType, SchemaType? FoundOnType); -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Builders/FragmentTypeBuilder.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Client.Generator.Schema; 2 | using GraphQLParser.AST; 3 | 4 | namespace Fireflies.GraphQL.Client.Generator.Builders; 5 | 6 | public class FragmentTypeBuilder { 7 | private readonly GraphQLFragmentDefinition _fragmentDefinition; 8 | private readonly GraphQLGeneratorContext _context; 9 | 10 | public FragmentTypeBuilder(GraphQLFragmentDefinition fragmentDefinition, GraphQLGeneratorContext context) { 11 | _fragmentDefinition = fragmentDefinition; 12 | _context = context; 13 | } 14 | 15 | public async Task Build() { 16 | var fragmentTypeName = _fragmentDefinition.FragmentName.Name.StringValue; 17 | 18 | if(!_context.RootContext.ShouldGenerateType(fragmentTypeName)) 19 | return fragmentTypeName; 20 | 21 | var fragmentSchemaType = _context.GetSchemaType(_fragmentDefinition.TypeCondition.Type); 22 | var subResultTypeBuilder = new SubResultTypeBuilder(fragmentTypeName, _fragmentDefinition, null, fragmentSchemaType, _context); 23 | subResultTypeBuilder.OnlyInterface(); 24 | await subResultTypeBuilder.Build(); 25 | 26 | foreach(var subSchemaType in fragmentSchemaType.PossibleTypes.Select(x => x.GetOfType(_context))) { 27 | var subTypeName = $"{fragmentTypeName}_{subSchemaType.Name.Capitalize()}"; 28 | var subFragmentBuilder = new SubResultTypeBuilder(subTypeName, _fragmentDefinition, fragmentSchemaType, subSchemaType, _context); 29 | subFragmentBuilder.AddInterfaceImplementation($"I{_fragmentDefinition.FragmentName.Name.StringValue}"); 30 | subFragmentBuilder.OnlyInterface(); 31 | subFragmentBuilder.ExactTypeConditionRequired(); 32 | await subFragmentBuilder.Build(); 33 | } 34 | 35 | return fragmentTypeName; 36 | } 37 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Builders/ITypeBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Client.Generator.Builders; 2 | 3 | public interface ITypeBuilder { 4 | public Task Build(); 5 | public string Source(); 6 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Builders/OperationResultTypeBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Fireflies.GraphQL.Client.Generator.Schema; 3 | using GraphQLParser.AST; 4 | 5 | namespace Fireflies.GraphQL.Client.Generator.Builders; 6 | 7 | public class OperationResultTypeBuilder : ResultTypeBuilderBase, ITypeBuilder { 8 | private readonly string _typeName; 9 | private readonly GraphQLOperationDefinition _operationDefinition; 10 | private readonly SchemaType _schemaType; 11 | private readonly GraphQLGeneratorContext _context; 12 | private readonly StringBuilder _stringBuilder = new(); 13 | 14 | public OperationResultTypeBuilder(string typeName, GraphQLOperationDefinition operationDefinition, SchemaType? parentType, SchemaType schemaType, GraphQLGeneratorContext context) : base(typeName + "Result", operationDefinition, parentType, schemaType, context) { 15 | _typeName = typeName; 16 | _operationDefinition = operationDefinition; 17 | _schemaType = schemaType; 18 | _context = context; 19 | } 20 | 21 | public string Source() { 22 | return _stringBuilder.ToString(); 23 | } 24 | 25 | public async Task Build() { 26 | TypeBuilder.AddOperationProperties(); 27 | 28 | var dataName = $"{_typeName}Data"; 29 | TypeBuilder.AddInterfaceImplementation($"IOperationResult"); 30 | TypeBuilder.AddProperty(dataName, "Data", new SchemaField { Type = _schemaType, Name = "data" }, new FieldMatch { FoundOnType = new SchemaType() }); 31 | 32 | var dataTypeBuilder = new SubResultTypeBuilder(dataName, _operationDefinition, null, _schemaType, _context); 33 | await dataTypeBuilder.Build(); 34 | 35 | await TypeBuilder.Build(); 36 | } 37 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Builders/RawTypeBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace Fireflies.GraphQL.Client.Generator.Builders; 4 | 5 | public class RawTypeBuilder : ITypeBuilder { 6 | private readonly StringBuilder _stringBuilder; 7 | 8 | public RawTypeBuilder() { 9 | _stringBuilder = new StringBuilder(); 10 | } 11 | 12 | public void Append(string value) { 13 | _stringBuilder.Append(value); 14 | } 15 | 16 | public void AppendLine() { 17 | _stringBuilder.AppendLine(); 18 | } 19 | 20 | public void AppendLine(string value) { 21 | _stringBuilder.AppendLine(value); 22 | } 23 | 24 | public Task Build() { 25 | return Task.CompletedTask; 26 | } 27 | 28 | public string Source() { 29 | return _stringBuilder.ToString(); 30 | } 31 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Builders/SubResultTypeBuilder.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Client.Generator.Schema; 2 | using GraphQLParser.AST; 3 | 4 | namespace Fireflies.GraphQL.Client.Generator.Builders; 5 | 6 | public class SubResultTypeBuilder : ResultTypeBuilderBase { 7 | public SubResultTypeBuilder(string typeName, ASTNode astNode, SchemaType? parentType, SchemaType schemaType, GraphQLGeneratorContext context) : base(typeName, astNode, parentType, schemaType, context) { 8 | } 9 | 10 | public async Task Build() { 11 | await GenerateProperties(); 12 | 13 | await TypeBuilder.Build(); 14 | } 15 | 16 | public void AddInterfaceImplementation(string interf) { 17 | TypeBuilder.AddInterfaceImplementation(interf); 18 | } 19 | 20 | public void OnlyInterface() { 21 | TypeBuilder.OnlyInterface(); 22 | } 23 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/ClientSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Client.Generator; 2 | 3 | public class ClientSettings { 4 | public string Uri { get; set; } 5 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Fireflies.GraphQL.Client.Generator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | latest 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/FragmentAccessor.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLParser.Visitors; 3 | 4 | namespace Fireflies.GraphQL.Client.Generator; 5 | 6 | public class FragmentAccessor { 7 | private readonly GraphQLDocument _document; 8 | private Dictionary? _fragments; 9 | 10 | private static readonly FragmentVisitor FragmentVisitorInstance; 11 | 12 | static FragmentAccessor() { 13 | FragmentVisitorInstance = new FragmentVisitor(); 14 | } 15 | 16 | public FragmentAccessor(GraphQLDocument document) { 17 | _document = document; 18 | } 19 | 20 | public async Task GetFragment(GraphQLFragmentName fragmentName) { 21 | var fragments = await LoadFragments(); 22 | return fragments[fragmentName.Name.StringValue]; 23 | } 24 | 25 | private async Task> LoadFragments() { 26 | if(_fragments == null) { 27 | var context = new FragmentVisitorContext(); 28 | await FragmentVisitorInstance.VisitAsync(_document, context).ConfigureAwait(false); 29 | _fragments = context.FragmentDefinitions.ToDictionary(x => x.FragmentName.Name.StringValue); 30 | } 31 | 32 | return _fragments; 33 | } 34 | 35 | private class FragmentVisitor : ASTVisitor { 36 | #pragma warning disable CS1998 37 | protected override async ValueTask VisitFragmentDefinitionAsync(GraphQLFragmentDefinition fragmentDefinition, FragmentVisitorContext context) { 38 | context.FragmentDefinitions.Add(fragmentDefinition); 39 | } 40 | #pragma warning restore CS1998 41 | } 42 | 43 | private class FragmentVisitorContext : IASTVisitorContext { 44 | public List FragmentDefinitions { get; } = new(); 45 | public CancellationToken CancellationToken => CancellationToken.None; 46 | } 47 | 48 | public async Task> GetAll() { 49 | var fragments = await LoadFragments(); 50 | return fragments.Values; 51 | } 52 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/GeneratorSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Client.Generator; 2 | 3 | public class GeneratorSettings { 4 | public string Namespace { get; set; } 5 | public bool ServiceCollection { get; set; } 6 | 7 | public bool IsValid => !string.IsNullOrWhiteSpace(Namespace); 8 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/GraphQLGeneratorContext.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Client.Generator.Schema; 2 | using GraphQLParser.AST; 3 | using GraphQLParser.Visitors; 4 | 5 | namespace Fireflies.GraphQL.Client.Generator; 6 | 7 | public class GraphQLGeneratorContext : IASTVisitorContext { 8 | public GraphQLRootGeneratorContext RootContext { get; } 9 | 10 | public GraphQLDocument Document { get; } 11 | public FragmentAccessor FragmentAccessor { get; } 12 | 13 | public CancellationToken CancellationToken => RootContext.CancellationToken; 14 | 15 | public GraphQLGeneratorContext(GraphQLRootGeneratorContext rootContext, GraphQLDocument document, FragmentAccessor fragmentAccessor) { 16 | RootContext = rootContext; 17 | Document = document; 18 | FragmentAccessor = fragmentAccessor; 19 | } 20 | 21 | public SchemaType GetSchemaType(string type) { 22 | return RootContext.SchemaTypes[type]; 23 | } 24 | 25 | public SchemaType GetSchemaType(GraphQLNamedType namedType) { 26 | return GetSchemaType(namedType.Name.StringValue); 27 | } 28 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/GraphQLGeneratorException.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Client.Generator; 2 | 3 | public class GraphQLGeneratorException : Exception { 4 | public GraphQLGeneratorException(string message) : base(message) { 5 | } 6 | 7 | public GraphQLGeneratorException(string message, Exception innerException) : base(message, innerException) { 8 | } 9 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/GraphQLRootGeneratorContext.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Nodes; 3 | using System.Text.Json.Serialization; 4 | using Fireflies.GraphQL.Client.Generator.Builders; 5 | using Fireflies.GraphQL.Client.Generator.Schema; 6 | using GraphQLParser.AST; 7 | using GraphQLParser.Visitors; 8 | 9 | namespace Fireflies.GraphQL.Client.Generator; 10 | 11 | public class GraphQLRootGeneratorContext : IASTVisitorContext { 12 | private readonly HashSet _generatedTypes = new(); 13 | private readonly List _typeBuilders = new(); 14 | 15 | public Dictionary SchemaTypes { get; } 16 | public CancellationToken CancellationToken { get; } 17 | 18 | public string? SubscriptionType { get; set; } 19 | public string? MutationType { get; set; } 20 | public string? QueryType { get; set; } 21 | 22 | public string Source => string.Join("\r\n", _typeBuilders.Select(x => x.Source())); 23 | 24 | public GraphQLRootGeneratorContext(JsonNode schema) { 25 | QueryType = schema["queryType"]?["name"]?.GetValue(); 26 | MutationType = schema["mutationType"]?["name"]?.GetValue(); 27 | SubscriptionType = schema["subscriptionType"]?["name"]?.GetValue(); 28 | SchemaTypes = schema["types"].Deserialize>(new JsonSerializerOptions { PropertyNameCaseInsensitive = true, Converters = { new JsonStringEnumConverter() } }).ToDictionary(x => x.Name); 29 | } 30 | 31 | public bool ShouldGenerateType(string type) { 32 | return _generatedTypes.Add(type); 33 | } 34 | 35 | public RawTypeBuilder GetRawTypeBuilder() { 36 | var builder = new RawTypeBuilder(); 37 | _typeBuilders.Add(builder); 38 | return builder; 39 | } 40 | 41 | public ClientBuilder GetClientBuilder(string clientName) { 42 | var builder = new ClientBuilder(clientName); 43 | _typeBuilders.Add(builder); 44 | return builder; 45 | } 46 | 47 | public OperationResultTypeBuilder GetOperationResultTypeBuilder(string typeName, GraphQLOperationDefinition operationDefinition, GraphQLGeneratorContext context) { 48 | var schemaType = GetSchemaType(operationDefinition, context); 49 | var builder = new OperationResultTypeBuilder(typeName, operationDefinition, null, schemaType, context); 50 | _typeBuilders.Add(builder); 51 | return builder; 52 | } 53 | 54 | private static SchemaType GetSchemaType(GraphQLOperationDefinition operationDefinition, GraphQLGeneratorContext context) { 55 | return context.GetSchemaType(operationDefinition.Operation switch { 56 | OperationType.Query => context.RootContext.QueryType!, 57 | OperationType.Mutation => context.RootContext.MutationType!, 58 | OperationType.Subscription => context.RootContext.SubscriptionType!, 59 | _ => throw new ArgumentOutOfRangeException(nameof(operationDefinition.Operation), operationDefinition, null) 60 | }); 61 | } 62 | 63 | public TypeBuilder GetTypeBuilder(string typeName, ASTNode astNode, GraphQLGeneratorContext context) { 64 | var builder = new TypeBuilder(typeName, astNode, context); 65 | _typeBuilders.Add(builder); 66 | return builder; 67 | } 68 | 69 | public SchemaType GetSchemaType(string type) { 70 | return SchemaTypes[type]; 71 | } 72 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/QueryCreator.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLParser.Visitors; 3 | 4 | namespace Fireflies.GraphQL.Client.Generator; 5 | 6 | public class QueryCreator : SDLPrinter { 7 | private int _fieldCounter = 0; 8 | 9 | public string Query { get; private set; } 10 | 11 | public async Task Execute(GraphQLOperationDefinition operationDefinition, GraphQLGeneratorContext context) { 12 | await using var writer = new StringWriter(); 13 | await PrintAsync(operationDefinition, writer, context.CancellationToken); 14 | 15 | var fragmentVisitor = new FragmentVisitor(this, writer); 16 | await fragmentVisitor.VisitAsync(operationDefinition, context); 17 | Query = writer.ToString(); 18 | } 19 | 20 | protected override async ValueTask VisitSelectionSetAsync(GraphQLSelectionSet selectionSet, DefaultPrintContext context) { 21 | if(selectionSet.Selections.Any(x => x.Kind == ASTNodeKind.Field && ((GraphQLField)x).Name.StringValue == "__typename")) { 22 | await base.VisitSelectionSetAsync(selectionSet, context).ConfigureAwait(false); 23 | return; 24 | } 25 | 26 | if(_fieldCounter > 0) { 27 | var graphQLField = new GraphQLField(new GraphQLName("__typename")); 28 | selectionSet.Selections.Add(graphQLField); 29 | } 30 | 31 | await base.VisitSelectionSetAsync(selectionSet, context).ConfigureAwait(false); 32 | } 33 | 34 | protected override async ValueTask VisitFieldAsync(GraphQLField field, DefaultPrintContext context) { 35 | _fieldCounter++; 36 | await base.VisitFieldAsync(field, context); 37 | _fieldCounter--; 38 | } 39 | 40 | private class FragmentVisitor : ASTVisitor { 41 | private readonly SDLPrinter _sdlPrinter; 42 | private readonly StringWriter _stringWriter; 43 | private bool _isInsideIncludedFragmentSpread; 44 | private readonly HashSet _includedFragments = new(); 45 | 46 | public FragmentVisitor(SDLPrinter sdlPrinter, StringWriter stringWriter) { 47 | _sdlPrinter = sdlPrinter; 48 | _stringWriter = stringWriter; 49 | } 50 | 51 | protected override async ValueTask VisitFragmentSpreadAsync(GraphQLFragmentSpread fragmentSpread, GraphQLGeneratorContext context) { 52 | _isInsideIncludedFragmentSpread = true; 53 | var fragment = await context.FragmentAccessor.GetFragment(fragmentSpread.FragmentName).ConfigureAwait(false); 54 | await VisitAsync(fragment, context); 55 | _isInsideIncludedFragmentSpread = false; 56 | } 57 | 58 | protected override async ValueTask VisitFragmentDefinitionAsync(GraphQLFragmentDefinition fragmentDefinition, GraphQLGeneratorContext context) { 59 | if(!_isInsideIncludedFragmentSpread) 60 | return; 61 | 62 | if(!_includedFragments.Add(fragmentDefinition.FragmentName.Name.StringValue)) 63 | return; 64 | 65 | await _stringWriter.WriteLineAsync(); 66 | await _sdlPrinter.PrintAsync(fragmentDefinition, _stringWriter); 67 | 68 | foreach(var selection in fragmentDefinition.SelectionSet.Selections) { 69 | await VisitAsync(selection, context).ConfigureAwait(false); 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Schema/SchemaEnumValue.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Client.Generator.Schema; 2 | 3 | public class SchemaEnumValue { 4 | public string Name { get; set; } 5 | public string? Description { get; set; } 6 | public bool IsDeprecated { get; set; } 7 | public string? DeprecationReason { get; set; } 8 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Schema/SchemaField.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Client.Generator.Schema; 2 | 3 | public class SchemaField { 4 | public string Name { get; set; } = null!; 5 | public string? Description { get; set; } 6 | public SchemaInputValue[] Args { get; set; } = Array.Empty(); 7 | public SchemaType Type { get; set; } = null!; 8 | public bool IsDeprecated { get; set; } 9 | public string? DeprecationReason { get; set; } 10 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Schema/SchemaInputValue.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Client.Generator.Schema; 2 | 3 | public class SchemaInputValue { 4 | public string Name { get; set; } 5 | public string? Description { get; set; } 6 | public SchemaType Type { get; set; } 7 | public string? DefaultValue { get; set; } 8 | 9 | public SchemaInputValue(string name, string? description, SchemaType type, string? defaultValue) { 10 | Name = name; 11 | Description = description; 12 | Type = type; 13 | DefaultValue = defaultValue; 14 | } 15 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Schema/SchemaType.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Client.Generator.Schema; 2 | 3 | public class SchemaType { 4 | public SchemaTypeKind Kind { get; set; } 5 | public string? Name { get; set; } 6 | public string? Description { get; set; } 7 | 8 | public SchemaField[] Fields { get; set; } = Array.Empty(); 9 | 10 | public SchemaType[] Interfaces { get; set; } = Array.Empty(); 11 | public SchemaType[] PossibleTypes { get; set; } = Array.Empty(); 12 | public SchemaEnumValue[] EnumValues { get; set; } = Array.Empty(); 13 | 14 | public SchemaInputValue[] InputFields { get; set; } 15 | public SchemaType? OfType { get; set; } 16 | 17 | public string? SpecifiedByURL { get; set; } = null; 18 | 19 | protected bool Equals(SchemaType other) { 20 | return Name == other.Name && Equals(OfType, other.OfType); 21 | } 22 | 23 | public override bool Equals(object? obj) { 24 | if(ReferenceEquals(null, obj)) return false; 25 | if(ReferenceEquals(this, obj)) return true; 26 | if(obj.GetType() != this.GetType()) return false; 27 | 28 | return Equals((SchemaType)obj); 29 | } 30 | 31 | public override int GetHashCode() { 32 | unchecked { 33 | return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ (OfType != null ? OfType.GetHashCode() : 0); 34 | } 35 | } 36 | 37 | public override string ToString() { 38 | return Name ?? ""; 39 | } 40 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Schema/SchemaTypeKind.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Client.Generator.Schema; 2 | 3 | public enum SchemaTypeKind { 4 | SCALAR, 5 | OBJECT, 6 | INTERFACE, 7 | UNION, 8 | ENUM, 9 | INPUT_OBJECT, 10 | LIST, 11 | NON_NULL 12 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Shared/Builder/HttpBuilder.cs: -------------------------------------------------------------------------------- 1 | public class HttpBuilder { 2 | private readonly HttpClient _client; 3 | 4 | public HttpBuilder(HttpClient client) { 5 | _client = client; 6 | } 7 | 8 | public Uri? Uri { 9 | get { 10 | return _client.BaseAddress; 11 | } 12 | set { 13 | _client.BaseAddress = value; 14 | } 15 | } 16 | 17 | public void AddRequestHeader(string name, string value) { 18 | _client.DefaultRequestHeaders.Add(name, value); 19 | } 20 | 21 | public void AddRequestHeader(string name, IEnumerable values) { 22 | _client.DefaultRequestHeaders.Add(name, values); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Shared/Builder/WebSocketBuilder.cs: -------------------------------------------------------------------------------- 1 | public class WebSocketBuilder { 2 | private readonly GraphQLWsClient _client; 3 | 4 | public WebSocketBuilder(GraphQLWsClient client) { 5 | _client = client; 6 | } 7 | 8 | public TimeSpan? ReconnectDelay { 9 | get => _client.ReconnectDelay; 10 | set => _client.ReconnectDelay = value; 11 | } 12 | 13 | public Uri? Uri { 14 | get => _client.Uri; 15 | set => _client.Uri = value; 16 | } 17 | 18 | public event Action Connecting { 19 | add { 20 | _client.Connecting += value; 21 | } 22 | remove { 23 | _client.Connecting -= value; 24 | } 25 | } 26 | 27 | public event Action Connected { 28 | add { 29 | _client.Connected += value; 30 | } 31 | remove { 32 | _client.Connected -= value; 33 | } 34 | } 35 | 36 | public event Action Reconnecting { 37 | add { 38 | _client.Reconnecting += value; 39 | } 40 | remove { 41 | _client.Reconnecting -= value; 42 | } 43 | } 44 | 45 | public event Action Disconnected { 46 | add { 47 | _client.Disconnected += value; 48 | } 49 | remove { 50 | _client.Disconnected -= value; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Shared/Error/ClientError.cs: -------------------------------------------------------------------------------- 1 | public class ClientError : IClientError { 2 | public string? Message { get; set; } 3 | public string? Code => Extensions?["code"]?.ToString(); 4 | public IReadOnlyList? Path { get; set; } 5 | public IReadOnlyList? Locations { get; set; } 6 | public IReadOnlyDictionary? Extensions { get; set; } 7 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Shared/Error/IClientError.cs: -------------------------------------------------------------------------------- 1 | public interface IClientError { 2 | string? Message { get; } 3 | string? Code { get; } 4 | IReadOnlyList? Path { get; } 5 | IReadOnlyList? Locations { get; } 6 | IReadOnlyDictionary? Extensions { get; } 7 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Shared/Error/Location.cs: -------------------------------------------------------------------------------- 1 | public readonly struct Location { 2 | public Location(int line, int column) { 3 | if(line < 1) { 4 | throw new ArgumentOutOfRangeException(nameof(line), line, "Line location out of range"); 5 | } 6 | 7 | if(column < 1) { 8 | throw new ArgumentOutOfRangeException(nameof(column), column, "Column location out of range"); 9 | } 10 | 11 | Line = line; 12 | Column = column; 13 | } 14 | 15 | public int Line { get; } 16 | 17 | public int Column { get; } 18 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Shared/GraphQLGlobalContext.cs: -------------------------------------------------------------------------------- 1 | public class GraphQLGlobalContext : IGraphQLGlobalContext { 2 | public event Action? RequestStarted; 3 | public event Action? RequestEnded; 4 | 5 | public void TriggerRequestStarted(IGraphQLClient client) { 6 | RequestStarted?.Invoke(client); 7 | } 8 | 9 | public void TriggerRequestEnded(IGraphQLClient client, IOperationResult? result) { 10 | RequestEnded?.Invoke(client, result); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Shared/GraphQLInt.cs: -------------------------------------------------------------------------------- 1 | public struct GraphQLInt { 2 | private readonly long _value; 3 | 4 | public GraphQLInt(long value) { 5 | _value = value; 6 | } 7 | 8 | public static implicit operator GraphQLInt(long value) { 9 | return new GraphQLInt(value); 10 | } 11 | 12 | public static implicit operator long(GraphQLInt custom) { 13 | return custom._value; 14 | } 15 | 16 | public static implicit operator GraphQLInt(ulong value) { 17 | return new GraphQLInt((long)value); 18 | } 19 | 20 | public static implicit operator ulong(GraphQLInt custom) { 21 | return (ulong)custom._value; 22 | } 23 | 24 | public static implicit operator GraphQLInt(int value) { 25 | return new GraphQLInt(value); 26 | } 27 | 28 | public static implicit operator int(GraphQLInt custom) { 29 | return (int)custom._value; 30 | } 31 | 32 | public static implicit operator GraphQLInt(uint value) { 33 | return new GraphQLInt(value); 34 | } 35 | 36 | public static implicit operator uint(GraphQLInt custom) { 37 | return (uint)custom._value; 38 | } 39 | 40 | public static implicit operator GraphQLInt(short value) { 41 | return new GraphQLInt(value); 42 | } 43 | 44 | public static implicit operator short(GraphQLInt custom) { 45 | return (short)custom._value; 46 | } 47 | 48 | public static implicit operator GraphQLInt(ushort value) { 49 | return new GraphQLInt(value); 50 | } 51 | 52 | public static implicit operator ushort(GraphQLInt custom) { 53 | return (ushort)custom._value; 54 | } 55 | 56 | public static implicit operator GraphQLInt(byte value) { 57 | return new GraphQLInt(value); 58 | } 59 | 60 | public static implicit operator byte(GraphQLInt custom) { 61 | return (byte)custom._value; 62 | } 63 | 64 | public static implicit operator GraphQLInt(sbyte value) { 65 | return new GraphQLInt(value); 66 | } 67 | 68 | public static implicit operator sbyte(GraphQLInt custom) { 69 | return (sbyte)custom._value; 70 | } 71 | 72 | public override int GetHashCode() { 73 | return _value.GetHashCode(); 74 | } 75 | 76 | public override string ToString() { 77 | return _value.ToString(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Shared/GraphQLIntConverter.cs: -------------------------------------------------------------------------------- 1 | public class GraphQLIntConverter : JsonConverter { 2 | public override GraphQLInt Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { 3 | return reader.GetInt64(); 4 | } 5 | 6 | public override void Write(Utf8JsonWriter writer, GraphQLInt value, JsonSerializerOptions options) { 7 | writer.WriteNumberValue(value); 8 | } 9 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Shared/IGraphQLClient.cs: -------------------------------------------------------------------------------- 1 | public interface IGraphQLClient { 2 | } 3 | -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Shared/IGraphQLGlobalContext.cs: -------------------------------------------------------------------------------- 1 | public interface IGraphQLGlobalContext { 2 | event Action? RequestStarted; 3 | event Action? RequestEnded; 4 | 5 | void TriggerRequestStarted(IGraphQLClient client); 6 | void TriggerRequestEnded(IGraphQLClient client, IOperationResult? result); 7 | } 8 | -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Shared/IOperationResult.cs: -------------------------------------------------------------------------------- 1 | public interface IOperationResult { 2 | IEnumerable Errors { get; } 3 | } 4 | 5 | public interface IOperationResult : IOperationResult { 6 | T? Data { get; } 7 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Shared/Subscription/ConnectionNotAcceptedException.cs: -------------------------------------------------------------------------------- 1 | public class ConnectionNotAcceptedException : SubscriptionException { 2 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Shared/Subscription/GraphQLSubscriber.cs: -------------------------------------------------------------------------------- 1 | public abstract class GraphQLSubscriber { 2 | public Guid Id { get; } = Guid.NewGuid(); 3 | 4 | public abstract Task Restart(); 5 | public abstract void Handle(JsonNode payload); 6 | } 7 | 8 | public class GraphQLSubscriber : GraphQLSubscriber { 9 | private readonly GraphQLWsClient _client; 10 | private readonly Func _instanceCreator; 11 | private readonly List> _watchers = new(); 12 | 13 | public JsonNode Request { get; private set; } 14 | 15 | public GraphQLSubscriber(GraphQLWsClient client, JsonNode request, Func instanceCreator) { 16 | _client = client; 17 | _instanceCreator = instanceCreator; 18 | Request = request; 19 | } 20 | 21 | public override async Task Restart() { 22 | Request = Request.Deserialize()!; 23 | await _client.Start(this); 24 | } 25 | 26 | public async Task Watch(Action onMessage) { 27 | var subscription = new GraphQLSubscription(onMessage, this); 28 | _watchers.Add(subscription); 29 | 30 | if(_watchers.Count == 1) { 31 | await _client.Start(this); 32 | } 33 | 34 | return subscription; 35 | } 36 | 37 | public async Task Unwatch(GraphQLSubscription subscription) { 38 | _watchers.Remove(subscription); 39 | 40 | if(_watchers.Count == 0) 41 | await _client.Stop(this); 42 | } 43 | 44 | public override void Handle(JsonNode payload) { 45 | var message = _instanceCreator(payload); 46 | foreach(var watcher in _watchers) { 47 | watcher.Handle(message); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Shared/Subscription/GraphQLSubscription.cs: -------------------------------------------------------------------------------- 1 | public class GraphQLSubscription : IAsyncDisposable { 2 | private readonly Action _onMessage; 3 | private readonly GraphQLSubscriber _subscriber; 4 | 5 | public GraphQLSubscription(Action onMessage, GraphQLSubscriber subscriber) { 6 | _onMessage = onMessage; 7 | _subscriber = subscriber; 8 | } 9 | 10 | public void Handle(T message) { 11 | _onMessage(message); 12 | } 13 | 14 | public async ValueTask DisposeAsync() { 15 | await _subscriber.Unwatch(this); 16 | } 17 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Shared/Subscription/SocketClosedException.cs: -------------------------------------------------------------------------------- 1 | public class SocketClosedException : SubscriptionException { 2 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/Shared/Subscription/SubscriptionException.cs: -------------------------------------------------------------------------------- 1 | public class SubscriptionException : Exception { 2 | } -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/SharedGenerator.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Client.Generator.Builders; 2 | using System.Reflection; 3 | 4 | namespace Fireflies.GraphQL.Client.Generator; 5 | 6 | public class SharedGenerator { 7 | private readonly DirectoryInfo _rootDirectory; 8 | private readonly GeneratorSettings _generatorSettings; 9 | 10 | public SharedGenerator(DirectoryInfo rootDirectory, GeneratorSettings generatorSettings) { 11 | _rootDirectory = rootDirectory; 12 | _generatorSettings = generatorSettings; 13 | } 14 | 15 | public async Task GenerateSharedFiles() { 16 | var typeBuilder = new RawTypeBuilder(); 17 | typeBuilder.AppendLine("// "); 18 | typeBuilder.AppendLine($"// "); 19 | typeBuilder.AppendLine(); 20 | typeBuilder.AppendLine("#nullable enable"); 21 | typeBuilder.AppendLine(); 22 | typeBuilder.AppendLine("using System.Collections.Concurrent;"); 23 | typeBuilder.AppendLine("using System.Net.WebSockets;"); 24 | typeBuilder.AppendLine("using System.Text;"); 25 | typeBuilder.AppendLine("using System.Text.Json;"); 26 | typeBuilder.AppendLine("using System.Text.Json.Nodes;"); 27 | typeBuilder.AppendLine("using System.Text.Json.Serialization;"); 28 | 29 | typeBuilder.AppendLine(); 30 | 31 | typeBuilder.AppendLine($"namespace {_generatorSettings.Namespace};"); 32 | 33 | await GetResource("Fireflies.GraphQL.Client.Generator.Shared.", typeBuilder); 34 | var filePath = Path.Combine(_rootDirectory.FullName, "GraphQLShared.g.cs"); 35 | await File.WriteAllTextAsync(filePath, typeBuilder.Source()); 36 | } 37 | 38 | private async Task GetResource(string resourcePattern, RawTypeBuilder builder) { 39 | var assembly = Assembly.GetExecutingAssembly(); 40 | foreach(var resourceName in assembly.GetManifestResourceNames().Where(x => x.StartsWith(resourcePattern))) { 41 | await using var stream = assembly.GetManifestResourceStream(resourceName); 42 | using var reader = new StreamReader(stream!); 43 | var content = await reader.ReadToEndAsync(); 44 | builder.AppendLine(); 45 | builder.AppendLine(content); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Client/Fireflies.GraphQL.Client.Generator/TypeMapper.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Client.Generator; 2 | 3 | public static class TypeMapper { 4 | public static string FromGraphQL(string name) { 5 | switch(name) { 6 | case "Int": 7 | return "GraphQLInt"; 8 | case "Boolean": 9 | return "bool"; 10 | case "ID": 11 | case "String": 12 | return "string"; 13 | case "Float": 14 | return "decimal"; 15 | default: 16 | return name; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Demos/Client/Fireflies.GraphQL.Demos.Client.Demo/Fireflies.GraphQL.ClientDemo.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /Demos/Client/Fireflies.GraphQL.Demos.Client.Demo/Fireflies.GraphQL.Demos.Client.Demo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 10 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Demos/Client/Fireflies.GraphQL.Demos.Client.Demo/GraphQL/GraphQLDemo/Queries.graphql: -------------------------------------------------------------------------------- 1 | query GetBooks { 2 | books { 3 | ...TitleFragment 4 | } 5 | } 6 | 7 | query GetBook($bookId: Int!) { 8 | getBook(bookId: $bookId) { 9 | ...TitleFragment 10 | } 11 | } 12 | 13 | fragment TitleFragment on IBook { 14 | title 15 | ... on InventoryBook { 16 | calculatedPrice 17 | editions { 18 | name 19 | released 20 | } 21 | } 22 | } 23 | 24 | mutation AddBook($title: String) { 25 | addBook(data: { title: $title }) { 26 | bookId 27 | ...TitleFragment 28 | } 29 | } 30 | 31 | subscription BookUpdated($bookId: Int!) { 32 | bookUpdated(bookId: $bookId) { 33 | title 34 | bookId 35 | __typename 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Demos/Client/Fireflies.GraphQL.Demos.Client.Demo/GraphQL/GraphQLDemo/Settings.json: -------------------------------------------------------------------------------- 1 | {"uri":"https://localhost:7273/graphql"} -------------------------------------------------------------------------------- /Demos/Client/Fireflies.GraphQL.Demos.Client.Demo/GraphQL/Settings.json: -------------------------------------------------------------------------------- 1 | {"namespace":"Fireflies.GraphQL.Demos.GraphQL"} -------------------------------------------------------------------------------- /Demos/Client/Fireflies.GraphQL.Demos.Client.Demo/Program.cs: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/new-console-template for more information 2 | //using MyNamespace; 3 | 4 | using Fireflies.GraphQL.Demos.GraphQL.GraphQLDemo; 5 | 6 | Console.WriteLine("Here we go..."); 7 | 8 | var client = new GraphQLDemoClient(h => { 9 | h.Uri = new Uri("https://localhost:7273/graphql"); 10 | }, h => { 11 | h.Uri = new Uri("wss://localhost:7273/graphql"); 12 | h.ReconnectDelay = TimeSpan.FromSeconds(5); 13 | }); 14 | 15 | client.Connected += () => Console.WriteLine("Connected via client"); 16 | client.Connecting += () => Console.WriteLine("Connecting"); 17 | client.Reconnecting += () => Console.WriteLine("Reconnecting"); 18 | client.Disconnected += () => Console.WriteLine("Disconnected"); 19 | 20 | Console.WriteLine("The first 10 books are"); 21 | var allBooks = await client.GetBooks().ConfigureAwait(false); 22 | foreach(var book in allBooks.Data.Books.Take(10)) { 23 | Console.WriteLine($" - {book.Title}"); 24 | } 25 | 26 | Console.WriteLine("Adding a new book"); 27 | var addedBook = await client.AddBook("My new book"); 28 | Console.WriteLine($"- Added book got ID: {addedBook.Data.AddBook.BookId}"); 29 | 30 | 31 | Console.WriteLine("Subscribing to changes"); 32 | var watcher = await client.BookUpdated(23).Watch(m => { 33 | Console.WriteLine($"- Update received: {m.Data.BookUpdated.Title} {m.Data.BookUpdated.BookId}"); 34 | }); 35 | 36 | Console.WriteLine("Will exit on ENTER"); 37 | Console.ReadLine(); 38 | 39 | await watcher.DisposeAsync(); 40 | -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Api/Fireflies.GraphQL.Demos.Server.Api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.AspNet; 2 | using Fireflies.GraphQL.Core; 3 | using Fireflies.IoC.TinyIoC; 4 | using Fireflies.Logging.NLog; 5 | using NLog; 6 | using NLog.Common; 7 | using NLog.Config; 8 | using NLog.Targets; 9 | using LogLevel = NLog.LogLevel; 10 | 11 | var builder = WebApplication.CreateBuilder(args); 12 | 13 | builder.Services.AddCors(options => { 14 | options.AddDefaultPolicy(builder => 15 | builder.SetIsOriginAllowed(_ => true) 16 | .AllowAnyMethod() 17 | .AllowAnyHeader() 18 | .AllowCredentials()); 19 | }); 20 | 21 | var app = builder.Build(); 22 | 23 | app.UseCors(corsPolicyBuilder => corsPolicyBuilder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()); 24 | 25 | var config = new LoggingConfiguration(); 26 | InternalLogger.LogLevel = LogLevel.Error; 27 | InternalLogger.LogToConsole = true; 28 | var consoleTarget = new ColoredConsoleTarget(); 29 | config.AddTarget("console", consoleTarget); 30 | config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, consoleTarget)); 31 | LogManager.Configuration = config; 32 | 33 | var container = new TinyIoCContainer(); 34 | 35 | // Enable websockets (needed for subscriptions) 36 | app.UseWebSockets(); 37 | 38 | /////////////////////////////////////// 39 | // Start GraphQL setup 40 | var graphQLOptions = new GraphQLOptionsBuilder(); 41 | 42 | // Set framework libraries 43 | graphQLOptions.SetDependencyResolver(new TinyIoCDependencyResolver(container)); 44 | graphQLOptions.SetLoggerFactory(new FirefliesNLogFactory()); 45 | 46 | // Add federation 47 | graphQLOptions.AddFederation("Books", "https://localhost:7273/graphql"); 48 | graphQLOptions.AddFederation("Authors", "https://localhost:7274/graphql"); 49 | 50 | // Add to pipeline 51 | app.UseGraphQL(await graphQLOptions.Build()); 52 | 53 | // End GraphQL setup 54 | /////////////////////////////////////// 55 | 56 | app.MapGet("/", () => "Hello World!"); 57 | 58 | app.Run(); -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Fireflies.GraphQL.Demo": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": true, 6 | "launchBrowser": false, 7 | "applicationUrl": "https://localhost:7272;http://localhost:5110", 8 | "environmentVariables": { 9 | "ASPNETCORE_ENVIRONMENT": "Development" 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Authors/Authors/AuthorOperations.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using Fireflies.GraphQL.Abstractions; 3 | using Fireflies.GraphQL.Abstractions.Connection; 4 | 5 | namespace Fireflies.GraphQL.Demos.Server.Authors.Authors; 6 | 7 | public class AuthorOperations { 8 | [GraphQLQuery] 9 | public Task Author(int authorId = 10) { 10 | if(authorId > 100) 11 | return Task.FromResult(new PseudnonymAuthor { Id = authorId, Name = "Calvin Smallhead" }); 12 | 13 | return Task.FromResult(new RealAuthor { Id = authorId, Name = "Calvin Little", Emails = new[] { "calvin.little@fireflies.tech" } }); 14 | } 15 | 16 | [GraphQLQuery] 17 | [GraphQLPagination] 18 | public Task> Authors(AuthorsFilter? filter) { 19 | var list = new List(); 20 | for(var i = 0; i < 1000; i++) { 21 | list.Add(new RealAuthor { Id = i, Name = $"Calvin the {i} Little", Emails = new[] { "calvin.little@fireflies.tech", "cl@fireflies.tech" } }); 22 | } 23 | 24 | return Task.FromResult(list.AsEnumerable()); 25 | } 26 | 27 | [GraphQLMutation] 28 | [MustBeAllowedToUpdateAuthor] 29 | public Task UpdateAuthor(UpdateAuthorInput input) { 30 | return Task.FromResult((IAuthor)new RealAuthor { Id = input.AuthorId, Name = input.Name, Emails = Array.Empty() }); 31 | } 32 | 33 | [GraphQLSubscription] 34 | public async IAsyncEnumerable AuthorAdded([EnumeratorCancellation] CancellationToken cancellationToken = default) { 35 | while(!cancellationToken.IsCancellationRequested) { 36 | await Task.Delay(7500, cancellationToken).ConfigureAwait(false); 37 | yield return new RealAuthor { Id = DateTime.UtcNow.Second, Name = "Lars" }; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Authors/Authors/AuthorsFilter.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Demos.Server.Authors.Authors; 2 | 3 | public class AuthorsFilter { 4 | public string? Contains { get; set; } 5 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Authors/Authors/IAuthor.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions; 2 | using Fireflies.GraphQL.Abstractions.Schema; 3 | using Fireflies.GraphQL.Core; 4 | using GraphQLParser.AST; 5 | 6 | namespace Fireflies.GraphQL.Demos.Server.Authors.Authors; 7 | 8 | public interface IAuthor { 9 | [GraphQLId(true)] 10 | public int Id { get; set; } 11 | 12 | [GraphQLDescription("The authors name")] 13 | public string Name { get; set; } 14 | 15 | public Task> Books(ASTNode astNode, IRequestContext requestContext); 16 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Authors/Authors/IBook.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core; 2 | using Fireflies.GraphQL.Core.Federation; 3 | using GraphQLParser.AST; 4 | 5 | namespace Fireflies.GraphQL.Demos.Server.Authors.Authors; 6 | 7 | public class IBook : FederatedQuery { 8 | private IBook(string query) : base(query) { 9 | } 10 | 11 | public static async Task Create(IAuthor author, ASTNode astNode, IRequestContext requestContext) { 12 | var query = $"query {{ books(filter: {{ authorId: {author.Id} }}) {await CreateSelectionSet(astNode, requestContext)} }}"; 13 | return new[] { new IBook(query) }; 14 | } 15 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Authors/Authors/MustBeAllowedToUpdateAuthorAttribute.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Authorization; 2 | 3 | namespace Fireflies.GraphQL.Demos.Server.Authors.Authors; 4 | 5 | public class MustBeAllowedToUpdateAuthorAttribute : GraphQLAuthorizationAttribute { 6 | // The name of the variable must match the name in the operation 7 | public override Task Authorize(UpdateAuthorInput? input) { 8 | return Task.FromResult(true); 9 | } 10 | 11 | public override string Help => "Need Update role"; 12 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Authors/Authors/PseudnonymAuthor.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core; 2 | using GraphQLParser.AST; 3 | 4 | namespace Fireflies.GraphQL.Demos.Server.Authors.Authors; 5 | 6 | public class PseudnonymAuthor : IAuthor { 7 | public int Id { get; set; } 8 | 9 | public string Name { get; set; } = null!; 10 | 11 | public DateTimeOffset? Born { get; set; } = DateTimeOffset.MinValue; 12 | 13 | public async Task> Books(ASTNode astNode, IRequestContext requestContext) { 14 | return await IBook.Create(this, astNode, requestContext).ConfigureAwait(false); 15 | } 16 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Authors/Authors/RealAuthor.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Schema; 2 | using Fireflies.GraphQL.Core; 3 | using GraphQLParser.AST; 4 | 5 | namespace Fireflies.GraphQL.Demos.Server.Authors.Authors; 6 | 7 | public class RealAuthor : IAuthor { 8 | public int Id { get; set; } 9 | 10 | public string Name { get; set; } = null!; 11 | 12 | [GraphQLDeprecated("Is not populated anymore")] 13 | public IEnumerable Emails { get; set; } = Enumerable.Empty(); 14 | 15 | public async Task> Books(ASTNode astNode, IRequestContext requestContext) { 16 | return await IBook.Create(this, astNode, requestContext).ConfigureAwait(false); 17 | } 18 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Authors/Authors/UpdateAuthorInput.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Demos.Server.Authors.Authors; 2 | 3 | public class UpdateAuthorInput { 4 | public int AuthorId { get; set; } 5 | public string Name { get; set; } 6 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Authors/Fireflies.GraphQL.Demos.Server.Authors.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Authors/Program.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Fireflies.GraphQL.AspNet; 3 | using Fireflies.GraphQL.Core; 4 | using Fireflies.GraphQL.Demos.Server.Authors; 5 | using Fireflies.GraphQL.Demos.Server.Authors.Authors; 6 | using Fireflies.IoC.Autofac; 7 | using Fireflies.Logging.NLog; 8 | using NLog; 9 | using NLog.Common; 10 | using NLog.Config; 11 | using NLog.Targets; 12 | using LogLevel = NLog.LogLevel; 13 | 14 | var builder = WebApplication.CreateBuilder(args); 15 | 16 | builder.Services.AddCors(options => { 17 | options.AddDefaultPolicy(builder => 18 | builder.SetIsOriginAllowed(_ => true) 19 | .AllowAnyMethod() 20 | .AllowAnyHeader() 21 | .AllowCredentials()); 22 | }); 23 | 24 | var app = builder.Build(); 25 | 26 | app.UseCors(corsPolicyBuilder => corsPolicyBuilder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()); 27 | 28 | var config = new LoggingConfiguration(); 29 | InternalLogger.LogLevel = LogLevel.Error; 30 | InternalLogger.LogToConsole = true; 31 | var consoleTarget = new ColoredConsoleTarget(); 32 | config.AddTarget("console", consoleTarget); 33 | config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, consoleTarget)); 34 | LogManager.Configuration = config; 35 | 36 | var containerBuilder = new ContainerBuilder(); 37 | containerBuilder.RegisterType().As>(); 38 | containerBuilder.RegisterType(); 39 | var container = containerBuilder.Build(); 40 | 41 | var graphQLOptions = new GraphQLOptionsBuilder(); 42 | graphQLOptions.SetLoggerFactory(new FirefliesNLogFactory()); 43 | graphQLOptions.Add(); 44 | graphQLOptions.SetDependencyResolver(new AutofacDependencyResolver(container)); 45 | app.UseWebSockets(); 46 | app.UseGraphQL(await graphQLOptions.Build()); 47 | 48 | app.MapGet("/", () => "Hello World!"); 49 | 50 | app.Run(); -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Authors/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Fireflies.GraphQL.FederationDemo": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "dotnetRunMessages": true, 10 | "applicationUrl": "https://localhost:7274;http://localhost:5112" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Authors/RequestContainerBuilder.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core; 2 | using Fireflies.IoC.Abstractions; 3 | 4 | namespace Fireflies.GraphQL.Demos.Server.Authors; 5 | 6 | public class RequestContainerBuilder : IRequestContainerBuilder { 7 | public void Build(ILifetimeScopeBuilder builder, HttpContext httpContext) { 8 | builder.RegisterInstance(new User()); 9 | } 10 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Authors/User.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Demos.Server.Authors; 2 | 3 | public class User { 4 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Authors/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Authors/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/AddBookInput.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Demos.Server.Books; 2 | 3 | public class AddBookInput { 4 | public string? Title { get; set; } 5 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/Blogs/Blog.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions; 2 | 3 | namespace Fireflies.GraphQL.Demos.Server.Books.Blogs; 4 | 5 | public class Blog { 6 | [GraphQLId] 7 | public int BlogId { get; set; } 8 | public string Url { get; set; } 9 | 10 | public List Posts { get; } 11 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/Blogs/BlogOperations.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions; 2 | using Fireflies.GraphQL.Abstractions.Connection; 3 | using Fireflies.GraphQL.Abstractions.Sorting; 4 | using Fireflies.GraphQL.Abstractions.Where; 5 | using Fireflies.GraphQL.Core; 6 | 7 | namespace Fireflies.GraphQL.Demos.Server.Books.Blogs; 8 | 9 | public class BlogOperations { 10 | [GraphQLQuery] 11 | [GraphQLSort] 12 | [GraphQLPagination] 13 | [GraphQLWhere] 14 | public IQueryable Blogs([Resolved] BloggingContext db) { 15 | return db.Blogs; 16 | } 17 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/Blogs/BloggingContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace Fireflies.GraphQL.Demos.Server.Books.Blogs; 4 | 5 | public class BloggingContext : DbContext { 6 | public DbSet Blogs { get; set; } 7 | public DbSet Posts { get; set; } 8 | 9 | public string DbPath { get; } 10 | 11 | public BloggingContext() { 12 | var folder = Environment.SpecialFolder.LocalApplicationData; 13 | var path = Environment.GetFolderPath(folder); 14 | DbPath = System.IO.Path.Join(path, "blogging.db"); 15 | } 16 | 17 | protected override void OnConfiguring(DbContextOptionsBuilder options) { 18 | options.UseSqlite($"Data Source={DbPath}"); 19 | } 20 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/Blogs/Post.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Demos.Server.Books.Blogs; 2 | 3 | public class Post { 4 | public int PostId { get; set; } 5 | public string Title { get; set; } 6 | public string Content { get; set; } 7 | 8 | public int BlogId { get; set; } 9 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/Books/BookOperations.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using Fireflies.GraphQL.Abstractions; 3 | using Fireflies.GraphQL.Abstractions.Connection; 4 | using Fireflies.GraphQL.Abstractions.Sorting; 5 | using Fireflies.GraphQL.Abstractions.Where; 6 | using GraphQLParser.AST; 7 | 8 | namespace Fireflies.GraphQL.Demos.Server.Books.Books; 9 | 10 | public class BookOperations { 11 | [GraphQLPagination] 12 | [GraphQLQuery] 13 | [GraphQLSort] 14 | [GraphQLWhere] 15 | [GraphQLParallel] 16 | public IEnumerable Books(BooksFilter? filter) { 17 | return YieldBooks(); 18 | } 19 | 20 | [GraphQLQuery] 21 | public async Task Book(int bookId) { 22 | return YieldBooks().First(); 23 | } 24 | 25 | [GraphQLMutation] 26 | public Task AddBook(AddBookInput data) { 27 | return Task.FromResult(new InventoryBook { 28 | BookId = DateTime.UtcNow.Second, 29 | Title = data.Title ?? " BookUpdated(int bookId, ASTNode astNode, [EnumeratorCancellation] CancellationToken cancellation) { 35 | while(!cancellation.IsCancellationRequested) { 36 | await Task.Delay(7500, cancellation).ConfigureAwait(false); 37 | yield return CreateInventoryBook(bookId); 38 | } 39 | } 40 | 41 | /////////////////////////////////////// 42 | // Helper methods 43 | 44 | private IEnumerable YieldBooks() { 45 | for(var i = 0; i < 1; i++) { 46 | yield return CreateInventoryBook(i); 47 | } 48 | 49 | for(var i = 0; i < 1; i++) { 50 | yield return CreateRemoteBook(i); 51 | } 52 | } 53 | 54 | private static RemoteBook CreateRemoteBook(int i) { 55 | return new RemoteBook { 56 | BookId = i, 57 | Title = $"My {i}th book", 58 | ISBN = "1234", 59 | }; 60 | } 61 | 62 | private static InventoryBook CreateInventoryBook(int i) { 63 | return new InventoryBook { 64 | BookId = i, Title = $"My {i}th book", ISBN = "1234", ExactInventory = 20, 65 | Editions = new[] { 66 | new Edition { Name = "Deluxe", Released = DateTimeOffset.UtcNow.AddYears(-1) }, 67 | new Edition { Name = "Original", Released = DateTimeOffset.UtcNow.AddYears(-2) } 68 | }.AsQueryable() 69 | }; 70 | } 71 | } 72 | 73 | public class BooksFilter { 74 | public int AuthorId { get; set; } 75 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/Books/Edition.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Demos.Server.Books.Books; 2 | 3 | public class Edition { 4 | public string Name { get; set; } 5 | public DateTimeOffset Released { get; set; } 6 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/Books/IBook.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions; 2 | 3 | namespace Fireflies.GraphQL.Demos.Server.Books.Books; 4 | 5 | public interface IBook { 6 | [GraphQLId] 7 | public int BookId { get; } 8 | 9 | string ISBN { get; } 10 | 11 | string Title { get; } 12 | } 13 | -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/Books/InventoryBook.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions; 2 | using Fireflies.GraphQL.Abstractions.Schema; 3 | using Fireflies.GraphQL.Abstractions.Sorting; 4 | 5 | namespace Fireflies.GraphQL.Demos.Server.Books.Books; 6 | 7 | public class InventoryBook : IBook { 8 | [GraphQLId] 9 | public int BookId { get; set; } 10 | 11 | public string ISBN { get; set; } = null!; 12 | public string Title { get; set; } = null!; 13 | 14 | public Task CalculatedPrice() { 15 | return Task.FromResult(23.3M); 16 | } 17 | 18 | [GraphQLSort] 19 | public IQueryable Editions { get; set; } 20 | 21 | [MustBeSalesman] 22 | [GraphQLDescription("Returns the exact inventory")] 23 | [GraphQLDeprecated("Will be 0 from 2024-01-01")] 24 | public int ExactInventory { get; set; } 25 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/Books/RemoteBook.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions; 2 | 3 | namespace Fireflies.GraphQL.Demos.Server.Books.Books; 4 | 5 | public class RemoteBook : IBook { 6 | [GraphQLId] 7 | public int BookId { get; set; } 8 | 9 | public string ISBN { get; set; } = null!; 10 | public string Title { get; set; } 11 | 12 | public IQueryable Editions { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/Fireflies.GraphQL.Demos.Server.Books.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/Migrations/20230302101823_InitialCreate.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | using Fireflies.GraphQL.Demos.Server.Books.Blogs; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | 9 | #nullable disable 10 | 11 | namespace Fireflies.GraphQL.Demo.Migrations 12 | { 13 | [DbContext(typeof(BloggingContext))] 14 | [Migration("20230302101823_InitialCreate")] 15 | partial class InitialCreate 16 | { 17 | /// 18 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 19 | { 20 | #pragma warning disable 612, 618 21 | modelBuilder.HasAnnotation("ProductVersion", "7.0.3"); 22 | 23 | modelBuilder.Entity("Blog", b => 24 | { 25 | b.Property("BlogId") 26 | .ValueGeneratedOnAdd() 27 | .HasColumnType("INTEGER"); 28 | 29 | b.Property("Url") 30 | .IsRequired() 31 | .HasColumnType("TEXT"); 32 | 33 | b.HasKey("BlogId"); 34 | 35 | b.ToTable("Blogs"); 36 | }); 37 | 38 | modelBuilder.Entity("Post", b => 39 | { 40 | b.Property("PostId") 41 | .ValueGeneratedOnAdd() 42 | .HasColumnType("INTEGER"); 43 | 44 | b.Property("BlogId") 45 | .HasColumnType("INTEGER"); 46 | 47 | b.Property("Content") 48 | .IsRequired() 49 | .HasColumnType("TEXT"); 50 | 51 | b.Property("Title") 52 | .IsRequired() 53 | .HasColumnType("TEXT"); 54 | 55 | b.HasKey("PostId"); 56 | 57 | b.HasIndex("BlogId"); 58 | 59 | b.ToTable("Posts"); 60 | }); 61 | 62 | modelBuilder.Entity("Post", b => 63 | { 64 | b.HasOne("Blog", "Blog") 65 | .WithMany("Posts") 66 | .HasForeignKey("BlogId") 67 | .OnDelete(DeleteBehavior.Cascade) 68 | .IsRequired(); 69 | 70 | b.Navigation("Blog"); 71 | }); 72 | 73 | modelBuilder.Entity("Blog", b => 74 | { 75 | b.Navigation("Posts"); 76 | }); 77 | #pragma warning restore 612, 618 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/Migrations/20230302101823_InitialCreate.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Fireflies.GraphQL.Demo.Migrations 6 | { 7 | /// 8 | public partial class InitialCreate : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.CreateTable( 14 | name: "Blogs", 15 | columns: table => new 16 | { 17 | BlogId = table.Column(type: "INTEGER", nullable: false) 18 | .Annotation("Sqlite:Autoincrement", true), 19 | Url = table.Column(type: "TEXT", nullable: false) 20 | }, 21 | constraints: table => 22 | { 23 | table.PrimaryKey("PK_Blogs", x => x.BlogId); 24 | }); 25 | 26 | migrationBuilder.CreateTable( 27 | name: "Posts", 28 | columns: table => new 29 | { 30 | PostId = table.Column(type: "INTEGER", nullable: false) 31 | .Annotation("Sqlite:Autoincrement", true), 32 | Title = table.Column(type: "TEXT", nullable: false), 33 | Content = table.Column(type: "TEXT", nullable: false), 34 | BlogId = table.Column(type: "INTEGER", nullable: false) 35 | }, 36 | constraints: table => 37 | { 38 | table.PrimaryKey("PK_Posts", x => x.PostId); 39 | table.ForeignKey( 40 | name: "FK_Posts_Blogs_BlogId", 41 | column: x => x.BlogId, 42 | principalTable: "Blogs", 43 | principalColumn: "BlogId", 44 | onDelete: ReferentialAction.Cascade); 45 | }); 46 | 47 | migrationBuilder.CreateIndex( 48 | name: "IX_Posts_BlogId", 49 | table: "Posts", 50 | column: "BlogId"); 51 | } 52 | 53 | /// 54 | protected override void Down(MigrationBuilder migrationBuilder) 55 | { 56 | migrationBuilder.DropTable( 57 | name: "Posts"); 58 | 59 | migrationBuilder.DropTable( 60 | name: "Blogs"); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/Migrations/BloggingContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | using Fireflies.GraphQL.Demos.Server.Books.Blogs; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 | 8 | #nullable disable 9 | 10 | namespace Fireflies.GraphQL.Demo.Migrations 11 | { 12 | [DbContext(typeof(BloggingContext))] 13 | partial class BloggingContextModelSnapshot : ModelSnapshot 14 | { 15 | protected override void BuildModel(ModelBuilder modelBuilder) 16 | { 17 | #pragma warning disable 612, 618 18 | modelBuilder.HasAnnotation("ProductVersion", "7.0.3"); 19 | 20 | modelBuilder.Entity("Blog", b => 21 | { 22 | b.Property("BlogId") 23 | .ValueGeneratedOnAdd() 24 | .HasColumnType("INTEGER"); 25 | 26 | b.Property("Url") 27 | .IsRequired() 28 | .HasColumnType("TEXT"); 29 | 30 | b.HasKey("BlogId"); 31 | 32 | b.ToTable("Blogs"); 33 | }); 34 | 35 | modelBuilder.Entity("Post", b => 36 | { 37 | b.Property("PostId") 38 | .ValueGeneratedOnAdd() 39 | .HasColumnType("INTEGER"); 40 | 41 | b.Property("BlogId") 42 | .HasColumnType("INTEGER"); 43 | 44 | b.Property("Content") 45 | .IsRequired() 46 | .HasColumnType("TEXT"); 47 | 48 | b.Property("Title") 49 | .IsRequired() 50 | .HasColumnType("TEXT"); 51 | 52 | b.HasKey("PostId"); 53 | 54 | b.HasIndex("BlogId"); 55 | 56 | b.ToTable("Posts"); 57 | }); 58 | 59 | modelBuilder.Entity("Post", b => 60 | { 61 | b.HasOne("Blog", "Blog") 62 | .WithMany("Posts") 63 | .HasForeignKey("BlogId") 64 | .OnDelete(DeleteBehavior.Cascade) 65 | .IsRequired(); 66 | 67 | b.Navigation("Blog"); 68 | }); 69 | 70 | modelBuilder.Entity("Blog", b => 71 | { 72 | b.Navigation("Posts"); 73 | }); 74 | #pragma warning restore 612, 618 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/Migrations/InitialCreate.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Fireflies.GraphQL.Demo.Migrations 6 | { 7 | /// 8 | public partial class InitialCreate : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.CreateTable( 14 | name: "Blogs", 15 | columns: table => new 16 | { 17 | BlogId = table.Column(type: "INTEGER", nullable: false) 18 | .Annotation("Sqlite:Autoincrement", true), 19 | Url = table.Column(type: "TEXT", nullable: false) 20 | }, 21 | constraints: table => 22 | { 23 | table.PrimaryKey("PK_Blogs", x => x.BlogId); 24 | }); 25 | 26 | migrationBuilder.CreateTable( 27 | name: "Posts", 28 | columns: table => new 29 | { 30 | PostId = table.Column(type: "INTEGER", nullable: false) 31 | .Annotation("Sqlite:Autoincrement", true), 32 | Title = table.Column(type: "TEXT", nullable: false), 33 | Content = table.Column(type: "TEXT", nullable: false), 34 | BlogId = table.Column(type: "INTEGER", nullable: false) 35 | }, 36 | constraints: table => 37 | { 38 | table.PrimaryKey("PK_Posts", x => x.PostId); 39 | table.ForeignKey( 40 | name: "FK_Posts_Blogs_BlogId", 41 | column: x => x.BlogId, 42 | principalTable: "Blogs", 43 | principalColumn: "BlogId", 44 | onDelete: ReferentialAction.Cascade); 45 | }); 46 | 47 | migrationBuilder.CreateIndex( 48 | name: "IX_Posts_BlogId", 49 | table: "Posts", 50 | column: "BlogId"); 51 | } 52 | 53 | /// 54 | protected override void Down(MigrationBuilder migrationBuilder) 55 | { 56 | migrationBuilder.DropTable( 57 | name: "Posts"); 58 | 59 | migrationBuilder.DropTable( 60 | name: "Blogs"); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/MustBeSalesmanAttribute.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Authorization; 2 | 3 | namespace Fireflies.GraphQL.Demos.Server.Books; 4 | 5 | public class MustBeSalesmanAttribute : GraphQLAuthorizationAttribute { 6 | internal MustBeSalesmanAttribute() { 7 | } 8 | 9 | public MustBeSalesmanAttribute(User user) { 10 | } 11 | 12 | public override Task Authorize() { 13 | return Task.FromResult(false); 14 | } 15 | 16 | public override string Help => "Must be authenticated as a salesman"; 17 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/Program.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.AspNet; 2 | using Fireflies.GraphQL.Core; 3 | using Fireflies.GraphQL.Demos.Server.Books; 4 | using Fireflies.GraphQL.Demos.Server.Books.Blogs; 5 | using Fireflies.GraphQL.Demos.Server.Books.Books; 6 | using Fireflies.GraphQL.Extensions.EntityFrameworkCore; 7 | using Fireflies.IoC.TinyIoC; 8 | using Fireflies.Logging.NLog; 9 | using NLog; 10 | using NLog.Common; 11 | using NLog.Config; 12 | using NLog.Targets; 13 | using LogLevel = NLog.LogLevel; 14 | 15 | var builder = WebApplication.CreateBuilder(args); 16 | 17 | builder.Services.AddCors(options => { 18 | options.AddDefaultPolicy(builder => 19 | builder.SetIsOriginAllowed(_ => true) 20 | .AllowAnyMethod() 21 | .AllowAnyHeader() 22 | .AllowCredentials()); 23 | }); 24 | 25 | var app = builder.Build(); 26 | 27 | app.UseCors(corsPolicyBuilder => corsPolicyBuilder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()); 28 | 29 | var config = new LoggingConfiguration(); 30 | InternalLogger.LogLevel = LogLevel.Error; 31 | InternalLogger.LogToConsole = true; 32 | var consoleTarget = new ColoredConsoleTarget(); 33 | config.AddTarget("console", consoleTarget); 34 | config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, consoleTarget)); 35 | LogManager.Configuration = config; 36 | 37 | var container = new TinyIoCContainer(); 38 | container.Register(); 39 | container.Register, RequestContainerBuilder>(); 40 | 41 | // Enable websockets (needed for subscriptions) 42 | app.UseWebSockets(); 43 | 44 | /////////////////////////////////////// 45 | // Start GraphQL setup 46 | var graphQLOptions = new GraphQLOptionsBuilder(); 47 | 48 | // Add entity framework support 49 | var entityFrameworkOptions = graphQLOptions.UseEntityFramework(); 50 | entityFrameworkOptions.Register(); 51 | 52 | // Set framework libraries 53 | graphQLOptions.SetDependencyResolver(new TinyIoCDependencyResolver(container)); 54 | graphQLOptions.SetLoggerFactory(new FirefliesNLogFactory()); 55 | 56 | //// Add operations 57 | graphQLOptions.Add(); 58 | graphQLOptions.Add(); 59 | 60 | // Add federation 61 | //graphQLOptions.AddFederation("Author", "https://localhost:7274/graphql"); 62 | 63 | // Add to pipeline 64 | app.UseGraphQL(await graphQLOptions.Build()); 65 | 66 | // End GraphQL setup 67 | /////////////////////////////////////// 68 | 69 | app.MapGet("/", () => "Hello World!"); 70 | 71 | app.Run(); -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Fireflies.GraphQL.Demo": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": true, 6 | "launchBrowser": false, 7 | "applicationUrl": "https://localhost:7273;http://localhost:5111", 8 | "environmentVariables": { 9 | "ASPNETCORE_ENVIRONMENT": "Development" 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/RequestContainerBuilder.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core; 2 | using Fireflies.IoC.Abstractions; 3 | 4 | namespace Fireflies.GraphQL.Demos.Server.Books; 5 | 6 | public class RequestContainerBuilder : IRequestContainerBuilder { 7 | public void Build(ILifetimeScopeBuilder builder, HttpContext httpContext) { 8 | builder.RegisterInstance(new User()); 9 | } 10 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/StringFilterOperatorInput.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Demos.Server.Books; 2 | 3 | public class StringFilterOperatorInput { 4 | public string? Eq { get; set; } 5 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/User.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Demos.Server.Books; 2 | 3 | public class User { 4 | } -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Demos/Server/Fireflies.GraphQL.Demos.Server.Books/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /Extensions/Fireflies.GraphQL.Extensions.EntityFrameworkCore/EntityFrameworkCoreExtensionsBuilder.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core; 2 | using Fireflies.GraphQL.Core.Generators.Sorting; 3 | using Fireflies.IoC.Abstractions; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace Fireflies.GraphQL.Extensions.EntityFrameworkCore; 7 | 8 | public class EntityFrameworkCoreExtensionsBuilder : IExtensionBuilder { 9 | private readonly GraphQLOptionsBuilder _optionsBuilder; 10 | private readonly List _dbContexts = new(); 11 | 12 | public EntityFrameworkCoreExtensionsBuilder(GraphQLOptionsBuilder optionsBuilder) { 13 | _optionsBuilder = optionsBuilder; 14 | } 15 | 16 | public void Register() where T : DbContext { 17 | _dbContexts.Add(typeof(T)); 18 | } 19 | 20 | public void Build() { 21 | _optionsBuilder.AddGeneratorBefore(new EntityFrameworkCoreMethodExtender()); 22 | } 23 | 24 | public void BuildRequestLifetimeScope(ILifetimeScopeBuilder lifetimeScopeBuilder) { 25 | foreach(var dbContext in _dbContexts) 26 | lifetimeScopeBuilder.RegisterType(dbContext); 27 | } 28 | 29 | public void BuildGraphQLLifetimeScope(ILifetimeScopeBuilder lifetimeScopeBuilder) { 30 | } 31 | } -------------------------------------------------------------------------------- /Extensions/Fireflies.GraphQL.Extensions.EntityFrameworkCore/EntityFrameworkCoreGraphQLOptionsBuilder.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core; 2 | 3 | namespace Fireflies.GraphQL.Extensions.EntityFrameworkCore; 4 | 5 | public static class EntityFrameworkCoreGraphQLOptionsBuilder { 6 | public static EntityFrameworkCoreExtensionsBuilder UseEntityFramework(this GraphQLOptionsBuilder optionsBuilder) { 7 | var builder = new EntityFrameworkCoreExtensionsBuilder(optionsBuilder); 8 | optionsBuilder.AddExtension(builder); 9 | return builder; 10 | } 11 | } -------------------------------------------------------------------------------- /Extensions/Fireflies.GraphQL.Extensions.EntityFrameworkCore/Fireflies.GraphQL.Extensions.EntityFrameworkCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | Fireflies.Tech 8 | Fireflies GraphQL Entity Framework Core extension 9 | 2023 Fireflies.tech 10 | MIT 11 | README.md 12 | Icon-128x128.png 13 | https://github.com/firefliestech/fireflies.graphql 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Extensions/Fireflies.GraphQL.Extensions.EntityFrameworkCore/Fireflies.GraphQL.Extensions.EntityFrameworkCore.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | false 9 | $description$ 10 | $copyright$ 11 | fireflies extension entity-framework 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Extensions/Fireflies.GraphQL.Extensions.EntityFrameworkCore/README.md: -------------------------------------------------------------------------------- 1 | # Fireflies GraphQL Entity Framework Core extension 2 | 3 | ## Example 4 | Add the following code to your WebApplication pipeline. 5 | ``` 6 | var entityFrameworkOptions = graphQLOptions.UseEntityFramework(); 7 | entityFrameworkOptions.Register(); 8 | ``` 9 | 10 | ## Operation definition 11 | ``` 12 | [GraphQLSort] 13 | [GraphQLQuery] 14 | public IQueryable Blogs([Resolved] BloggingContext db) { 15 | return db.Blogs; 16 | } 17 | ``` 18 | 19 | ## Model 20 | ``` 21 | using Microsoft.EntityFrameworkCore; 22 | 23 | public class BloggingContext : DbContext { 24 | public DbSet Blogs { get; set; } 25 | public DbSet Posts { get; set; } 26 | 27 | public string DbPath { get; } 28 | 29 | public BloggingContext() { 30 | var folder = Environment.SpecialFolder.LocalApplicationData; 31 | var path = Environment.GetFolderPath(folder); 32 | DbPath = System.IO.Path.Join(path, "blogging.db"); 33 | } 34 | 35 | protected override void OnConfiguring(DbContextOptionsBuilder options) { 36 | options.UseSqlite($"Data Source={DbPath}"); 37 | } 38 | } 39 | 40 | public class Blog { 41 | public int BlogId { get; set; } 42 | public string Url { get; set; } 43 | 44 | public List Posts { get; } 45 | } 46 | 47 | public class Post { 48 | public int PostId { get; set; } 49 | public string Title { get; set; } 50 | public string Content { get; set; } 51 | 52 | public int BlogId { get; set; } 53 | } 54 | ``` 55 | 56 | _Logo by freepik_ -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Authorization/GraphQLAuthorizationAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Authorization; 2 | 3 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Class)] 4 | public abstract class GraphQLAuthorizationAttribute : GraphQLAuthorizationBaseAttribute { 5 | public abstract Task Authorize(); 6 | } 7 | 8 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Class)] 9 | public abstract class GraphQLAuthorizationAttribute : GraphQLAuthorizationBaseAttribute { 10 | public abstract Task Authorize(TData data); 11 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Authorization/GraphQLAuthorizationBaseAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Authorization; 2 | 3 | public abstract class GraphQLAuthorizationBaseAttribute : GraphQLAttribute { 4 | public abstract string Help { get; } 5 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Connection/GraphQlPaginationAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Connection; 2 | 3 | public class GraphQLPaginationAttribute : GraphQLAttribute { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Fireflies.GraphQL.Abstractions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | Fireflies.Tech 8 | Fireflies GraphQL abstractions 9 | 2023 Fireflies.tech 10 | MIT 11 | README.md 12 | Icon-128x128.png 13 | https://github.com/firefliestech/fireflies.graphql 14 | latest 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Fireflies.GraphQL.Abstractions.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | false 9 | $description$ 10 | $copyright$ 11 | fireflies graphql 12 | 13 | -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Generator/GraphQLGeneratorAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Generator; 2 | 3 | public class GraphQLGeneratorAttribute : GraphQLAttribute { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Generator/GraphQLNoWrapperAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Generator; 2 | 3 | public class GraphQLNoWrapperAttribute : Attribute { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Generator/GraphQLNullable.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Generator; 2 | 3 | public class GraphQLNullable : GraphQLAttribute { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/GraphQLAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions; 2 | 3 | public abstract class GraphQLAttribute : Attribute { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/GraphQLId.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions; 2 | 3 | public abstract class GraphQLId { 4 | } 5 | 6 | public class GraphQLId : GraphQLId { 7 | private readonly T _value; 8 | 9 | public GraphQLId(T value) { 10 | _value = value; 11 | } 12 | 13 | public override string ToString() { 14 | return _value?.ToString()!; 15 | } 16 | 17 | public static implicit operator T(GraphQLId value) => value._value; 18 | public static implicit operator GraphQLId(T value) => new(value); 19 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/GraphQLInternalAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions; 2 | 3 | public class GraphQLInternalAttribute : Attribute { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/GraphQLMutationAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions; 2 | 3 | public class GraphQLMutationAttribute : GraphQLOperationAttribute { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/GraphQLOperationAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions; 2 | 3 | public abstract class GraphQLOperationAttribute : GraphQLAttribute { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/GraphQLParallel.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions; 2 | 3 | /// Will make the result builder iterate the items in parallel. 4 | /// 5 | /// Should only be used on fields with a significant IO/processing time. On simple operations this attribute usually causes execution to be slower. 6 | /// 7 | public class GraphQLParallel : GraphQLAttribute { 8 | public bool SortResults { get; set; } 9 | public int MaxDegreeOfParallelism { get; set; } = -1; 10 | 11 | public GraphQLParallel(bool sortResults = false) { 12 | SortResults = sortResults; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/GraphQLQueryAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions; 2 | 3 | public class GraphQLQueryAttribute : GraphQLOperationAttribute { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/GraphQLSubscriptionAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions; 2 | 3 | public class GraphQLSubscriptionAttribute : GraphQLOperationAttribute { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/GraphQlIdAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions; 2 | 3 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Parameter)] 4 | public class GraphQLIdAttribute : GraphQLAttribute { 5 | public bool KeepAsOriginalType { get; } 6 | 7 | public GraphQLIdAttribute() { 8 | } 9 | 10 | public GraphQLIdAttribute(bool keepAsOriginalType = false) { 11 | KeepAsOriginalType = keepAsOriginalType; 12 | } 13 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/README.md: -------------------------------------------------------------------------------- 1 | # Fireflies GraphQL abstractions 2 | 3 | Abstractions for Fireflies GraphQL. 4 | 5 | _Logo by freepik_ -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Schema/GraphQLDeprecatedAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Schema; 2 | 3 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Class)] 4 | public class GraphQLDeprecatedAttribute : GraphQLAttribute { 5 | public string Reason { get; } 6 | 7 | public GraphQLDeprecatedAttribute(string reason) { 8 | Reason = reason; 9 | } 10 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Schema/GraphQLDescriptionAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Schema; 2 | 3 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Field)] 4 | public class GraphQLDescriptionAttribute : GraphQLAttribute { 5 | public string Description { get; } 6 | 7 | public GraphQLDescriptionAttribute(string description) { 8 | Description = description; 9 | } 10 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Schema/GraphQLUnionAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Schema; 2 | 3 | [AttributeUsage(AttributeTargets.Interface)] 4 | public class GraphQLUnionAttribute : GraphQLAttribute { 5 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Sorting/GraphQLSortAttribute.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Generator; 2 | 3 | namespace Fireflies.GraphQL.Abstractions.Sorting; 4 | 5 | public class GraphQLSortAttribute : GraphQLGeneratorAttribute { 6 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Sorting/SortOrder.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Sorting; 2 | 3 | public enum SortOrder { 4 | ASC, 5 | DESC 6 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Where/BooleanWhere.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Where; 2 | 3 | public class BooleanWhere : Where { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Where/CollectionWhere.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Where; 2 | 3 | public class CollectionWhere { 4 | public T? Any { get; set; } 5 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Where/DateTimeWhere.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Where; 2 | 3 | public class DateTimeWhere : Where { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Where/DecimalWhere.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Where; 2 | 3 | public class DecimalWhere : NumberWhere { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Where/GraphQLWhereAttribute.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Generator; 2 | 3 | namespace Fireflies.GraphQL.Abstractions.Where; 4 | 5 | public class GraphQLWhereAttribute : GraphQLGeneratorAttribute { 6 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Where/IntWhere.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Where; 2 | 3 | public class IntWhere : NumberWhere { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Where/NumberWhere.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Where; 2 | 3 | public abstract class NumberWhere : Where where T : struct { 4 | public T? GreaterThan { get; set; } 5 | public T? GreaterThanOrEq { get; set; } 6 | public T? LessThan { get; set; } 7 | public T? LessThanOrEq { get; set; } 8 | 9 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Where/StringWhere.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Abstractions.Where; 2 | 3 | public class StringWhere : Where { 4 | public string? Contains { get; set; } 5 | public string? DoesntContain { get; set; } 6 | public string? StartsWith { get; set; } 7 | public string? DoesntStartWith { get; set; } 8 | public string? EndsWith { get; set; } 9 | public string? DoesntEndWith { get; set; } 10 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Abstractions/Where/Where.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Generator; 2 | 3 | namespace Fireflies.GraphQL.Abstractions.Where; 4 | 5 | public abstract class Where { 6 | [GraphQLNullable] 7 | public T? Equal { get; set; } 8 | 9 | [GraphQLNullable] 10 | public T? NotEqual { get; set; } 11 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.AspNet/AspNetConnectionContext.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core; 2 | using Fireflies.IoC.Abstractions; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace Fireflies.GraphQL.AspNet; 6 | 7 | internal class AspNetConnectionContext : IConnectionContext { 8 | private readonly GraphQLOptions _options; 9 | 10 | private readonly CancellationTokenSource _cancellationTokenSource; 11 | 12 | public CancellationToken CancellationToken => _cancellationTokenSource.Token; 13 | public IDependencyResolver ConnectionDependencyResolver { get; internal set; } = null!; 14 | 15 | public HttpContext HttpContext { get; } 16 | public Dictionary RequestHeaders => HttpContext.Request.Headers.Where(x => !x.Key.StartsWith(":")).ToDictionary(x => x.Key, x => x.Value.ToArray()); 17 | public string QueryString => HttpContext.Request.QueryString.Value; 18 | 19 | public IResultBuilder Results { get; } 20 | 21 | public bool IsWebSocket => WebSocket != null; 22 | public IWsProtocolHandler? WebSocket { get; internal set; } 23 | 24 | public AspNetConnectionContext(HttpContext httpContext, GraphQLOptions options) { 25 | _options = options; 26 | _cancellationTokenSource = new(); 27 | 28 | HttpContext = httpContext; 29 | Results = new ResultBuilder(HttpContext.WebSockets.IsWebSocketRequest, _cancellationTokenSource); 30 | } 31 | 32 | private AspNetConnectionContext(AspNetConnectionContext parent) { 33 | _options = parent._options; 34 | _cancellationTokenSource = parent._cancellationTokenSource; 35 | 36 | HttpContext = parent.HttpContext; 37 | Results = new ResultBuilder(HttpContext.WebSockets.IsWebSocketRequest, _cancellationTokenSource); 38 | ConnectionDependencyResolver = parent.ConnectionDependencyResolver; 39 | } 40 | 41 | public IConnectionContext CreateChildContext() { 42 | return new AspNetConnectionContext(this); 43 | } 44 | 45 | public IDependencyResolver CreateRequestContainer() { 46 | var lifetimeScopeResolver = ConnectionDependencyResolver.BeginLifetimeScope(builder => { 47 | builder.RegisterType(); 48 | builder.RegisterInstance((IConnectionContext)this); 49 | if(ConnectionDependencyResolver.TryResolve>(out var innerBuilder)) { 50 | innerBuilder!.Build(builder, HttpContext); 51 | } 52 | 53 | _options.Extensions.BuildRequestLifetimeScope(builder); 54 | }); 55 | 56 | return lifetimeScopeResolver; 57 | } 58 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.AspNet/Fireflies.GraphQL.AspNet.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | Fireflies.Tech 8 | Fireflies GraphQL ASP.NET middleware 9 | 2023 Fireflies.tech 10 | MIT 11 | README.md 12 | Icon-128x128.png 13 | https://github.com/firefliestech/fireflies.graphql 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Fireflies.GraphQL.AspNet/Fireflies.GraphQL.AspNet.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | false 9 | $description$ 10 | $copyright$ 11 | fireflies graphql aspnet 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Fireflies.GraphQL.AspNet/GraphQLMiddlewareExtensions.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core; 2 | using Microsoft.AspNetCore.Builder; 3 | 4 | namespace Fireflies.GraphQL.AspNet; 5 | 6 | public static class GraphQLMiddlewareExtensions { 7 | public static IApplicationBuilder UseGraphQL(this IApplicationBuilder builder, GraphQLOptions? options = null) { 8 | return builder.UseMiddleware(options ?? new GraphQLOptions()); 9 | } 10 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/AuthorizationHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Reflection; 3 | using Fireflies.GraphQL.Abstractions.Authorization; 4 | using Fireflies.GraphQL.Core.Exceptions; 5 | using GraphQLParser.AST; 6 | 7 | namespace Fireflies.GraphQL.Core; 8 | 9 | internal static class AuthorizationHelper { 10 | private static readonly ConcurrentDictionary MethodCache = new(); 11 | 12 | public static async Task Authorize(MemberInfo memberInfo, GraphQLField field, IRequestContext requestContext) { 13 | var authorizationAttributes = memberInfo.CustomAttributes.Select(x => x.AttributeType).Where(c => c.IsAssignableTo(typeof(GraphQLAuthorizationBaseAttribute))); 14 | await Authorize(requestContext, field, authorizationAttributes).ConfigureAwait(false); 15 | } 16 | 17 | private static async Task Authorize(IRequestContext requestContext, GraphQLField field, IEnumerable authorizationAttributes) { 18 | var any = false; 19 | 20 | foreach(var authorizationAttribute in authorizationAttributes) { 21 | any = true; 22 | var handler = requestContext.DependencyResolver.Resolve(authorizationAttribute); 23 | 24 | var actualAuthorizeMethod = MethodCache.GetOrAdd(authorizationAttribute, _ => authorizationAttribute.GetMethod(nameof(GraphQLAuthorizationAttribute.Authorize), BindingFlags.Instance | BindingFlags.Public)!); 25 | var argumentBuilder = new ArgumentBuilder(field.Arguments, actualAuthorizeMethod, requestContext, null); 26 | var arguments = await argumentBuilder.Build(field); 27 | var authorized = await ((Task)actualAuthorizeMethod.Invoke(handler, arguments)!).ConfigureAwait(false); 28 | if(authorized) 29 | return; 30 | } 31 | 32 | if(!any) 33 | return; 34 | 35 | throw new GraphQLUnauthorizedException(); 36 | } 37 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Exceptions/DuplicateNameException.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Exceptions; 2 | 3 | public class DuplicateNameException : GraphQLException { 4 | public DuplicateNameException(string? message) : base(message) { 5 | } 6 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Exceptions/GraphQLException.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Exceptions; 2 | 3 | public class GraphQLException : Exception { 4 | public GraphQLException(string? message) : base(message) { 5 | } 6 | 7 | public GraphQLException(string? message, Exception innerException) : base(message, innerException) { 8 | } 9 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Exceptions/GraphQLTypeException.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Exceptions; 2 | 3 | public class GraphQLTypeException : GraphQLException { 4 | public GraphQLTypeException(string message) : base(message) { 5 | } 6 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Exceptions/GraphQLUnauthorizedException.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Exceptions; 2 | 3 | internal class GraphQLUnauthorizedException : Exception { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/ExtensionRegistry.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.IoC.Abstractions; 2 | 3 | namespace Fireflies.GraphQL.Core; 4 | 5 | public class ExtensionRegistry { 6 | private readonly List _extensionBuilders = new(); 7 | 8 | public void Add(IExtensionBuilder builder) { 9 | _extensionBuilders.Add(builder); 10 | } 11 | 12 | public void BuildOptions() { 13 | foreach(var extensionBuilder in _extensionBuilders) { 14 | extensionBuilder.Build(); 15 | } 16 | } 17 | 18 | public void BuildRequestLifetimeScope(ILifetimeScopeBuilder lifetimeScopeBuilder) { 19 | foreach(var extensionBuilder in _extensionBuilders) { 20 | extensionBuilder.BuildRequestLifetimeScope(lifetimeScopeBuilder); 21 | } 22 | } 23 | 24 | public void BuildGraphQLLifetimeScope(ILifetimeScopeBuilder lifetimeScopeBuilder) { 25 | foreach(var extensionBuilder in _extensionBuilders) { 26 | extensionBuilder.BuildGraphQLLifetimeScope(lifetimeScopeBuilder); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Extensions/AsyncParallelData.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Extensions; 2 | 3 | public record struct AsyncParallelData(T Value, int Index); -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Extensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Threading.Tasks.Dataflow; 3 | using Fireflies.Utility.Reflection; 4 | 5 | namespace Fireflies.GraphQL.Core.Extensions; 6 | 7 | public static class EnumerableExtensions { 8 | private static readonly ConcurrentDictionary _collectionCache = new(); 9 | 10 | public static bool IsQueryable(this Type type) { 11 | return type.IsQueryable(out _); 12 | } 13 | 14 | public static bool IsCollection(this Type type, out Type elementType) { 15 | var realType = type.DiscardTask(); 16 | elementType = realType; 17 | 18 | var cachedType = _collectionCache.GetOrAdd(type, _ => { 19 | if(realType.IsEnumerable(out var enumerableType) || realType.IsQueryable(out enumerableType)) 20 | return enumerableType; 21 | 22 | var implementICollection = realType.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>)); 23 | return implementICollection?.GetGenericArguments()[0]; 24 | }); 25 | 26 | if(cachedType == null) 27 | return false; 28 | 29 | elementType = cachedType; 30 | return true; 31 | } 32 | 33 | public static bool IsCollection(this Type type) { 34 | return type.IsCollection(out _); 35 | } 36 | 37 | private static bool IsQueryable(this Type type, out Type elementType) { 38 | return GetElementTypeForEnumerableOf(type, out elementType, typeof(IQueryable<>), false); 39 | } 40 | 41 | private static bool IsEnumerable(this Type type, out Type elementType) { 42 | return GetElementTypeForEnumerableOf(type, out elementType, typeof(IEnumerable<>), true); 43 | } 44 | 45 | private static bool GetElementTypeForEnumerableOf(Type type, out Type elementType, Type lookingFor, bool checkArray) { 46 | elementType = type.DiscardTask(); 47 | 48 | if(elementType.IsGenericType) { 49 | var typeDefinition = elementType.GetGenericTypeDefinition(); 50 | 51 | if(typeDefinition == lookingFor) { 52 | elementType = GetElementType(elementType); 53 | return true; 54 | } 55 | } 56 | 57 | if(checkArray && elementType.IsArray) { 58 | elementType = GetElementType(elementType); 59 | return true; 60 | } 61 | 62 | return false; 63 | } 64 | 65 | private static Type GetElementType(this Type type) { 66 | return type.IsArray ? type.GetElementType()! : type.GetGenericArguments()[0]; 67 | } 68 | 69 | public static Task AsyncParallelForEach(this IEnumerable source, Func, Task> body, int maxDegreeOfParallelism = DataflowBlockOptions.Unbounded, TaskScheduler? scheduler = null) { 70 | var options = new ExecutionDataflowBlockOptions { 71 | MaxDegreeOfParallelism = maxDegreeOfParallelism 72 | }; 73 | if(scheduler != null) 74 | options.TaskScheduler = scheduler; 75 | 76 | var block = new ActionBlock>(body, options); 77 | 78 | var index = 0; 79 | foreach(var item in source) 80 | block.Post(new AsyncParallelData(item, index++)); 81 | 82 | block.Complete(); 83 | return block.Completion; 84 | } 85 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Extensions/QueryableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using System.Reflection; 3 | 4 | namespace Fireflies.GraphQL.Core.Extensions; 5 | 6 | public static class QueryableExtensions { 7 | public static IOrderedQueryable OrderByMember(this IQueryable source, string memberPath) { 8 | return source.OrderByMemberUsing(memberPath, "OrderBy"); 9 | } 10 | 11 | public static IOrderedQueryable OrderByMemberDescending(this IQueryable source, string memberPath) { 12 | return source.OrderByMemberUsing(memberPath, "OrderByDescending"); 13 | } 14 | 15 | public static IOrderedQueryable ThenByMember(this IOrderedQueryable source, string memberPath) { 16 | return source.OrderByMemberUsing(memberPath, "ThenBy"); 17 | } 18 | 19 | public static IOrderedQueryable ThenByMemberDescending(this IOrderedQueryable source, string memberPath) { 20 | return source.OrderByMemberUsing(memberPath, "ThenByDescending"); 21 | } 22 | 23 | private static IOrderedQueryable OrderByMemberUsing(this IQueryable source, string memberPath, string method) { 24 | var parameter = Expression.Parameter(typeof(T), "item"); 25 | 26 | var memberParts = GetMemberParts(memberPath); 27 | var member = memberParts.Aggregate((Expression)parameter, Expression.PropertyOrField); 28 | var keySelector = Expression.Lambda(member, parameter); 29 | var methodCall = Expression.Call(typeof(Queryable), method, new[] { parameter.Type, member.Type }, source.Expression, Expression.Quote(keySelector)); 30 | return (IOrderedQueryable)source.Provider.CreateQuery(methodCall); 31 | } 32 | 33 | public static IEnumerable GetMemberParts(string memberPath) { 34 | var memberParts = memberPath.Split('.'); 35 | 36 | var currentType = typeof(T); 37 | for(var i = 0; i < memberParts.Length; i++) { 38 | var member = currentType.GetMember(memberParts[i], BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase).FirstOrDefault(); 39 | if(member != null) { 40 | memberParts[i] = member.Name; 41 | currentType = member switch { 42 | FieldInfo fieldInfo => fieldInfo.FieldType, 43 | MethodInfo methodInfo => methodInfo.ReturnType, 44 | PropertyInfo propertyInfo => propertyInfo.PropertyType, 45 | _ => throw new ArgumentOutOfRangeException(nameof(member)) 46 | }; 47 | } else { 48 | break; 49 | } 50 | } 51 | 52 | return memberParts; 53 | } 54 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Federation/FederatedQuery.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Generator; 2 | using GraphQLParser.AST; 3 | using GraphQLParser.Visitors; 4 | 5 | namespace Fireflies.GraphQL.Core.Federation; 6 | 7 | [GraphQLNoWrapper] 8 | public abstract class FederatedQuery { 9 | public string Query { get; private set; } 10 | 11 | protected FederatedQuery(string query) { 12 | Query = query; 13 | } 14 | 15 | protected static async Task CreateSelectionSet(ASTNode astNode, IRequestContext requestContext) { 16 | await using var query = new StringWriter(); 17 | var sdlPrinter = new SDLPrinter(); 18 | var hasSelectionSetNode = (IHasSelectionSetNode)astNode; 19 | await sdlPrinter.PrintAsync(hasSelectionSetNode.SelectionSet, query, requestContext.CancellationToken); 20 | return query.ToString(); 21 | } 22 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Federation/FederationBase.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Federation; 2 | 3 | public class FederationBase { 4 | protected readonly IConnectionContext ConnectionContext; 5 | 6 | public FederationBase(IConnectionContext context) { 7 | ConnectionContext = context; 8 | } 9 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Federation/FederationClient.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Nodes; 3 | using Fireflies.GraphQL.Core.Federation.Schema; 4 | using Fireflies.GraphQL.Core.Json; 5 | using GraphQLParser.AST; 6 | 7 | namespace Fireflies.GraphQL.Core.Federation; 8 | 9 | internal class FederationClient { 10 | private readonly string _url; 11 | private readonly HttpClient _httpClient; 12 | 13 | public FederationClient(string url) { 14 | _url = url; 15 | _httpClient = new HttpClient(); 16 | } 17 | 18 | public async Task FetchSchema() { 19 | var stringContent = new StringContent(FederationQueryBuilder.BuildQuery(FederationQueryBuilder.SchemaQuery, null, OperationType.Query, "FederatedIntrospectionQuery", null)); 20 | var result = await _httpClient.PostAsync(_url, stringContent).ConfigureAwait(false); 21 | result.EnsureSuccessStatusCode(); 22 | 23 | var stream = await result.Content.ReadAsStreamAsync().ConfigureAwait(false); 24 | var deserializeObject = await JsonSerializer.DeserializeAsync(stream).ConfigureAwait(false); 25 | 26 | if(deserializeObject?["data"]?["__schema"] == null) 27 | throw new FederationException("Invalid schema received"); 28 | 29 | return deserializeObject["data"]!["__schema"]!.Deserialize(DefaultJsonSerializerSettings.DefaultSettings)!; 30 | } 31 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Federation/FederationException.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core.Exceptions; 2 | 3 | namespace Fireflies.GraphQL.Core.Federation; 4 | 5 | public class FederationException : GraphQLException { 6 | public FederationException(string message) : base(message) { 7 | } 8 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Federation/FederationExecutionException.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Nodes; 2 | 3 | namespace Fireflies.GraphQL.Core.Federation; 4 | 5 | public class FederationExecutionException : Exception { 6 | public JsonArray Node { get; } 7 | 8 | public FederationExecutionException(JsonArray jsonNode) : base("Federated call returned error") { 9 | Node = jsonNode; 10 | } 11 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Federation/FederationQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Nodes; 3 | using Fireflies.GraphQL.Core.Json; 4 | using GraphQLParser.AST; 5 | 6 | namespace Fireflies.GraphQL.Core.Federation; 7 | 8 | public static class FederationQueryBuilder { 9 | public static string BuildQuery(string query, string? fragments, OperationType operationType, string name, Dictionary? variables) => 10 | JsonSerializer.Serialize(new JsonObject { 11 | { "query", JsonValue.Create($"{operationType.ToString().ToLower()} {name} {{ {query} }}\r\n{fragments}") }, 12 | { "variables", variables != null ? JsonValue.Create(variables) : null } 13 | }, DefaultJsonSerializerSettings.DefaultSettings); 14 | 15 | public static string SchemaQuery => 16 | @"__schema { 17 | queryType { 18 | name 19 | } 20 | mutationType { 21 | name 22 | } 23 | subscriptionType { 24 | name 25 | } 26 | types { 27 | ...FullType 28 | } 29 | directives { 30 | name 31 | description 32 | locations 33 | args { 34 | ...InputValue 35 | } 36 | } 37 | } 38 | } 39 | 40 | fragment FullType on __Type { 41 | kind 42 | name 43 | description 44 | federated 45 | fields(includeDeprecated: true) { 46 | name 47 | description 48 | args { 49 | ...InputValue 50 | } 51 | type { 52 | ...TypeRef 53 | } 54 | isDeprecated 55 | deprecationReason 56 | } 57 | inputFields { 58 | ...InputValue 59 | } 60 | interfaces { 61 | ...TypeRef 62 | } 63 | enumValues(includeDeprecated: true) { 64 | name 65 | description 66 | isDeprecated 67 | deprecationReason 68 | } 69 | possibleTypes { 70 | ...TypeRef 71 | } 72 | } 73 | 74 | fragment InputValue on __InputValue { 75 | name 76 | description 77 | type { 78 | ...TypeRef 79 | } 80 | defaultValue 81 | } 82 | 83 | fragment TypeRef on __Type { 84 | kind 85 | name 86 | ofType { 87 | kind 88 | name 89 | ofType { 90 | kind 91 | name 92 | ofType { 93 | kind 94 | name 95 | ofType { 96 | kind 97 | name 98 | ofType { 99 | kind 100 | name 101 | ofType { 102 | kind 103 | name 104 | ofType { 105 | kind 106 | name 107 | } 108 | } 109 | } 110 | } 111 | } 112 | } 113 | } 114 | "; 115 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Federation/GraphQLFederatedAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Federation; 2 | 3 | public class GraphQLFederatedAttribute : Attribute { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Federation/Schema/FederationField.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Generator; 2 | 3 | namespace Fireflies.GraphQL.Core.Federation.Schema; 4 | 5 | // ReSharper disable once InconsistentNaming 6 | [GraphQLNoWrapper] 7 | public class FederationField : FederationFieldBase { 8 | public FederationInputValue[] Args { get; set; } = Array.Empty(); 9 | 10 | public bool IsDeprecated { get; set; } 11 | public string? DeprecationReason { get; set; } 12 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Federation/Schema/FederationFieldBase.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Federation.Schema; 2 | 3 | public abstract class FederationFieldBase { 4 | public string Name { get; set; } = null!; 5 | public string? Description { get; set; } 6 | public FederationType Type { get; set; } = null!; 7 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Federation/Schema/FederationInputValue.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Generator; 2 | 3 | namespace Fireflies.GraphQL.Core.Federation.Schema; 4 | 5 | // ReSharper disable once InconsistentNaming 6 | [GraphQLNoWrapper] 7 | public class FederationInputValue : FederationFieldBase { 8 | public string? DefaultValue { get; set; } 9 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Federation/Schema/FederationSchema.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Generator; 2 | using Fireflies.GraphQL.Core.Schema; 3 | 4 | namespace Fireflies.GraphQL.Core.Federation.Schema; 5 | 6 | // ReSharper disable once InconsistentNaming 7 | [GraphQLNoWrapper] 8 | public class FederationSchema { 9 | public string? Description { get; set; } 10 | public FederationType[] Types { get; set; } = Array.Empty(); 11 | public FederationType? QueryType { get; set; } 12 | public FederationType? MutationType { get; set; } 13 | public FederationType? SubscriptionType { get; set; } 14 | public __Directive[] Directives { get; set; } = Array.Empty<__Directive>(); 15 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Federation/Schema/FederationType.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Generator; 2 | using Fireflies.GraphQL.Core.Schema; 3 | 4 | namespace Fireflies.GraphQL.Core.Federation.Schema; 5 | 6 | // ReSharper disable InconsistentNaming 7 | [GraphQLNoWrapper] 8 | public class FederationType { 9 | public __TypeKind Kind { get; set; } 10 | public string? Name { get; set; } 11 | public string? Description { get; set; } 12 | 13 | public bool Federated { get; set; } 14 | 15 | public FederationField[] Fields { get; set; } = Array.Empty(); 16 | public FederationType[] Interfaces { get; set; } = Array.Empty(); 17 | public FederationType[] PossibleTypes { get; set; } = Array.Empty(); 18 | 19 | public __EnumValue[] EnumValues { get; set; } = Array.Empty<__EnumValue>(); 20 | 21 | public FederationInputValue[] InputFields { get; set; } = Array.Empty(); 22 | public FederationType? OfType { get; set; } 23 | 24 | public string? SpecifiedByURL { get; set; } = null; 25 | } 26 | // ReSharper enable InconsistentNaming -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Fireflies.GraphQL.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | Fireflies.Tech 8 | Fireflies GraphQL core 9 | 2023 Fireflies.tech 10 | MIT 11 | README.md 12 | Icon-128x128.png 13 | https://github.com/firefliestech/fireflies.graphql 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Fireflies.GraphQL.Core.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | false 9 | $description$ 10 | $copyright$ 11 | fireflies graphql core 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/FragmentAccessor.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | using GraphQLParser.Visitors; 3 | 4 | namespace Fireflies.GraphQL.Core; 5 | 6 | public class FragmentAccessor { 7 | private readonly GraphQLDocument _document; 8 | private readonly IRequestContext _context; 9 | private Dictionary? _fragments; 10 | 11 | private readonly SemaphoreSlim _semaphore = new(1); 12 | 13 | private static readonly FragmentVisitor FragmentVisitorInstance; 14 | 15 | static FragmentAccessor() { 16 | FragmentVisitorInstance = new FragmentVisitor(); 17 | } 18 | 19 | public FragmentAccessor(GraphQLDocument document, IRequestContext context) { 20 | _document = document; 21 | _context = context; 22 | } 23 | 24 | public async Task GetFragment(GraphQLFragmentName fragmentName) { 25 | return await GetFragment(fragmentName.Name.StringValue); 26 | } 27 | 28 | public async Task GetFragment(string name) { 29 | if(_fragments != null) 30 | return _fragments[name]; 31 | 32 | try { 33 | await _semaphore.WaitAsync().ConfigureAwait(false); 34 | if(_fragments == null) { 35 | var context = new FragmentVisitorContext(_context); 36 | await FragmentVisitorInstance.VisitAsync(_document, context).ConfigureAwait(false); 37 | _fragments = context.FragmentDefinitions.ToDictionary(x => x.FragmentName.Name.StringValue); 38 | } 39 | } finally { 40 | _semaphore.Release(); 41 | } 42 | 43 | return _fragments[name]; 44 | } 45 | 46 | private class FragmentVisitor : ASTVisitor { 47 | protected override ValueTask VisitFragmentDefinitionAsync(GraphQLFragmentDefinition fragmentDefinition, FragmentVisitorContext context) { 48 | context.FragmentDefinitions.Add(fragmentDefinition); 49 | return ValueTask.CompletedTask; 50 | } 51 | } 52 | 53 | private class FragmentVisitorContext : IASTVisitorContext { 54 | private readonly IRequestContext _context; 55 | 56 | public FragmentVisitorContext(IRequestContext context) { 57 | _context = context; 58 | } 59 | 60 | public List FragmentDefinitions { get; } = new(); 61 | public CancellationToken CancellationToken => _context.CancellationToken; 62 | } 63 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/BaseDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Reflection.Emit; 3 | 4 | namespace Fireflies.GraphQL.Core.Generators; 5 | 6 | public class BaseDescriptor { 7 | public MemberInfo MemberInfo { get; set; } 8 | public IEnumerable ParameterTypes { get; set; } 9 | public Type ReturnType { get; set; } 10 | public bool GeneratingInterface { get; set; } 11 | public IEnumerable> DefineParameterCallbacks { get; set; } 12 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/Connection/ConnectionBase.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Generator; 2 | 3 | namespace Fireflies.GraphQL.Core.Generators.Connection; 4 | 5 | public abstract class ConnectionBase { 6 | 7 | } 8 | 9 | [GraphQLNoWrapper] 10 | public class ConnectionBase : ConnectionBase where TEdge : EdgeBase { 11 | private readonly IEnumerable _filteredEdges; 12 | 13 | protected ConnectionBase(IEnumerable edges, int first, string? after) { 14 | if(first <= 0) 15 | first = 1; 16 | 17 | TotalCount = edges.Count(); 18 | 19 | _filteredEdges = edges; 20 | if(after != null) 21 | _filteredEdges = _filteredEdges.SkipWhile(x => x.Cursor != after).Skip(1); 22 | _filteredEdges = _filteredEdges.Take(first); 23 | 24 | var lastFilteredEdge = _filteredEdges.LastOrDefault(); 25 | var lastEdge = edges.LastOrDefault(); 26 | PageInfo = new PageInfo { 27 | EndCursor = lastFilteredEdge?.Cursor ?? null, 28 | HasNextPage = lastEdge != null && lastFilteredEdge != lastEdge 29 | }; 30 | } 31 | 32 | public int TotalCount { get; } 33 | public IEnumerable Edges => _filteredEdges; 34 | public PageInfo PageInfo { get; } 35 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/Connection/EdgeBase.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Text.Json; 3 | using System.Text.Json.Nodes; 4 | using Fireflies.GraphQL.Abstractions; 5 | using Fireflies.GraphQL.Abstractions.Generator; 6 | using Fireflies.Utility.Reflection; 7 | using Fireflies.Utility.Reflection.Fasterflect; 8 | 9 | namespace Fireflies.GraphQL.Core.Generators.Connection; 10 | 11 | [GraphQLNoWrapper] 12 | public abstract class EdgeBase { 13 | protected EdgeBase(TBase node) { 14 | Node = node; 15 | 16 | var cursor = new JsonObject(); 17 | foreach(var member in ReflectionCache.GetMembers(node.GetType())) { 18 | if(member.HasCustomAttribute()) { 19 | var value = member switch { 20 | PropertyInfo propertyInfo => Reflect.PropertyGetter(propertyInfo), 21 | MethodInfo methodInfo => Reflect.Method(methodInfo, typeof(WrapperRegistry))(node, (WrapperRegistry)null!), //TODO: Maybe ID-properties should not be wrapped as a method? 22 | _ => null 23 | }; 24 | 25 | if(value == null) { 26 | cursor.Add(member.Name, JsonValue.Create(null)); 27 | } else { 28 | cursor.Add(member.Name, JsonValue.Create(value.ToString())); 29 | } 30 | 31 | } 32 | } 33 | 34 | var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(JsonSerializer.Serialize(cursor)); 35 | Cursor = Convert.ToBase64String(plainTextBytes); 36 | } 37 | 38 | public TBase Node { get; set; } 39 | public string Cursor { get; } 40 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/Connection/PageInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Generators.Connection; 2 | 3 | public class PageInfo { 4 | public string? EndCursor { get; set; } 5 | public bool HasNextPage { get; set; } 6 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/GeneratorRegistry.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core.Exceptions; 2 | 3 | namespace Fireflies.GraphQL.Core.Generators; 4 | 5 | internal class GeneratorRegistry { 6 | private readonly List _registered = new(); 7 | 8 | public IEnumerable GetGenerators() where T : IGenerator { 9 | return _registered.OfType(); 10 | } 11 | 12 | public void Add(TGenerator generator) where TGenerator : IGenerator { 13 | _registered.Add(generator); 14 | } 15 | 16 | public void AddBefore(IGenerator generator) where TAddBefore : IGenerator { 17 | var oldIndex = _registered.FindIndex(x => x is TAddBefore); 18 | if(oldIndex == -1) 19 | throw new GraphQLException($"Cant find a generator with type {typeof(TAddBefore).Name}"); 20 | 21 | _registered.Insert(oldIndex, generator); 22 | } 23 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/IGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Generators; 2 | 3 | public interface IGenerator { 4 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/IMethodExtenderGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Fireflies.GraphQL.Core.Generators; 4 | 5 | public interface IMethodExtenderGenerator : IGenerator { 6 | MethodExtenderDescriptor GetMethodExtenderDescriptor(MemberInfo memberInfo, Type originalType, Type wrappedReturnType, ref int parameterCount); 7 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/ITypeExtenderGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection.Emit; 2 | 3 | namespace Fireflies.GraphQL.Core.Generators; 4 | 5 | public interface ITypeExtenderGenerator : IGenerator { 6 | void Extend(TypeBuilder typeBuilder, MethodBuilder wrappedMethod, BaseDescriptor baseDescriptor); 7 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/MethodExtenderDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection.Emit; 2 | 3 | namespace Fireflies.GraphQL.Core.Generators; 4 | 5 | public class MethodExtenderDescriptor { 6 | public bool ShouldDecorate { get; } 7 | 8 | public Type[] ParameterTypes { get; } 9 | public Action DefineParametersCallback { get; } 10 | 11 | public Action GenerateCallback { get; } 12 | 13 | public MethodExtenderDescriptor() { 14 | ShouldDecorate = false; 15 | ParameterTypes = Array.Empty(); 16 | DefineParametersCallback = _ => { }; 17 | GenerateCallback = (_, _) => { }; 18 | } 19 | 20 | public MethodExtenderDescriptor(Type[] parameterTypes, Action defineParametersCallback, Action generateCallback) { 21 | ShouldDecorate = true; 22 | ParameterTypes = parameterTypes; 23 | DefineParametersCallback = defineParametersCallback; 24 | GenerateCallback = generateCallback; 25 | } 26 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/MethodExtenderStep.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Generators; 2 | 3 | public enum MethodExtenderStep { 4 | BeforeWrap, 5 | AfterWrap 6 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/Where/BooleanWhereExpressionHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using Fireflies.GraphQL.Abstractions.Where; 3 | 4 | namespace Fireflies.GraphQL.Core.Generators.Where; 5 | 6 | public static class BooleanWhereExpressionHelper { 7 | public static Expression CreateExpression(string operation, Expression member, object? value) { 8 | if(operation.Equals(nameof(BooleanWhere.Equal), StringComparison.InvariantCultureIgnoreCase)) { 9 | return WhereExpressionBuilderHelper.CreateOperationExpression(member, value, false, Expression.Equal); 10 | } 11 | 12 | if(operation.Equals(nameof(BooleanWhere.NotEqual), StringComparison.InvariantCultureIgnoreCase)) { 13 | return WhereExpressionBuilderHelper.CreateOperationExpression(member, value, false, Expression.NotEqual); 14 | } 15 | 16 | throw new NotImplementedException($"Operation type '{operation}' is not implemented for {nameof(BooleanWhere)}"); 17 | } 18 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/Where/CollectionWhereExpressionBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using System.Reflection; 3 | using Fireflies.GraphQL.Abstractions.Where; 4 | using GraphQLParser.AST; 5 | 6 | namespace Fireflies.GraphQL.Core.Generators.Where; 7 | 8 | internal class CollectionWhereExpressionBuilder : WhereExpressionBuilder { 9 | public MethodCallExpression Result { get; protected set; } 10 | } 11 | 12 | internal class CollectionWhereExpressionBuilder : CollectionWhereExpressionBuilder { 13 | private readonly ParameterExpression _parameter; 14 | private readonly ValueAccessor _valueAccessor; 15 | private readonly Stack _parentTypes = new(); 16 | 17 | public CollectionWhereExpressionBuilder(Stack parentFields, ValueAccessor valueAccessor, ParameterExpression parameter) { 18 | _parameter = parameter; 19 | _valueAccessor = valueAccessor; 20 | _parentTypes.Push(typeof(TElement)); 21 | ParentFields = parentFields; 22 | } 23 | 24 | protected override async ValueTask VisitObjectFieldAsync(GraphQLObjectField objectField, IRequestContext context) { 25 | if(objectField.Name.StringValue.Equals(nameof(CollectionWhere.Any), StringComparison.InvariantCultureIgnoreCase)) { 26 | var subWhereExpressionBuilder = new WhereExpressionBuilder(_valueAccessor); 27 | await subWhereExpressionBuilder.VisitAsync(objectField.Value, context); 28 | 29 | var method = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).First(x => x.Name == nameof(Enumerable.Any) && x.GetParameters().Length == 2).MakeGenericMethod(typeof(TElement)); 30 | var expressionCall = Expression.Call(null, method, GetMemberPath(_parameter), subWhereExpressionBuilder.Result); 31 | 32 | Result = expressionCall; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/Where/EnumerableWhereBuilder.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | 3 | namespace Fireflies.GraphQL.Core.Generators.Where; 4 | 5 | public class EnumerableWhereBuilder { 6 | private readonly ValueAccessor _valueAccessor; 7 | public IEnumerable Result { get; private set; } 8 | 9 | public EnumerableWhereBuilder(IEnumerable queryable, ValueAccessor valueAccessor) { 10 | _valueAccessor = valueAccessor; 11 | Result = queryable; 12 | } 13 | 14 | public ValueTask Build(ASTNode? node, IRequestContext context) { 15 | var whereExpressionBuilder = new WhereExpressionBuilder(_valueAccessor); 16 | whereExpressionBuilder.VisitAsync(node, context).GetAwaiter().GetResult(); 17 | if(whereExpressionBuilder.Result != null) 18 | Result = Result.Where(whereExpressionBuilder.Result.Compile()); 19 | 20 | return ValueTask.CompletedTask; 21 | } 22 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/Where/NumberWhereExpressionHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using Fireflies.GraphQL.Abstractions.Where; 3 | 4 | namespace Fireflies.GraphQL.Core.Generators.Where; 5 | 6 | public static class NumberWhereExpressionHelper where T : struct { 7 | public static Expression CreateExpression(string operation, Expression member, object? value) { 8 | if(operation.Equals(nameof(NumberWhere.Equal), StringComparison.InvariantCultureIgnoreCase)) { 9 | return WhereExpressionBuilderHelper.CreateOperationExpression(member, value, false, Expression.Equal); 10 | } 11 | 12 | if(operation.Equals(nameof(NumberWhere.NotEqual), StringComparison.InvariantCultureIgnoreCase)) { 13 | return WhereExpressionBuilderHelper.CreateOperationExpression(member, value, false, Expression.NotEqual); 14 | } 15 | 16 | if(operation.Equals(nameof(NumberWhere.GreaterThanOrEq), StringComparison.InvariantCultureIgnoreCase)) { 17 | return WhereExpressionBuilderHelper.CreateOperationExpression(member, value, false, Expression.GreaterThanOrEqual); 18 | } 19 | 20 | if(operation.Equals(nameof(NumberWhere.GreaterThan), StringComparison.InvariantCultureIgnoreCase)) { 21 | return WhereExpressionBuilderHelper.CreateOperationExpression(member, value, false, Expression.GreaterThan); 22 | } 23 | 24 | if(operation.Equals(nameof(NumberWhere.LessThanOrEq), StringComparison.InvariantCultureIgnoreCase)) { 25 | return WhereExpressionBuilderHelper.CreateOperationExpression(member, value, false, Expression.LessThanOrEqual); 26 | } 27 | 28 | if(operation.Equals(nameof(NumberWhere.LessThan), StringComparison.InvariantCultureIgnoreCase)) { 29 | return WhereExpressionBuilderHelper.CreateOperationExpression(member, value, false, Expression.LessThan); 30 | } 31 | 32 | throw new NotImplementedException($"Operation type '{operation}' is not implemented for {nameof(NumberWhere)}"); 33 | } 34 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/Where/QueryableWhereBuilder.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | 3 | namespace Fireflies.GraphQL.Core.Generators.Where; 4 | 5 | internal class QueryableWhereBuilder { 6 | private readonly ValueAccessor _valueAccessor; 7 | public IQueryable Result { get; private set; } 8 | 9 | public QueryableWhereBuilder(IQueryable queryable, ValueAccessor valueAccessor) { 10 | _valueAccessor = valueAccessor; 11 | Result = queryable; 12 | } 13 | 14 | public ValueTask Build(ASTNode? node, IRequestContext context) { 15 | var whereExpressionBuilder = new WhereExpressionBuilder(_valueAccessor); 16 | whereExpressionBuilder.VisitAsync(node, context).GetAwaiter().GetResult(); 17 | if(whereExpressionBuilder.Result != null) 18 | Result = Result.Where(whereExpressionBuilder.Result); 19 | 20 | return ValueTask.CompletedTask; 21 | } 22 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/Where/WhereExpressionBuilderHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace Fireflies.GraphQL.Core.Generators.Where; 4 | 5 | public static class WhereExpressionBuilderHelper { 6 | public static Expression CreateOperationExpression(Expression member, object? value, bool negate, Func factory) { 7 | var resultExpression = factory(member, Expression.Constant(value)); 8 | 9 | if(negate) 10 | resultExpression = Expression.Not(resultExpression); 11 | 12 | return resultExpression; 13 | } 14 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Generators/Where/WhereHelper.cs: -------------------------------------------------------------------------------- 1 | using GraphQLParser.AST; 2 | 3 | namespace Fireflies.GraphQL.Core.Generators.Where; 4 | 5 | public static class WhereHelper { 6 | public static async Task?> WhereEnumerableTaskResult(Task?> resultTask, GraphQLField rootField, IRequestContext graphQLContext, ValueAccessor valueAccessor) { 7 | var taskResult = await resultTask.ConfigureAwait(false); 8 | 9 | if(taskResult == null) 10 | return null; 11 | 12 | var whereNode = rootField.Arguments?.FirstOrDefault(x => x.Name == "where"); 13 | if(whereNode == null) 14 | return taskResult; 15 | 16 | var asQueryable = taskResult.AsQueryable(); 17 | var whereBuilder = new EnumerableWhereBuilder(asQueryable, valueAccessor); 18 | whereBuilder.Build(whereNode, graphQLContext).GetAwaiter().GetResult(); 19 | return whereBuilder.Result; 20 | } 21 | 22 | public static IEnumerable? WhereEnumerableResult(IEnumerable? result, GraphQLField graphQLField, IRequestContext graphQLContext, ValueAccessor valueAccessor) { 23 | return WhereEnumerableTaskResult(Task.FromResult(result), graphQLField, graphQLContext, valueAccessor).Result; 24 | } 25 | 26 | public static async Task?> WhereQueryableTaskResult(Task?> resultTask, GraphQLField rootField, IRequestContext graphQLContext, ValueAccessor valueAccessor) { 27 | var taskResult = await resultTask.ConfigureAwait(false); 28 | if(taskResult == null) 29 | return null; 30 | 31 | var whereNode = rootField.Arguments?.FirstOrDefault(x => x.Name == "where"); 32 | if(whereNode == null) 33 | return taskResult; 34 | 35 | var whereBuilder = new QueryableWhereBuilder(taskResult, valueAccessor); 36 | whereBuilder.Build(whereNode, graphQLContext).GetAwaiter().GetResult(); 37 | return whereBuilder.Result; 38 | } 39 | 40 | public static IQueryable? WhereQueryableResult(IQueryable? result, GraphQLField graphQLField, IRequestContext graphQLContext, ValueAccessor valueAccessor) { 41 | return WhereQueryableTaskResult(Task.FromResult(result), graphQLField, graphQLContext, valueAccessor).Result; 42 | } 43 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/GraphQLOptions.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.IoC.Abstractions; 2 | using Fireflies.Logging.Abstractions; 3 | 4 | namespace Fireflies.GraphQL.Core; 5 | 6 | public class GraphQLOptions { 7 | public string Url { get; internal set; } = "/graphql"; 8 | public IDependencyResolver DependencyResolver { get; internal set; } = null!; 9 | public IFirefliesLoggerFactory LoggerFactory { get; internal set; } = null!; 10 | 11 | internal IEnumerable AllOperations => QueryOperations.Union(MutationsOperations).Union(SubscriptionOperations); 12 | internal List QueryOperations { get; } = new(); 13 | internal List MutationsOperations { get; } = new(); 14 | internal List SubscriptionOperations { get; } = new(); 15 | 16 | internal string? SchemaDescription { get; set; } 17 | public ExtensionRegistry Extensions { get; } = new(); 18 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/GraphQLPath.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core.Extensions; 2 | 3 | namespace Fireflies.GraphQL.Core; 4 | 5 | internal class GraphQLPath : IGraphQLPath { 6 | public IEnumerable Path { get; } 7 | 8 | public IGraphQLPath Add(params object[] subPath) { 9 | return new GraphQLPath(this, subPath); 10 | } 11 | 12 | private GraphQLPath(GraphQLPath parent, object[] subPath) { 13 | for(var i = 0; i < subPath.Length; i++) { 14 | if(subPath[i] is string s) 15 | subPath[i] = s.LowerCaseFirstLetter(); 16 | } 17 | 18 | Path = parent.Path.Union(subPath); 19 | } 20 | 21 | internal GraphQLPath(Stack path) { 22 | Path = path.Select(x => x).Reverse().ToList(); 23 | } 24 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/GraphQLRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace Fireflies.GraphQL.Core; 4 | 5 | public class GraphQLRequest { 6 | public string? Query { get; set; } = null!; 7 | public Dictionary? Variables { get; set; } = new(); 8 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/IConnectionContext.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.IoC.Abstractions; 2 | using GraphQLParser.Visitors; 3 | 4 | namespace Fireflies.GraphQL.Core; 5 | 6 | public interface IConnectionContext : IASTVisitorContext { 7 | bool IsWebSocket { get; } 8 | IWsProtocolHandler? WebSocket { get; } 9 | 10 | Dictionary RequestHeaders { get; } 11 | string QueryString { get; } 12 | 13 | IResultBuilder Results { get; } 14 | 15 | IConnectionContext CreateChildContext(); 16 | IDependencyResolver CreateRequestContainer(); 17 | } 18 | 19 | public interface IConnectionContext : IConnectionContext { 20 | public THttpContext HttpContext { get; } 21 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/IExtensionBuilder.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.IoC.Abstractions; 2 | 3 | namespace Fireflies.GraphQL.Core; 4 | 5 | public interface IExtensionBuilder { 6 | void Build(); 7 | void BuildRequestLifetimeScope(ILifetimeScopeBuilder lifetimeScopeBuilder); 8 | void BuildGraphQLLifetimeScope(ILifetimeScopeBuilder lifetimeScopeBuilder); 9 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/IGraphQLPath.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core; 2 | 3 | public interface IGraphQLPath { 4 | IEnumerable Path { get; } 5 | IGraphQLPath Add(params object[] subPath); 6 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/IRequestContainerBuilder.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.IoC.Abstractions; 2 | 3 | namespace Fireflies.GraphQL.Core; 4 | 5 | public interface IRequestContainerBuilder { 6 | void Build(ILifetimeScopeBuilder builder, THttpContext httpContext); 7 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/IRequestContext.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core.Json; 2 | using Fireflies.IoC.Abstractions; 3 | using GraphQLParser.AST; 4 | using GraphQLParser.Visitors; 5 | 6 | namespace Fireflies.GraphQL.Core; 7 | 8 | public interface IRequestContext : IASTVisitorContext { 9 | IConnectionContext ConnectionContext { get; } 10 | IDependencyResolver DependencyResolver { get; } 11 | 12 | string? Id { get; } 13 | byte[]? RawRequest { get; } 14 | GraphQLDocument? Document { get; } 15 | 16 | FragmentAccessor? FragmentAccessor { get; } 17 | ValueAccessor? ValueAccessor { get; } 18 | 19 | ResultJsonWriter? Writer { get; } 20 | 21 | Task PublishResult(JsonWriter writer); 22 | void IncreaseExpectedOperations(); 23 | void Cancel(); 24 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/IResultBuilder.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core.Json; 2 | 3 | namespace Fireflies.GraphQL.Core; 4 | 5 | public interface IResultBuilder : IAsyncEnumerable<(string? Id, byte[] Result)> { 6 | Task PublishResult(string? id, JsonWriter writer); 7 | void Done(); 8 | void IncreaseExpectedOperations(int i = 1); 9 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/IWsProtocolHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Net.WebSockets; 2 | using System.Text.Json.Nodes; 3 | 4 | namespace Fireflies.GraphQL.Core; 5 | 6 | public interface IWsProtocolHandler { 7 | Task Accept(); 8 | Task CloseAsync(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken cancellationToken); 9 | 10 | void Process(); 11 | Task HandleResult((string? Id, byte[] Result) subResult); 12 | string? SubProtocol { get; } 13 | Task HandleFederatedResponse(byte[] bytes, string operationName, IRequestContext requestContext); 14 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Json/DefaultJsonSerializerSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Fireflies.GraphQL.Core.Json; 5 | 6 | public static class DefaultJsonSerializerSettings { 7 | public static JsonSerializerOptions DefaultSettings { get; } = new() { PropertyNameCaseInsensitive = true, Converters = { new JsonStringEnumConverter() }, DefaultIgnoreCondition = JsonIgnoreCondition.Never }; 8 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Json/GraphQLError.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Json; 2 | 3 | public class GraphQLError : IGraphQLError { 4 | public string Message { get; } 5 | public IGraphQLPath? Path { get; } 6 | public Dictionary Extensions { get; } = new(); 7 | 8 | public GraphQLError(string code, string message) { 9 | Message = message; 10 | AddExtension("code", code); 11 | } 12 | 13 | public GraphQLError(IGraphQLPath path, string code, string message) : this(code, message) { 14 | Path = path; 15 | } 16 | 17 | public void AddExtension(string key, object? value) { 18 | Extensions[key] = value; 19 | } 20 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Json/GraphQLRawError.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Nodes; 2 | 3 | namespace Fireflies.GraphQL.Core.Json; 4 | 5 | public class GraphQLRawError { 6 | public JsonNode Node { get; } 7 | 8 | public GraphQLRawError(JsonNode node) { 9 | Node = node; 10 | } 11 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Json/IErrorCollection.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Json; 2 | 3 | public interface IErrorCollection { 4 | IGraphQLError AddError(IGraphQLPath path, string code, string message); 5 | IGraphQLError AddError(string code, string message); 6 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Json/IGraphQLError.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Json; 2 | 3 | public interface IGraphQLError { 4 | string Message { get; } 5 | IGraphQLPath? Path { get; } 6 | Dictionary Extensions { get; } 7 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Json/JsonWriterFactory.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core.Scalar; 2 | 3 | namespace Fireflies.GraphQL.Core.Json; 4 | 5 | public class JsonWriterFactory { 6 | private readonly ScalarRegistry _scalarRegistry; 7 | 8 | public JsonWriterFactory(ScalarRegistry scalarRegistry) { 9 | _scalarRegistry = scalarRegistry; 10 | } 11 | 12 | public ResultJsonWriter CreateResultWriter() { 13 | return new ResultJsonWriter(_scalarRegistry); 14 | } 15 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Json/JsonWriterMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Json; 2 | 3 | public class JsonWriterMetadata { 4 | public bool Federated { get; set; } 5 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/NullabilityChecker.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Fireflies.GraphQL.Abstractions.Generator; 3 | using Fireflies.Utility.Reflection; 4 | 5 | namespace Fireflies.GraphQL.Core; 6 | 7 | internal static class NullabilityChecker { 8 | private static readonly NullabilityInfoContext NullabilityContext = new(); 9 | private static readonly Dictionary _cache = new(); 10 | 11 | public static bool IsNullable(ParameterInfo parameterInfo) { 12 | if(!_cache.TryGetValue(parameterInfo, out var value)) { 13 | lock(_cache) { 14 | if(parameterInfo.HasCustomAttribute()) { 15 | value = true; 16 | } else { 17 | var nullability = NullabilityContext.Create(parameterInfo); 18 | if(parameterInfo.ParameterType.IsTask()) { 19 | value = nullability.GenericTypeArguments[0].ReadState == NullabilityState.Nullable; 20 | } else { 21 | value = nullability.ReadState == NullabilityState.Nullable; 22 | } 23 | } 24 | _cache[parameterInfo] = value; 25 | } 26 | } 27 | 28 | return value; 29 | } 30 | 31 | public static bool IsNullable(PropertyInfo propertyInfo) { 32 | if(!_cache.TryGetValue(propertyInfo, out var value)) { 33 | lock(_cache) { 34 | value = propertyInfo.HasCustomAttribute() || NullabilityContext.Create(propertyInfo).ReadState == NullabilityState.Nullable; 35 | _cache[propertyInfo] = value; 36 | } 37 | } 38 | 39 | return value; 40 | } 41 | 42 | public static bool IsNullable(MethodInfo methodInfo) { 43 | if(_cache.TryGetValue(methodInfo, out var value)) 44 | return value; 45 | 46 | lock(_cache) { 47 | value = methodInfo.HasCustomAttribute() || IsNullable(methodInfo.ReturnParameter); 48 | _cache[methodInfo] = value; 49 | } 50 | 51 | return value; 52 | } 53 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/OperationContext.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core.Json; 2 | using Fireflies.IoC.Abstractions; 3 | using GraphQLParser.AST; 4 | 5 | namespace Fireflies.GraphQL.Core; 6 | 7 | public class OperationContext : IRequestContext { 8 | public IRequestContext RequestContext { get; } 9 | public OperationType OperationType { get; } 10 | public string? OperationName { get; set; } 11 | 12 | public IConnectionContext ConnectionContext => RequestContext.ConnectionContext; 13 | public IDependencyResolver DependencyResolver => RequestContext.DependencyResolver; 14 | 15 | public string? Id => RequestContext.Id; 16 | public byte[]? RawRequest => RequestContext.RawRequest; 17 | public GraphQLDocument? Document => RequestContext.Document; 18 | 19 | public CancellationToken CancellationToken => RequestContext.CancellationToken; 20 | 21 | public FragmentAccessor? FragmentAccessor => RequestContext.FragmentAccessor; 22 | public ValueAccessor? ValueAccessor => RequestContext.ValueAccessor; 23 | public ResultJsonWriter? Writer => RequestContext.Writer; 24 | 25 | public OperationContext(IRequestContext requestContext, GraphQLOperationDefinition operationDefinition) { 26 | RequestContext = requestContext; 27 | OperationType = operationDefinition.Operation; 28 | OperationName = operationDefinition.Name?.StringValue; 29 | } 30 | 31 | public Task PublishResult(JsonWriter writer) { 32 | return RequestContext.PublishResult(writer); 33 | } 34 | 35 | public void IncreaseExpectedOperations() { 36 | RequestContext.IncreaseExpectedOperations(); 37 | } 38 | 39 | public void Cancel() { 40 | RequestContext.Cancel(); 41 | } 42 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/OperationDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Fireflies.GraphQL.Core; 4 | 5 | internal class OperationDescriptor { 6 | public Type Type { get; } 7 | public string Name { get; } 8 | public MethodInfo Method { get; } 9 | 10 | internal OperationDescriptor(string name, Type type, MethodInfo method) { 11 | Name = name; 12 | Type = type; 13 | Method = method; 14 | } 15 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/README.md: -------------------------------------------------------------------------------- 1 | # Fireflies GraphQL core 2 | 3 | The actual GraphQL execution engine. Most commonly used in conjunction with the Fireflies.GraphQL.AspNet 4 | 5 | _Logo by freepik_ -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/RequestContext.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core.Json; 2 | using Fireflies.IoC.Abstractions; 3 | using GraphQLParser.AST; 4 | 5 | namespace Fireflies.GraphQL.Core; 6 | 7 | public class RequestContext : IRequestContext { 8 | private readonly CancellationTokenSource _cancellationTokenSource; 9 | 10 | public IConnectionContext ConnectionContext { get; internal set; } 11 | public IDependencyResolver DependencyResolver { get; } 12 | public string? Id { get; } 13 | public byte[]? RawRequest { get; } 14 | 15 | public CancellationToken CancellationToken => _cancellationTokenSource.Token; 16 | 17 | public FragmentAccessor? FragmentAccessor { get; set; } = null!; 18 | public ValueAccessor? ValueAccessor { get; set; } = null!; 19 | public ResultJsonWriter? Writer { get; set; } 20 | public GraphQLDocument? Document { get; set; } 21 | 22 | public RequestContext(IConnectionContext connectionContext, IDependencyResolver requestLifetimeScope) { 23 | ConnectionContext = connectionContext; 24 | DependencyResolver = requestLifetimeScope; 25 | _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(connectionContext.CancellationToken); 26 | } 27 | 28 | public RequestContext(IConnectionContext connectionContext, IDependencyResolver requestLifetimeScope, string? id, byte[]? rawRequest) : this(connectionContext, requestLifetimeScope){ 29 | Id = id; 30 | RawRequest = rawRequest; 31 | } 32 | 33 | public async Task PublishResult(JsonWriter writer) { 34 | await ConnectionContext.Results.PublishResult(Id, writer).ConfigureAwait(false); 35 | } 36 | 37 | public void IncreaseExpectedOperations() { 38 | ConnectionContext.Results.IncreaseExpectedOperations(); 39 | } 40 | 41 | public void Cancel() { 42 | _cancellationTokenSource.Cancel(); 43 | } 44 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/ResolvedAttribute.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions; 2 | 3 | namespace Fireflies.GraphQL.Core; 4 | 5 | public class ResolvedAttribute : GraphQLAttribute { 6 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/ResultBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Channels; 2 | using Fireflies.GraphQL.Core.Json; 3 | 4 | namespace Fireflies.GraphQL.Core; 5 | 6 | public class ResultBuilder : IResultBuilder { 7 | private readonly bool _isWebsocket; 8 | private readonly CancellationTokenSource _cancellationToken; 9 | private readonly Channel<(string? Id, JsonWriter Writer)> _channel; 10 | private int _outstandingOperations; 11 | 12 | public ResultBuilder(bool isWebsocket, CancellationTokenSource cancellationToken) { 13 | _isWebsocket = isWebsocket; 14 | _cancellationToken = cancellationToken; 15 | _channel = Channel.CreateUnbounded<(string? Id, JsonWriter Writer)>(new UnboundedChannelOptions { SingleReader = true }); 16 | } 17 | 18 | public async IAsyncEnumerator<(string? Id, byte[] Result)> GetAsyncEnumerator(CancellationToken cancellationToken = new()) { 19 | while(!_cancellationToken.IsCancellationRequested) { 20 | (string? Id, JsonWriter Writer) entry; 21 | try { 22 | await _channel.Reader.WaitToReadAsync(_cancellationToken.Token).ConfigureAwait(false); 23 | entry = await _channel.Reader.ReadAsync(_cancellationToken.Token).ConfigureAwait(false); 24 | } catch(OperationCanceledException) { 25 | break; 26 | } 27 | 28 | if(_isWebsocket) { 29 | yield return (entry.Id, await entry.Writer.GetBuffer().ConfigureAwait(false)); 30 | } else { 31 | var newOutstandingOperations = Interlocked.Decrement(ref _outstandingOperations); 32 | if(newOutstandingOperations != 0) 33 | continue; 34 | 35 | yield return (entry.Id, await entry.Writer.GetBuffer().ConfigureAwait(false)); 36 | 37 | yield break; 38 | } 39 | } 40 | } 41 | 42 | public async Task PublishResult(string? id, JsonWriter writer) { 43 | await _channel.Writer.WriteAsync((id, writer), _cancellationToken.Token).ConfigureAwait(false); 44 | } 45 | 46 | public void Done() { 47 | _cancellationToken.Cancel(); 48 | } 49 | 50 | public void IncreaseExpectedOperations(int i = 1) { 51 | if(!_isWebsocket) 52 | Interlocked.Add(ref _outstandingOperations, i); 53 | } 54 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/ResultContext.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core.Json; 2 | using GraphQLParser.Visitors; 3 | 4 | namespace Fireflies.GraphQL.Core; 5 | 6 | public sealed record ResultContext : IASTVisitorContext { 7 | private readonly HashSet _addedFields = new(); 8 | 9 | public Type Type { get; } 10 | public object? Data { get; private set; } 11 | public ResultContext? ParentContext { get; } 12 | public CancellationToken CancellationToken => RequestContext.CancellationToken; 13 | public IRequestContext RequestContext { get; } 14 | public JsonWriter Writer { get; set; } = null!; 15 | 16 | public FragmentAccessor FragmentAccessor => RequestContext.FragmentAccessor!; 17 | public ValueAccessor ValueAccessor => RequestContext.ValueAccessor!; 18 | 19 | public Stack Path { get; } 20 | 21 | public ResultContext(Type type, IRequestContext requestContext) { 22 | Type = type; 23 | RequestContext = requestContext; 24 | Writer = requestContext.Writer!; 25 | Path = new(); 26 | } 27 | 28 | public ResultContext(object data, IRequestContext requestContext, JsonWriter writer) : this(data.GetType(), requestContext) { 29 | Writer = writer; 30 | Data = data; 31 | } 32 | 33 | private ResultContext(Type type, ResultContext parentContext) { 34 | ParentContext = parentContext; 35 | RequestContext = parentContext.RequestContext; 36 | Writer = parentContext.Writer; 37 | Type = type; 38 | Path = new Stack(parentContext.Path.Reverse()); 39 | } 40 | 41 | public ResultContext CreateChildContext(object data, JsonWriter writer) { 42 | var childContext = new ResultContext(data.GetType(), this) { 43 | Data = data, 44 | Writer = writer 45 | }; 46 | 47 | return childContext; 48 | } 49 | 50 | public ResultContext CreateChildContext(object data) { 51 | var childContext = new ResultContext(data.GetType(), this) { 52 | Data = data, 53 | Writer = Writer 54 | }; 55 | 56 | return childContext; 57 | } 58 | 59 | public bool ShouldAdd(string name) { 60 | return _addedFields.Add(name); 61 | } 62 | 63 | public bool Any(Type lookingFor) { 64 | if(Type.IsAssignableTo(lookingFor)) 65 | return true; 66 | 67 | return ParentContext != null && ParentContext.Any(lookingFor); 68 | } 69 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Scalar/BooleanScalarHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace Fireflies.GraphQL.Core.Scalar; 4 | 5 | public class BooleanScalarHandler : IScalarHandler { 6 | public Type BaseType => typeof(bool); 7 | 8 | public void Serialize(Utf8JsonWriter writer, object value) { 9 | writer.WriteBooleanValue(Convert.ToBoolean(value)); 10 | } 11 | 12 | public void Serialize(Utf8JsonWriter writer, string property, object value) { 13 | writer.WriteBoolean(property, Convert.ToBoolean(value)); 14 | } 15 | 16 | public object? Deserialize(object value, Type type) { 17 | if(value.GetType().IsAssignableTo(type)) 18 | return value; 19 | 20 | return Convert.ChangeType(value, type); 21 | } 22 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Scalar/DateTimeOffsetScalarHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Text.Json; 3 | 4 | namespace Fireflies.GraphQL.Core.Scalar; 5 | 6 | public class DateTimeOffsetScalarHandler : IScalarHandler { 7 | public Type BaseType => typeof(DateTimeOffset); 8 | 9 | public void Serialize(Utf8JsonWriter writer, object value) { 10 | writer.WriteStringValue(((DateTimeOffset)value).ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo)); 11 | } 12 | 13 | public void Serialize(Utf8JsonWriter writer, string property, object value) { 14 | writer.WriteString(property, ((DateTimeOffset)value).ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo)); 15 | } 16 | 17 | public object? Deserialize(object value, Type type) { 18 | return DateTimeOffset.Parse(value.ToString()!); 19 | } 20 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Scalar/DateTimeScalarHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Text.Json; 3 | 4 | namespace Fireflies.GraphQL.Core.Scalar; 5 | 6 | public class DateTimeScalarHandler : IScalarHandler { 7 | public Type BaseType => typeof(DateTime); 8 | 9 | public void Serialize(Utf8JsonWriter writer, object value) { 10 | writer.WriteStringValue(((DateTimeOffset)value).ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo)); 11 | } 12 | 13 | public void Serialize(Utf8JsonWriter writer, string property, object value) { 14 | writer.WriteString(property, ((DateTimeOffset)value).ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz", DateTimeFormatInfo.InvariantInfo)); 15 | } 16 | 17 | public object? Deserialize(object value, Type type) { 18 | return DateTimeOffset.Parse(value.ToString()!); 19 | } 20 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Scalar/FloatScalarHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace Fireflies.GraphQL.Core.Scalar; 4 | 5 | public class FloatScalarHandler : IScalarHandler { 6 | public Type BaseType => typeof(decimal); 7 | 8 | public void Serialize(Utf8JsonWriter writer, object value) { 9 | writer.WriteNumberValue((decimal)Convert.ChangeType(value, TypeCode.Decimal)); 10 | } 11 | 12 | public void Serialize(Utf8JsonWriter writer, string property, object value) { 13 | writer.WriteNumber(property, (decimal)Convert.ChangeType(value, TypeCode.Decimal)); 14 | } 15 | 16 | public object? Deserialize(object value, Type type) { 17 | if(value.GetType().IsAssignableTo(type)) 18 | return value; 19 | 20 | return Convert.ChangeType(value, type); 21 | } 22 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Scalar/IScalarHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace Fireflies.GraphQL.Core.Scalar; 4 | 5 | public interface IScalarHandler { 6 | Type BaseType { get; } 7 | void Serialize(Utf8JsonWriter writer, object value); 8 | void Serialize(Utf8JsonWriter writer, string property, object value); 9 | object? Deserialize(object value, Type type); 10 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Scalar/IntScalarHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace Fireflies.GraphQL.Core.Scalar; 4 | 5 | public class IntScalarHandler : IScalarHandler { 6 | public Type BaseType => typeof(int); 7 | 8 | public void Serialize(Utf8JsonWriter writer, object value) { 9 | writer.WriteNumberValue((long)Convert.ChangeType(value, TypeCode.Int64)); 10 | } 11 | 12 | public void Serialize(Utf8JsonWriter writer, string property, object value) { 13 | writer.WriteNumber(property, (long)Convert.ChangeType(value, TypeCode.Int64)); 14 | } 15 | 16 | public object? Deserialize(object value, Type type) { 17 | if(value.GetType().IsAssignableTo(type)) 18 | return value; 19 | 20 | return Convert.ChangeType(value, type); 21 | } 22 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Scalar/ObjectScalarHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace Fireflies.GraphQL.Core.Scalar; 4 | 5 | public class ObjectScalarHandler : IScalarHandler { 6 | public Type BaseType => typeof(string); 7 | 8 | public void Serialize(Utf8JsonWriter writer, object value) { 9 | writer.WriteStringValue(JsonSerializer.Serialize(value)); 10 | } 11 | 12 | public void Serialize(Utf8JsonWriter writer, string property, object value) { 13 | writer.WriteString(property, JsonSerializer.Serialize(value)); 14 | } 15 | 16 | public object? Deserialize(object value, Type type) { 17 | return null; 18 | } 19 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Scalar/ScalarRegistry.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core.Extensions; 2 | 3 | namespace Fireflies.GraphQL.Core.Scalar; 4 | 5 | public class ScalarRegistry { 6 | private readonly Dictionary _handlers = new(); 7 | 8 | public void AddScalar(IScalarHandler handler) { 9 | _handlers.Add(typeof(T), handler); 10 | } 11 | 12 | public void AddScalar(Type type, IScalarHandler handler) { 13 | _handlers.Add(type, handler); 14 | } 15 | 16 | public bool IsValidGraphQLObjectType(Type type) { 17 | return Type.GetTypeCode(type) == TypeCode.Object && !_handlers.ContainsKey(type); 18 | } 19 | 20 | public bool Contains(Type type) { 21 | return _handlers.ContainsKey(type); 22 | } 23 | 24 | public bool NameToType(string? typeName, out Type? type) { 25 | foreach(var x in _handlers.Where(x => x.Key.Name.Equals(typeName, StringComparison.InvariantCultureIgnoreCase))) { 26 | type = x.Key; 27 | return true; 28 | } 29 | 30 | type = null; 31 | return false; 32 | } 33 | 34 | public bool GetHandler(Type memberInfo, out IScalarHandler? handler) { 35 | if(_handlers.TryGetValue(memberInfo, out var value)) { 36 | handler = value; 37 | return true; 38 | } 39 | 40 | handler = null; 41 | return false; 42 | } 43 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Scalar/StringScalarHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace Fireflies.GraphQL.Core.Scalar; 4 | 5 | public class StringScalarHandler : IScalarHandler { 6 | public Type BaseType => typeof(string); 7 | 8 | public void Serialize(Utf8JsonWriter writer, object value) { 9 | writer.WriteStringValue(value?.ToString() ?? ""); 10 | } 11 | 12 | public void Serialize(Utf8JsonWriter writer, string property, object value) { 13 | writer.WriteString(property, value.ToString()); 14 | } 15 | 16 | public object? Deserialize(object value, Type type) { 17 | if(value.GetType().IsAssignableTo(type)) 18 | return value; 19 | 20 | return value.ToString(); 21 | } 22 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Scalar/TimeSpanScalarHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace Fireflies.GraphQL.Core.Scalar; 4 | 5 | public class TimeSpanScalarHandler : IScalarHandler { 6 | public Type BaseType => typeof(TimeSpan); 7 | 8 | public void Serialize(Utf8JsonWriter writer, object value) { 9 | writer.WriteStringValue(((TimeSpan)value).ToString()); 10 | } 11 | 12 | public void Serialize(Utf8JsonWriter writer, string property, object value) { 13 | writer.WriteString(property, ((TimeSpan)value).ToString()); 14 | } 15 | 16 | public object? Deserialize(object value, Type type) { 17 | return TimeSpan.Parse(value.ToString()!); 18 | } 19 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Scalar/UIntScalarHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace Fireflies.GraphQL.Core.Scalar; 4 | 5 | public class UIntScalarHandler : IScalarHandler { 6 | public Type BaseType => typeof(int); 7 | 8 | public void Serialize(Utf8JsonWriter writer, object value) { 9 | writer.WriteNumberValue((ulong)Convert.ChangeType(value, TypeCode.UInt64)); 10 | } 11 | 12 | public void Serialize(Utf8JsonWriter writer, string property, object value) { 13 | writer.WriteNumber(property, (ulong)Convert.ChangeType(value, TypeCode.UInt64)); 14 | } 15 | 16 | public object? Deserialize(object value, Type type) { 17 | if(value.GetType().IsAssignableTo(type)) 18 | return value; 19 | 20 | return Convert.ChangeType(value, type); 21 | } 22 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Scalar/VersionScalarHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace Fireflies.GraphQL.Core.Scalar; 4 | 5 | public class VersionScalarHandler : IScalarHandler { 6 | public Type BaseType => typeof(Version); 7 | 8 | public void Serialize(Utf8JsonWriter writer, object value) { 9 | writer.WriteStringValue(((Version)value).ToString()); 10 | } 11 | 12 | public void Serialize(Utf8JsonWriter writer, string property, object value) { 13 | writer.WriteString(property, ((Version)value).ToString()); 14 | } 15 | 16 | public object? Deserialize(object value, Type type) { 17 | return Version.Parse(value.ToString()!); 18 | } 19 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Schema/__Directive.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Generator; 2 | 3 | namespace Fireflies.GraphQL.Core.Schema; 4 | 5 | // ReSharper disable once InconsistentNaming 6 | // ReSharper disable UnusedMember.Global 7 | [GraphQLNoWrapper] 8 | public class __Directive { 9 | public string? Name { get; set; } 10 | public string? Description { get; set; } 11 | public __DirectiveLocation[] Locations { get; set; } = Array.Empty<__DirectiveLocation>(); 12 | public __InputValue[] Args { get; set; } = Array.Empty<__InputValue>(); 13 | public bool IsRepeatable { get; set; } 14 | } 15 | // ReSharper restore UnusedMember.Global -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Schema/__DirectiveLocation.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Schema; 2 | 3 | // ReSharper disable InconsistentNaming 4 | // ReSharper disable UnusedMember.Global 5 | public enum __DirectiveLocation { 6 | QUERY, 7 | MUTATION, 8 | SUBSCRIPTION, 9 | FIELD, 10 | FRAGMENT_DEFINITION, 11 | FRAGMENT_SPREAD, 12 | INLINE_FRAGMENT, 13 | VARIABLE_DEFINITION, 14 | SCHEMA, 15 | SCALAR, 16 | OBJECT, 17 | FIELD_DEFINITION, 18 | ARGUMENT_DEFINITION, 19 | INTERFACE, 20 | UNION, 21 | ENUM, 22 | ENUM_VALUE, 23 | INPUT_OBJECT, 24 | INPUT_FIELD_DEFINITION, 25 | } 26 | // ReSharper restore InconsistentNaming 27 | // ReSharper restore UnusedMember.Global -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Schema/__EnumValue.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Generator; 2 | 3 | namespace Fireflies.GraphQL.Core.Schema; 4 | 5 | // ReSharper disable once InconsistentNaming 6 | [GraphQLNoWrapper] 7 | public class __EnumValue { 8 | public string Name { get; set; } 9 | public string? Description { get; set; } 10 | public bool IsDeprecated { get; set; } 11 | public string? DeprecationReason { get; set; } 12 | 13 | public __EnumValue(string name, string? description, string? deprecationReason) { 14 | Name = name; 15 | Description = description; 16 | DeprecationReason = deprecationReason; 17 | IsDeprecated = DeprecationReason != null; 18 | } 19 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Schema/__Field.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Core.Extensions; 2 | using System.Reflection; 3 | using Fireflies.GraphQL.Abstractions.Authorization; 4 | using Fireflies.GraphQL.Abstractions.Generator; 5 | using Fireflies.GraphQL.Core.Federation.Schema; 6 | 7 | namespace Fireflies.GraphQL.Core.Schema; 8 | 9 | // ReSharper disable once InconsistentNaming 10 | [GraphQLNoWrapper] 11 | public class __Field { 12 | public string Name { get; set; } = null!; 13 | public string? Description { get; set; } 14 | public __InputValue[] Args { get; set; } = Array.Empty<__InputValue>(); 15 | public __Type Type { get; set; } = null!; 16 | public bool IsDeprecated { get; set; } 17 | public string? DeprecationReason { get; set; } 18 | 19 | // ReSharper disable once UnusedMember.Global 20 | // Used when json deserializes schema for federated queries 21 | public __Field() { 22 | } 23 | 24 | public __Field(MemberInfo memberInfo) { 25 | Name = memberInfo.GraphQLName(); 26 | Description = GetFieldDescription(memberInfo); 27 | var deprecatedReason = memberInfo.GetDeprecatedReason(); 28 | IsDeprecated = deprecatedReason != null; 29 | DeprecationReason = deprecatedReason; 30 | } 31 | 32 | public static __Field FromFederation(FederationField field) { 33 | return new __Field() { 34 | Args = field.Args.Select(__InputValue.FromFederation).ToArray(), 35 | DeprecationReason = field.DeprecationReason, 36 | Description = field.Description, 37 | IsDeprecated = field.IsDeprecated, 38 | Name = field.Name, 39 | Type = __Type.FromFederation(field.Type) 40 | }; 41 | } 42 | 43 | private string? GetFieldDescription(MemberInfo memberInfo) { 44 | string? description = null; 45 | 46 | var descriptionAttribute = memberInfo.GetDescription(); 47 | if(descriptionAttribute != null) 48 | description += descriptionAttribute; 49 | 50 | var authorizationAttributes = memberInfo.DeclaringType!.GetCustomAttributes().Union(memberInfo.GetCustomAttributes()); 51 | if(authorizationAttributes.Any()) { 52 | if(description != null) { 53 | description += "\n\n"; 54 | } 55 | 56 | description += "Authorization required:\n" + string.Join("\nOR\n", authorizationAttributes.Select(x => x.Help)); 57 | } 58 | 59 | return description; 60 | } 61 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Schema/__InputValue.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Generator; 2 | using Fireflies.GraphQL.Core.Federation.Schema; 3 | 4 | namespace Fireflies.GraphQL.Core.Schema; 5 | 6 | // ReSharper disable once InconsistentNaming 7 | [GraphQLNoWrapper] 8 | public class __InputValue { 9 | public string Name { get; set; } 10 | public string? Description { get; set; } 11 | public __Type Type { get; set; } 12 | public string? DefaultValue { get; set; } 13 | 14 | public __InputValue(string name, string? description, __Type type, string? defaultValue) { 15 | Name = name; 16 | Description = description; 17 | Type = type; 18 | DefaultValue = defaultValue; 19 | } 20 | 21 | public static __InputValue FromFederation(FederationInputValue field) { 22 | return new(field.Name, field.Description, __Type.FromFederation(field.Type), field.DefaultValue); 23 | } 24 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Schema/__Schema.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Generator; 2 | 3 | namespace Fireflies.GraphQL.Core.Schema; 4 | 5 | // ReSharper disable once InconsistentNaming 6 | [GraphQLNoWrapper] 7 | public class __Schema { 8 | public string? Description { get; set; } 9 | public __Type[] Types { get; set; } = Array.Empty<__Type>(); 10 | public __Type? QueryType { get; set; } 11 | public __Type? MutationType { get; set; } 12 | public __Type? SubscriptionType { get; set; } 13 | public __Directive[] Directives { get; set; } = Array.Empty<__Directive>(); 14 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Schema/__SchemaQuery.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions; 2 | using Fireflies.GraphQL.Abstractions.Generator; 3 | 4 | namespace Fireflies.GraphQL.Core.Schema; 5 | 6 | // ReSharper disable once InconsistentNaming 7 | [GraphQLNoWrapper] 8 | public class __SchemaQuery { 9 | private readonly __Schema _schema; 10 | 11 | public __SchemaQuery(__Schema schema) { 12 | _schema = schema; 13 | } 14 | 15 | [GraphQLQuery] 16 | public __Schema __Schema() { 17 | return _schema; 18 | } 19 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Schema/__Type.cs: -------------------------------------------------------------------------------- 1 | using Fireflies.GraphQL.Abstractions.Generator; 2 | using Fireflies.GraphQL.Core.Extensions; 3 | using Fireflies.GraphQL.Core.Federation; 4 | using Fireflies.GraphQL.Core.Federation.Schema; 5 | 6 | namespace Fireflies.GraphQL.Core.Schema; 7 | 8 | // ReSharper disable InconsistentNaming 9 | [GraphQLNoWrapper] 10 | public class __Type { 11 | private readonly IEnumerable<__Field> _fields; 12 | private readonly IEnumerable<__EnumValue> _enumValues; 13 | 14 | public __Type(Type? baseType, IEnumerable<__Field>? fields = null, IEnumerable<__EnumValue>? enumValues = null, IEnumerable<__InputValue>? inputValues = null) { 15 | _fields = fields ?? Enumerable.Empty<__Field>(); 16 | _enumValues = enumValues ?? Enumerable.Empty<__EnumValue>(); 17 | InputFields = inputValues?.ToArray() ?? Array.Empty<__InputValue>(); 18 | Description = baseType?.GetDescription(); 19 | Federated = baseType?.IsAssignableTo(typeof(FederatedQuery)) ?? false; 20 | } 21 | 22 | private __Type(FederationType field) { 23 | Kind = field.Kind; 24 | Name = field.Name; 25 | Description = field.Description; 26 | SpecifiedByURL = field.SpecifiedByURL; 27 | 28 | InputFields = field.InputFields.Select(__InputValue.FromFederation).ToArray(); 29 | Interfaces = field.Interfaces.Select(x => new __Type(x)).ToArray(); 30 | PossibleTypes = field.PossibleTypes.Select(x => new __Type(x)).ToArray(); 31 | 32 | if(field.OfType != null) 33 | OfType = new __Type(field.OfType); 34 | 35 | _fields = field.Fields.Select(__Field.FromFederation).ToArray(); 36 | _enumValues = field.EnumValues; 37 | } 38 | 39 | public static __Type FromFederation(FederationType field) { 40 | return new __Type(field); 41 | } 42 | 43 | public __TypeKind Kind { get; set; } 44 | public string? Name { get; set; } 45 | public string? Description { get; set; } 46 | public bool Federated { get; set; } 47 | 48 | public __Field[] Fields(bool includeDeprecated = false) { 49 | return includeDeprecated ? _fields.ToArray() : _fields.Where(f => !f.IsDeprecated).ToArray(); 50 | } 51 | 52 | public __Type[] Interfaces { get; set; } = Array.Empty<__Type>(); 53 | public __Type[] PossibleTypes { get; set; } = Array.Empty<__Type>(); 54 | 55 | // ReSharper disable once UnusedMember.Global 56 | public __EnumValue[] EnumValues(bool includeDeprecated = false) { 57 | return includeDeprecated ? _enumValues.ToArray() : _enumValues.Where(f => !f.IsDeprecated).ToArray(); 58 | } 59 | 60 | public __InputValue[] InputFields { get; set; } 61 | public __Type? OfType { get; set; } 62 | 63 | // ReSharper disable once UnusedMember.Global 64 | public string? SpecifiedByURL { get; set; } = null; 65 | 66 | public override string ToString() { 67 | return Name + (OfType != null ? $" ({OfType.Name})" : null); 68 | } 69 | } 70 | // ReSharper enable InconsistentNaming -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/Schema/__TypeKind.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core.Schema; 2 | 3 | // ReSharper disable InconsistentNaming 4 | // ReSharper disable UnusedMember.Global 5 | public enum __TypeKind { 6 | SCALAR, 7 | OBJECT, 8 | INTERFACE, 9 | UNION, 10 | ENUM, 11 | INPUT_OBJECT, 12 | LIST, 13 | NON_NULL 14 | } 15 | // ReSharper enable InconsistentNaming 16 | // ReSharper restore UnusedMember.Global -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/SchemaValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Fireflies.GraphQL.Abstractions; 3 | using Fireflies.GraphQL.Core.Exceptions; 4 | using Fireflies.GraphQL.Core.Extensions; 5 | using Fireflies.GraphQL.Core.Scalar; 6 | using Fireflies.Utility.Reflection; 7 | 8 | namespace Fireflies.GraphQL.Core; 9 | 10 | internal class SchemaValidator { 11 | private readonly IEnumerable _operations; 12 | private readonly ScalarRegistry _scalarRegistry; 13 | private readonly Dictionary _verifiedTyped = new(); 14 | 15 | public SchemaValidator(IEnumerable operations, ScalarRegistry scalarRegistry) { 16 | _operations = operations; 17 | _scalarRegistry = scalarRegistry; 18 | } 19 | 20 | public void Validate() { 21 | foreach(var operation in _operations) { 22 | var returnType = operation.Method.ReturnType.DiscardTask(); 23 | InspectType(returnType); 24 | } 25 | } 26 | 27 | private void InspectType(Type type) { 28 | if(type.IsCollection(out var elementType)) { 29 | type = elementType; 30 | } 31 | 32 | if(!_scalarRegistry.IsValidGraphQLObjectType(type) || type.IsSubclassOf(typeof(GraphQLId))) 33 | return; 34 | 35 | if(type == typeof(void)) 36 | return; 37 | 38 | var graphQLName = type.GraphQLName(); 39 | if(_verifiedTyped.TryGetValue(graphQLName, out var existingType)) { 40 | if(existingType != type) { 41 | throw new DuplicateNameException($"{type.Name} is used for more than one type"); 42 | } 43 | 44 | return; 45 | } 46 | 47 | _verifiedTyped.Add(graphQLName, type); 48 | 49 | foreach(var subType in type.GetAllGraphQLMemberInfo()) { 50 | Type typeToInspect; 51 | if(subType is PropertyInfo propertyInfo) 52 | typeToInspect = propertyInfo.PropertyType; 53 | else if(subType is MethodInfo methodInfo) 54 | typeToInspect = methodInfo.ReturnType; 55 | else 56 | throw new ArgumentOutOfRangeException(nameof(subType)); 57 | 58 | typeToInspect = typeToInspect.DiscardTask(); 59 | typeToInspect = Nullable.GetUnderlyingType(typeToInspect) ?? typeToInspect; 60 | 61 | InspectType(typeToInspect); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Fireflies.GraphQL.Core/WrapperRegistry.cs: -------------------------------------------------------------------------------- 1 | namespace Fireflies.GraphQL.Core; 2 | 3 | public class WrapperRegistry { 4 | private readonly Dictionary _wrappedTypes = new(); 5 | 6 | public bool TryGetValue(Type type, out Type existingWrapper) { 7 | if(_wrappedTypes.TryGetValue(type, out var existingType)) { 8 | existingWrapper = existingType; 9 | return true; 10 | } 11 | 12 | existingWrapper = null!; 13 | return false; 14 | } 15 | 16 | public void Add(Type type, Type wrapper) { 17 | _wrappedTypes[type] = wrapper; 18 | } 19 | 20 | public Type GetWrapperOfSelf(Type impl) { 21 | return TryGetValue(impl, out var existingWrapper) ? existingWrapper : impl; 22 | } 23 | } -------------------------------------------------------------------------------- /Images/GraphQL.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firefliestech/Fireflies.GraphQL/16a4298972e1d0e3508d498922db3dc060754e6f/Images/GraphQL.ai -------------------------------------------------------------------------------- /Images/Icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firefliestech/Fireflies.GraphQL/16a4298972e1d0e3508d498922db3dc060754e6f/Images/Icon-128x128.png -------------------------------------------------------------------------------- /Images/Icon.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firefliestech/Fireflies.GraphQL/16a4298972e1d0e3508d498922db3dc060754e6f/Images/Icon.ai -------------------------------------------------------------------------------- /Images/Organization.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firefliestech/Fireflies.GraphQL/16a4298972e1d0e3508d498922db3dc060754e6f/Images/Organization.ai -------------------------------------------------------------------------------- /LoadTest/LoadTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /LoadTest/Program.cs: -------------------------------------------------------------------------------- 1 | using NBomber.CSharp; 2 | using NBomber.Http.CSharp; 3 | 4 | using var httpClient = new HttpClient(); 5 | 6 | var scenario = Scenario.Create("http_scenario", async context => { 7 | var request = 8 | Http.CreateRequest("POST", "https://localhost:7273/graphql") 9 | .WithHeader("Accept", "text/html") 10 | .WithBody(new StringContent(@"{""query"":""{ books { bookId numbers title iSBN } }""}")); 11 | 12 | var response = await Http.Send(httpClient, request); 13 | 14 | return response; 15 | }) 16 | .WithWarmUpDuration(TimeSpan.FromSeconds(2)) 17 | .WithLoadSimulations(Simulation.Inject(rate: 20, 18 | interval: TimeSpan.FromSeconds(1), 19 | during: TimeSpan.FromSeconds(5))); 20 | 21 | NBomberRunner 22 | .RegisterScenarios(scenario) 23 | .Run(); 24 | 25 | Console.ReadLine(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fireflies GraphQL plattform 2 | 3 | The plattform consists of two parts which may be used independently. 4 | 5 | ## The server part 6 | Head over to the [documentation](Fireflies.GraphQL.AspNet/README.md) for the ASP.NET server side part. 7 | 8 | ## The client part 9 | Head over to the [documentation](Client/Fireflies.GraphQL.Client.Console/README.md) for the client generator. 10 | 11 | _Logo by freepik_ -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | VERSION=$1 2 | ROOT_PATH=$(pwd) 3 | 4 | find -name *.nuspec | grep -v "/obj/" | grep -v "Fireflies.Shared" | while read -r i 5 | do 6 | DIR_NAME=$(dirname $i) 7 | echo "Entering $DIR_NAME" 8 | cd $DIR_NAME 9 | 10 | dotnet pack -p:PackageVersion=$VERSION --output $ROOT_PATH/nupkg 11 | 12 | cd $ROOT_PATH 13 | done 14 | --------------------------------------------------------------------------------