├── .editorconfig ├── .gitattributes ├── .gitignore ├── GraphQL.nuspec ├── GraphQL.sln ├── LICENSE.md ├── README.md ├── appveyor.yml ├── lib └── netstandard2.0 │ ├── GraphQL-Parser.dll │ ├── GraphQL.dll │ └── GraphQL.pdb ├── nuget.exe ├── package.json ├── src ├── CommonAssemblyInfo.cs ├── GraphQl.SchemaGenerator.Tests │ ├── GraphQl.SchemaGenerator.Tests.csproj │ ├── Helpers │ │ └── AssertExtensions.cs │ ├── Mocks │ │ └── MockServiceProvider.cs │ ├── Schemas │ │ ├── AsyncSchema.cs │ │ ├── DateSchema.cs │ │ ├── DictionarySchema.cs │ │ ├── DuplicateSchema.cs │ │ ├── EchoSchema.cs │ │ ├── EchoStateSchema.cs │ │ ├── GenericsSchema.cs │ │ ├── PerformanceSchema.cs │ │ └── PrimitiveSchema.cs │ ├── Tests │ │ ├── AsyncSchemaTests.cs │ │ ├── DateTests.cs │ │ ├── ErrorTests.cs │ │ ├── GenericQueryTests.cs │ │ ├── MutationTests.cs │ │ ├── PerformanceTests.cs │ │ ├── QueryTests.cs │ │ └── TypeHelperTests.cs │ ├── app.config │ └── packages.config └── GraphQl.SchemaGenerator │ ├── Attributes │ ├── GraphKnownTypeAttribute.cs │ ├── GraphNotRequiredAttribute.cs │ ├── GraphRouteAttribute.cs │ ├── GraphTypeAttribute.cs │ └── NotNullAttribute.cs │ ├── CHANGELOG.md │ ├── DeepDictionaryRequest.cs │ ├── DocumentOperations.cs │ ├── Extensions │ ├── AssignableExtensions.cs │ ├── ResolveFieldContextExtensions.cs │ └── TypeExtensions.cs │ ├── GraphQl.SchemaGenerator.csproj │ ├── GraphTypeConverter.cs │ ├── GraphTypeResolver.cs │ ├── Helpers │ ├── StringHelper.cs │ └── TypeHelper.cs │ ├── IGraphTypeResolver.cs │ ├── Models │ ├── FieldDefinition.cs │ ├── FieldInformation.cs │ ├── GraphControllerDefinition.cs │ ├── GraphRouteDefinition.cs │ └── RequiredType.cs │ ├── ObjectGraphTypeBuilder.cs │ ├── Properties │ └── launchSettings.json │ ├── README.md │ ├── Schema │ ├── GraphTypesLookup.cs │ ├── IDomainSchemaTypeMapping.cs │ ├── ISchemaFactory.cs │ └── SchemaBuilder.cs │ ├── SchemaGenerator.cs │ ├── Types │ ├── OriginalDateGraphType.cs │ └── TimeSpanGraphType.cs │ ├── Wrappers │ ├── ByteArrayGraphType.cs │ ├── EnumerationGraphTypeWrapper.cs │ ├── IIGnore.cs │ ├── InputObjectGraphTypeWrapper.cs │ ├── InterfaceGraphTypeWrapper.cs │ ├── KeyValuePairGraphType.cs │ ├── KeyValuePairInputGraphType.cs │ └── ObjectGraphTypeWrapper.cs │ ├── app.config │ └── packages.config ├── test-local.ps1 └── test.ps1 /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | [*.{cs}] 12 | indent_size = 4 13 | 14 | [*.js] 15 | charset = utf-8 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [{package.json}] 20 | indent_style = space 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | 3 | .vs/ 4 | *.user 5 | *.suo 6 | *.nupkg 7 | npm-debug.log 8 | bundle.js 9 | fixie-results.xml 10 | 11 | TestResults/ 12 | [Oo]bj/ 13 | [Bb]in/ 14 | packages/ 15 | node_modules/ 16 | nuget/lib 17 | /src/GraphQL/project.lock.json 18 | /src/GraphQL.Tests/project.lock.json 19 | -------------------------------------------------------------------------------- /GraphQL.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Navitaire.DotRez.Core.GraphQL 5 | 0.2.0.2 6 | Derek Holmes 7 | https://github.com/holm0563 8 | https://raw.githubusercontent.com/holm0563/graphql-schemaGenerator/AsReference/LICENSE.md 9 | https://github.com/holm0563/graphql-schemaGenerator 10 | false 11 | Generate graph ql queries and schemas easily from dot net models. 12 | 13 | GraphQL json api 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /GraphQL.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32112.339 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.SchemaGenerator", "src\GraphQl.SchemaGenerator\GraphQL.SchemaGenerator.csproj", "{0FF5301B-4CD7-409A-9D02-9390DE7BEBA3}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.SchemaGenerator.Tests", "src\GraphQl.SchemaGenerator.Tests\GraphQL.SchemaGenerator.Tests.csproj", "{4052E386-847B-4F3C-957C-0B957E319127}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {0FF5301B-4CD7-409A-9D02-9390DE7BEBA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {0FF5301B-4CD7-409A-9D02-9390DE7BEBA3}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {0FF5301B-4CD7-409A-9D02-9390DE7BEBA3}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {0FF5301B-4CD7-409A-9D02-9390DE7BEBA3}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {4052E386-847B-4F3C-957C-0B957E319127}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {4052E386-847B-4F3C-957C-0B957E319127}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {4052E386-847B-4F3C-957C-0B957E319127}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {4052E386-847B-4F3C-957C-0B957E319127}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {CB2FBE11-C733-4101-81BB-A7C99B986004} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Navitaire, an Amadeus Company 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Schema Generator - GraphQL for .NET 2 | 3 | This uses [GraphQL for .NET](https://github.com/graphql-dotnet/graphql-dotnet) and wraps it to easily generate a GraphQL schema based on C# models. The schema generator will automatically create a schema from existing C# models. This includes every response model, request model, and composed class in these models. This can save a lot of time with an existing SDK or API project that is adding GraphQL support. 4 | 5 | ## Installation 6 | 7 | *Todo 8 | 9 | ## Configuration 10 | 11 | Define your routes with the GraphRoute attribute: 12 | 13 | ```csharp 14 | /// 15 | /// An example of the sdk that could be exposed. This is decorated with 16 | /// attributes to self generate a graph schema. 17 | /// 18 | public class StarWarsAttributeSchema 19 | { 20 | private readonly StarWarsData _data = new StarWarsData(); 21 | 22 | /// 23 | /// Get the current hero. 24 | /// 25 | /// 26 | /// Example of graph ql attribute using the defaults. 27 | /// 28 | /// Droid. 29 | [GraphRoute] 30 | public Droid Hero() 31 | { 32 | var item = _data.GetDroidByIdAsync("3").Result; 33 | 34 | return item; 35 | } 36 | } 37 | ``` 38 | 39 | ## Example Usage 40 | 41 | ```csharp 42 | IServiceProvider provider = new MockServiceProvider(); //Resolves your classes 43 | var schemaGenerator = new SchemaGenerator(provider); 44 | //See the readme.md in the schema generator project for more details on what this is doing. 45 | var schema = schemaGenerator.CreateSchema(typeof(StarWarsAttributeSchema)); 46 | 47 | //Standard GraphQL execution 48 | var query = @" 49 | query HeroNameQuery { 50 | hero { 51 | name 52 | } 53 | } 54 | "; 55 | var exec = new DocumentExecuter(new AntlrDocumentBuilder(), new DocumentValidator()); 56 | var result = exec.ExecuteAsync(schema, null, query, null).Result; 57 | ``` 58 | 59 | ## Roadmap 60 | 61 | ### Supported Data Types 62 | - [x] Enums 63 | - [x] Dictionaries 64 | - [x] IEnumerable 65 | - [x] DateTime, DateTimeOffset 66 | - [x] Timespan 67 | - [x] Byte Array 68 | - [x] Key value pair 69 | 70 | ### Supported Conversions 71 | - [x] Mutations 72 | - [x] Queries 73 | - [ ] Interfaces 74 | - [x] Descriptions (via description attribute) 75 | - [ ] Descriptions (via summary text) 76 | - [x] Enumerations 77 | - [x] Input Objects 78 | - [x] Mutations 79 | - [ ] Unions 80 | - [ ] Async execution 81 | - [ ] Methods converting to inner queries 82 | - Void return types are not supported, doesn't make sense per the graph spec. 83 | 84 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.1.0.{build} 2 | environment: 3 | CI: true 4 | nuget: 5 | account_feed: true 6 | project_feed: true 7 | disable_publish_on_pr: true 8 | build_script: 9 | - cmd: >- 10 | npm install -g npm 11 | 12 | npm --version 13 | 14 | node --version 15 | 16 | npm run build-ci 17 | -------------------------------------------------------------------------------- /lib/netstandard2.0/GraphQL-Parser.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holm0563/graphql-schemaGenerator/a04230e4dfd54deced4054041cb0ab29f526f48a/lib/netstandard2.0/GraphQL-Parser.dll -------------------------------------------------------------------------------- /lib/netstandard2.0/GraphQL.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holm0563/graphql-schemaGenerator/a04230e4dfd54deced4054041cb0ab29f526f48a/lib/netstandard2.0/GraphQL.dll -------------------------------------------------------------------------------- /lib/netstandard2.0/GraphQL.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holm0563/graphql-schemaGenerator/a04230e4dfd54deced4054041cb0ab29f526f48a/lib/netstandard2.0/GraphQL.pdb -------------------------------------------------------------------------------- /nuget.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holm0563/graphql-schemaGenerator/a04230e4dfd54deced4054041cb0ab29f526f48a/nuget.exe -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-dotnet", 3 | "version": "0.10.1", 4 | "description": "GraphQL for .NET", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack --progress", 8 | "test": "babel-node tools/build.js test", 9 | "build": "babel-node tools/build.js", 10 | "build-release": "set CONFIGURATION=Release&& babel-node tools/build.js", 11 | "build-ci": "set CONFIGURATION=Release&& npm install && babel-node tools/build.js ci", 12 | "build-nuget": "babel-node tools/build.js nuget", 13 | "build-artifacts": "babel-node tools/build.js artifacts", 14 | "release": "set CONFIGURATION=Release&& babel-node tools/build.js ci", 15 | "restore": "babel-node tools/build.js restore", 16 | "setVersion": "babel-node tools/build.js setVersion" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/holm0563/graphql-schemaGenerator.git" 21 | }, 22 | "author": "Joe McBride", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/holm0563/graphql-schemaGenerator/issues" 26 | }, 27 | "homepage": "https://github.com/holm0563/graphql-schemaGenerator", 28 | "dependencies": { 29 | "graphql": "^0.12.x" 30 | }, 31 | "devDependencies": { 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/CommonAssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | [assembly: AssemblyDescription("GraphQL for .NET")] 4 | [assembly: AssemblyTitle("GraphQL")] 5 | [assembly: AssemblyProduct("GraphQL")] 6 | [assembly: AssemblyCopyright("Copyright 2015-2016 Joseph T. McBride et al. All rights reserved.")] 7 | [assembly: AssemblyTrademark("")] 8 | [assembly: AssemblyVersion("0.9.3.0")] 9 | [assembly: AssemblyFileVersion("0.9.3.0")] 10 | [assembly: AssemblyInformationalVersion("0.9.3.0")] 11 | [assembly: CLSCompliant(false)] 12 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/GraphQl.SchemaGenerator.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | GraphQl.SchemaGenerator.Tests 6 | GraphQl.SchemaGenerator.Tests 7 | Copyright © 2016 8 | true 9 | 10 | 11 | 12 | full 13 | CS0618 14 | 15 | 16 | 17 | pdbonly 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ..\..\lib\netstandard2.0\GraphQL.dll 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Helpers/AssertExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using GraphQL.Execution; 4 | using GraphQL.Http; 5 | using GraphQL.Validation; 6 | using GraphQL.Validation.Complexity; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Linq; 9 | using Xunit; 10 | 11 | namespace GraphQL.SchemaGenerator.Tests.Helpers 12 | { 13 | public static class GraphAssert 14 | { 15 | public static void QuerySuccess(GraphQL.Types.Schema schema, string query, string expected, string variables = null, bool compareBoth = true) 16 | { 17 | var exec = new DocumentExecuter(new GraphQLDocumentBuilder(), new DocumentValidator(), new ComplexityAnalyzer()); 18 | var result = exec.ExecuteAsync(schema, null, query, null, variables?.ToInputs()).Result; 19 | var result2 = DocumentOperations.ExecuteOperationsAsync(schema, null, query, variables?.ToInputs()).Result; 20 | 21 | var writtenResult = JsonConvert.SerializeObject(result.Data); 22 | var writtenResult2 = JsonConvert.SerializeObject(result2.Data); 23 | var queryResult = CreateQueryResult(expected); 24 | var expectedResult = JsonConvert.SerializeObject(queryResult.Data); 25 | 26 | var errors = result.Errors?.FirstOrDefault(); 27 | var errors2 = result2.Errors?.FirstOrDefault(); 28 | //for easy debugging 29 | var allTypes = schema.AllTypes; 30 | 31 | Assert.Null(errors?.Message); 32 | Assert.Null(errors2?.Message); 33 | Assert.Equal(expectedResult, writtenResult); 34 | Assert.Equal(expectedResult, writtenResult2); 35 | } 36 | 37 | public static void QueryOperationsSuccess(GraphQL.Types.Schema schema, string query, string expected, string variables = null, bool compareBoth = true, IList blackListedOperations = null) 38 | { 39 | var result2 = DocumentOperations.ExecuteOperationsAsync(schema, null, query, variables?.ToInputs(), blackListedOperations: blackListedOperations).Result; 40 | 41 | var writtenResult2 = JsonConvert.SerializeObject(result2.Data); 42 | var queryResult = CreateQueryResult(expected); 43 | var expectedResult = JsonConvert.SerializeObject(queryResult.Data); 44 | 45 | var errors = result2.Errors?.FirstOrDefault(); 46 | //for easy debugging 47 | var allTypes = schema.AllTypes; 48 | 49 | Assert.Null(errors?.Message); 50 | Assert.Equal(expectedResult, writtenResult2); 51 | } 52 | 53 | private static ExecutionResult CreateQueryResult(string result) 54 | { 55 | object expected = null; 56 | if (!string.IsNullOrWhiteSpace(result)) 57 | { 58 | expected = JObject.Parse(result); 59 | } 60 | 61 | var eResult = new ExecutionResult { Data = expected }; 62 | 63 | return eResult; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Mocks/MockServiceProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GraphQL.SchemaGenerator.Tests.Mocks 4 | { 5 | class MockServiceProvider : IServiceProvider 6 | { 7 | public object Data { get; set; } 8 | 9 | public MockServiceProvider() 10 | { 11 | 12 | } 13 | 14 | public MockServiceProvider(object data) 15 | { 16 | Data = data; 17 | } 18 | 19 | public object GetService(Type serviceType) 20 | { 21 | if (Data == null) 22 | { 23 | return Activator.CreateInstance(serviceType); 24 | } 25 | 26 | return Data; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Schemas/AsyncSchema.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using GraphQL.SchemaGenerator.Attributes; 7 | 8 | namespace GraphQL.SchemaGenerator.Tests.Schemas 9 | { 10 | [GraphType] 11 | public class AsyncSchema 12 | { 13 | [Description(@"Tests a variety or request and response types.{VerifyComment}")] 14 | [GraphRoute] 15 | public Task TestRequest(Schema1Request request) 16 | { 17 | var schema = new EchoSchema(); 18 | return Task.FromResult(schema.TestRequest(request)); 19 | } 20 | 21 | [GraphRoute] 22 | public async Task TestEnumerableRequest(IEnumerable request) 23 | { 24 | var schema = new EchoSchema(); 25 | return await Task.FromResult(schema.TestEnumerableRequest(request)); 26 | } 27 | 28 | 29 | [GraphRoute] 30 | public async Task> TestEnumerable(Schema1Request request) 31 | { 32 | var schema = new EchoSchema(); 33 | return await Task.FromResult(schema.TestEnumerable(request)); 34 | } 35 | 36 | [GraphRoute] 37 | public async Task NotRecommendedToReturnATask() 38 | { 39 | await Task.Delay(1); 40 | } 41 | 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Schemas/DateSchema.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Threading; 4 | using GraphQL.SchemaGenerator.Attributes; 5 | 6 | namespace GraphQL.SchemaGenerator.Tests.Schemas 7 | { 8 | [GraphType] 9 | public class DateSchema 10 | { 11 | [GraphRoute] 12 | public Dates Dates(Dates dates) 13 | { 14 | Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-GB"); 15 | return dates; 16 | } 17 | 18 | } 19 | 20 | public class Dates 21 | { 22 | public DateTimeOffset? Offset { get; set; } 23 | public DateTime? DateTime { get; set; } 24 | 25 | public DateTime? DateUTC => DateTime.Value.ToUniversalTime(); 26 | 27 | public DateTime? Y2k { get; } = new DateTime(2000, 1, 1); 28 | 29 | public DateTime? Y2kUtc { get; } = new DateTime(2000,1,1,0,0,0, DateTimeKind.Utc); 30 | 31 | public TimeSpan? TimeSpan { get; set; } = new TimeSpan(0,1,1,1); 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Schemas/DictionarySchema.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using GraphQL.SchemaGenerator.Attributes; 6 | 7 | namespace GraphQL.SchemaGenerator.Tests.Schemas 8 | { 9 | [GraphType] 10 | public class DictionarySchema 11 | { 12 | [GraphRoute] 13 | public IDictionary DictionaryRequest(DictionaryRequest request) 14 | { 15 | return request.Dictionary; 16 | } 17 | } 18 | 19 | public class DictionaryRequest 20 | { 21 | public IDictionary Dictionary { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Schemas/DuplicateSchema.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using GraphQL.SchemaGenerator.Attributes; 5 | 6 | namespace GraphQL.SchemaGenerator.Tests.Schemas 7 | { 8 | [GraphType] 9 | public class DuplicateSchema 10 | { 11 | [Description(@"Tests error handling.")] 12 | [GraphRoute(name:"SameRoute")] 13 | public DuplicateResponse TestRequest() 14 | { 15 | return new DuplicateResponse 16 | { 17 | Value = 9 18 | }; 19 | } 20 | 21 | [Description(@"Tests error handling.")] 22 | [GraphRoute(name: "SameRoute")] 23 | public DuplicateResponse TestRequest2() 24 | { 25 | return new DuplicateResponse 26 | { 27 | Value = 2 28 | }; 29 | } 30 | } 31 | 32 | public class DuplicateResponse 33 | { 34 | public int Value { get; set; } 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Schemas/EchoSchema.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using GraphQL.SchemaGenerator.Attributes; 6 | 7 | namespace GraphQL.SchemaGenerator.Tests.Schemas 8 | { 9 | [GraphType] 10 | public class EchoSchema 11 | { 12 | [Description(@"Tests a variety or request and response types.{VerifyComment}")] 13 | [GraphRoute] 14 | public SchemaResponse TestRequest(Schema1Request request) 15 | { 16 | return new SchemaResponse 17 | { 18 | Value = request?.Echo ?? 5, 19 | StringValue = request?.Data, 20 | DecimalValue = request?.Decimal 21 | }; 22 | } 23 | 24 | [GraphRoute] 25 | public SchemaResponse TestEnumerableRequest(IEnumerable request) 26 | { 27 | if (request == null) 28 | { 29 | return null; 30 | } 31 | return new SchemaResponse 32 | { 33 | Value = request.First().Echo ?? 5, 34 | StringValue = request.First().Data 35 | }; 36 | } 37 | 38 | 39 | [GraphRoute] 40 | public IEnumerable TestEnumerable(Schema1Request request) 41 | { 42 | return new List 43 | { 44 | new SchemaResponse 45 | { 46 | Value = 1 47 | }, 48 | new SchemaResponse 49 | { 50 | Value = request?.Echo ?? 5 51 | }, 52 | }; 53 | } 54 | 55 | [GraphRoute] 56 | public IResponse TestInterface() 57 | { 58 | return new SchemaResponse 59 | { 60 | Value = 8, 61 | Enum = Episode.JEDI 62 | }; 63 | } 64 | } 65 | 66 | //todo: note sure why we had two types and what they are for. 67 | [GraphKnownType(typeof(SchemaResponse), typeof(SchemaResponse))] 68 | public interface IResponse 69 | { 70 | int Value { get; } 71 | } 72 | 73 | public class Schema1Request 74 | { 75 | public int? Echo { get; set; } 76 | public string Data { get; set; } 77 | 78 | public decimal? Decimal { get; set; } 79 | 80 | public DateTimeOffset? Offset { get; set; } 81 | 82 | public IEnumerable ComplexRequests { get; set; } 83 | 84 | public InnerRequest InnerRequest { get; set; } 85 | } 86 | 87 | public class InnerRequest 88 | { 89 | public string InnerData { get; set; } 90 | } 91 | 92 | public class SchemaResponse : IResponse 93 | { 94 | public Episode Enum { get; set; } = Episode.NEWHOPE; 95 | 96 | public int Value { get; set; } 97 | 98 | public int? NullValue { get; } = null; 99 | 100 | public decimal? DecimalValue { get; set; } 101 | 102 | public DateTimeOffset? Date { get; set; } = new DateTime(1999,1,1); 103 | 104 | public TimeSpan TimeSpan { get; set; } 105 | 106 | public byte[] ByteArray { get; set; } 107 | 108 | public string StringValue { get; set; } 109 | 110 | public IDictionary> NestDictionary { get; set; } 111 | 112 | public IDictionary Values { get; set; } = new Dictionary 113 | { 114 | {"99", new Response2 {ComplicatedResponse = new Schema1Request {Data = "99", Echo = 99} } }, 115 | {"59", new Response2 {ComplicatedResponse = new Schema1Request {Data = "59", Echo = 59} } }, 116 | {"null", null} 117 | }; 118 | } 119 | 120 | public class Response2 121 | { 122 | public Schema1Request ComplicatedResponse { get; set; } 123 | } 124 | 125 | public enum Episode 126 | { 127 | NEWHOPE, 128 | JEDI 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Schemas/EchoStateSchema.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.ComponentModel.DataAnnotations; 5 | using GraphQL.SchemaGenerator.Attributes; 6 | using GraphQL.SchemaGenerator.Models; 7 | 8 | namespace GraphQL.SchemaGenerator.Tests.Schemas 9 | { 10 | [GraphType] 11 | public class EchoStateSchema 12 | { 13 | private static StateResponse State { get; } = new StateResponse(); 14 | 15 | [Description(@"Sets the data.")] 16 | [GraphRoute(isMutation:true)] 17 | public StateResponse SetData(int request) 18 | { 19 | State.Data = request; 20 | 21 | return GetState(); 22 | } 23 | 24 | [Description(@"Sets both the data and state.")] 25 | [GraphRoute(isMutation: true)] 26 | public StateResponse Set(SetRequest request) 27 | { 28 | State.Data = request.Data; 29 | State.State = request.State ?? ValidStates.Open; 30 | State.Decimal = request.Decimal; 31 | 32 | return GetState(); 33 | } 34 | 35 | [GraphRoute(isMutation: true)] 36 | public StateResponse SetAdvanced(SetRequestAdvanced request) 37 | { 38 | State.Data = request.Data + request.NonRequiredInt; 39 | State.State = request.State ?? ValidStates.Open; 40 | State.Decimal = request.Decimal; 41 | 42 | if (request.NonRequiredBool) 43 | { 44 | State.State = ValidStates.Closed; 45 | } 46 | 47 | return GetState(); 48 | } 49 | 50 | [GraphRoute(isMutation: true)] 51 | public SetRequestAdvancedString SetAdvancedString(SetRequestAdvancedString request) 52 | { 53 | return request; 54 | } 55 | 56 | [Description(@"Sets the state.")] 57 | [GraphRoute(isMutation: true)] 58 | public StateResponse SetState(ValidStates request) 59 | { 60 | State.State = request; 61 | 62 | return GetState(); 63 | } 64 | 65 | [Description(@"Reads the state.")] 66 | [GraphRoute] //since it returns a value, query will be assumed 67 | public StateResponse GetState() 68 | { 69 | return State; 70 | } 71 | } 72 | 73 | public enum ValidStates 74 | { 75 | Open = 1, 76 | Closed = 0 77 | }; 78 | 79 | public class StateResponse 80 | { 81 | public ValidStates State { get; set; } 82 | public decimal? Decimal { get; set; } 83 | public int Data { get; set; } 84 | } 85 | 86 | public class SetRequest 87 | { 88 | public ValidStates? State { get; set; } 89 | 90 | public decimal? Decimal { get; set; } 91 | 92 | [Required] 93 | public int Data { get; set; } 94 | } 95 | 96 | public class SetRequestAdvanced: SetRequest 97 | { 98 | [GraphNotRequired] 99 | public bool NonRequiredBool { get; set; } 100 | [GraphNotRequired] 101 | public int NonRequiredInt { get; set; } 102 | //defaults to not required unless [GraphNotRequired(required)] is set. 103 | [Required] 104 | public DateTime? NullRequiredDateTime { get; set; } 105 | public string NotRequiredString{ get; set; } 106 | } 107 | 108 | public class SetRequestAdvancedString 109 | { 110 | [GraphNotRequired] 111 | public bool NonRequiredBool { get; set; } 112 | //defaults to required (strings can be null) 113 | [Required] 114 | public string RequiredString { get; set; } 115 | //defaults to not required unless [GraphNotRequired(required)] is set. 116 | [Required] 117 | public DateTime? NullRequiredDateTime { get; set; } 118 | //default to not required 119 | public string NotRequiredString { get; set; } 120 | //defaults to not required unless [GraphNotRequired(required)] is set. 121 | [Required] 122 | public IList RequiredObjects{get;set;} 123 | public SetRequest NonRequiredObject { get; set; } 124 | [Required] 125 | public SetRequest RequiredObject { get; set; } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Schemas/GenericsSchema.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel; 3 | using GraphQL.SchemaGenerator.Attributes; 4 | 5 | namespace GraphQL.SchemaGenerator.Tests.Schemas 6 | { 7 | public class GenericsSchema 8 | { 9 | [Description(@"Tests a multiple types")] 10 | [GraphRoute] 11 | public EchoGeneric EchoGenerics(EchoGeneric int1, EchoGeneric int2, 12 | EchoGeneric string1) 13 | { 14 | return new EchoGeneric 15 | { 16 | data = $"{int1?.data}{int2?.data}{string1?.data}" 17 | }; 18 | } 19 | 20 | [Description(@"Tests int types")] 21 | [GraphRoute] 22 | public EchoGenericList EchoClassGenerics() 23 | { 24 | return new EchoGenericList(new List 25 | { 26 | new Inner 27 | { 28 | InnerInt = 1, 29 | InnerString = "Hi" 30 | } 31 | } 32 | ); 33 | } 34 | 35 | [Description(@"Tests int types")] 36 | [GraphRoute] 37 | public EchoGenericList EchoClassGenerics2() 38 | { 39 | return new EchoGenericList(new List 40 | { 41 | new Inner2 42 | { 43 | Inner2Int = 2, 44 | Inner2String = "Bye" 45 | } 46 | } 47 | ); 48 | } 49 | } 50 | 51 | public class EchoGeneric 52 | { 53 | public T data { get; set; } 54 | } 55 | 56 | public class EchoGenericList : EchoGeneric where T : class 57 | { 58 | public EchoGenericList(IEnumerable values) 59 | { 60 | list = values; 61 | } 62 | 63 | public IEnumerable list { get; } 64 | } 65 | 66 | public class Inner 67 | { 68 | public string InnerString { get; set; } 69 | public int InnerInt { get; set; } 70 | } 71 | 72 | public class Inner2 73 | { 74 | public string Inner2String { get; set; } 75 | public int Inner2Int { get; set; } 76 | } 77 | } -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Schemas/PerformanceSchema.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using GraphQL.SchemaGenerator.Attributes; 5 | 6 | namespace GraphQL.SchemaGenerator.Tests.Schemas 7 | { 8 | [GraphType] 9 | public class PerformanceSchema 10 | { 11 | private readonly List largeList; 12 | 13 | public PerformanceSchema() 14 | { 15 | largeList = new List(); 16 | 17 | for (var x = 0; x < 10000; x++) 18 | largeList.Add(new SchemaResponse 19 | { 20 | Date = DateTimeOffset.Now 21 | }); 22 | } 23 | 24 | [GraphRoute] 25 | public List TestList() 26 | { 27 | return largeList; 28 | } 29 | 30 | [GraphRoute] 31 | public List SlowCall() 32 | { 33 | Thread.Sleep(1000); 34 | return new List(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Schemas/PrimitiveSchema.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using GraphQL.SchemaGenerator.Attributes; 3 | 4 | namespace GraphQL.SchemaGenerator.Tests.Schemas 5 | { 6 | [GraphType] 7 | public class PrimitiveSchema 8 | { 9 | [Description(@"Tests a null response mutation")] 10 | [GraphRoute(IsMutation = true)] 11 | public void TestRequest(bool clear) 12 | { 13 | 14 | } 15 | 16 | [Description(@"Tests a string response mutation")] 17 | [GraphRoute] 18 | public string TestString() 19 | { 20 | return "Hi"; 21 | } 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Tests/AsyncSchemaTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using GraphQL.Execution; 5 | using GraphQL.Http; 6 | using GraphQL.SchemaGenerator.Tests.Helpers; 7 | using GraphQL.SchemaGenerator.Tests.Mocks; 8 | using GraphQL.SchemaGenerator.Tests.Schemas; 9 | using GraphQL.Validation; 10 | using GraphQL.Validation.Complexity; 11 | using Xunit; 12 | 13 | namespace GraphQL.SchemaGenerator.Tests.Tests 14 | { 15 | public class AsyncSchemaTests 16 | { 17 | [Fact] 18 | public void CreateSchema_WithClassArgument_HasExpectedSchema() 19 | { 20 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 21 | var schema = schemaGenerator.CreateSchema(typeof(AsyncSchema)); 22 | 23 | var sut = schema.AllTypes; 24 | Assert.True(sut.Any(t=>t.Name == "Input_Schema1Request")); 25 | } 26 | 27 | [Fact] 28 | public void BasicParameterExample_Works() 29 | { 30 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 31 | var schema = schemaGenerator.CreateSchema(typeof(AsyncSchema)); 32 | 33 | var query = @"{ 34 | testRequest {value} 35 | }"; 36 | 37 | var expected = @"{ 38 | testRequest: {value:5} 39 | }"; 40 | 41 | GraphAssert.QuerySuccess(schema, query, expected); 42 | } 43 | 44 | [Fact] 45 | public void WithParameterExample_Works() 46 | { 47 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 48 | var schema = schemaGenerator.CreateSchema(typeof(AsyncSchema)); 49 | 50 | var query = @"{ 51 | testRequest(request:{echo:1}) {value} 52 | }"; 53 | 54 | var expected = @"{ 55 | testRequest: {value:1} 56 | }"; 57 | 58 | GraphAssert.QuerySuccess(schema, query, expected); 59 | } 60 | 61 | [Fact] 62 | public void WithEnumerableParameterExample_Works() 63 | { 64 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 65 | var schema = schemaGenerator.CreateSchema(typeof(AsyncSchema)); 66 | 67 | var query = @"{ 68 | testEnumerableRequest(request:[{echo:1}]) {value} 69 | }"; 70 | 71 | var expected = @"{ 72 | testEnumerableRequest: {value:1} 73 | }"; 74 | 75 | GraphAssert.QuerySuccess(schema, query, expected); 76 | } 77 | 78 | [Fact] 79 | public void WithStringParameterExample_Works() 80 | { 81 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 82 | var schema = schemaGenerator.CreateSchema(typeof(AsyncSchema)); 83 | 84 | var query = @"{ 85 | testRequest(request:{data:""yes""}) {stringValue} 86 | }"; 87 | 88 | var expected = @"{ 89 | testRequest: {stringValue:""yes""} 90 | }"; 91 | 92 | GraphAssert.QuerySuccess(schema, query, expected); 93 | } 94 | 95 | [Fact] 96 | public void WithStringEscapedParameterExample_Works() 97 | { 98 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 99 | var schema = schemaGenerator.CreateSchema(typeof(AsyncSchema)); 100 | 101 | //data = y\es m"am 102 | var query = @"{ 103 | testRequest(request:{data:""y\\es m\""am""}) {stringValue} 104 | }"; 105 | 106 | var expected = @"{ 107 | testRequest: {stringValue:""y\\es m\""am""} 108 | }"; 109 | 110 | GraphAssert.QuerySuccess(schema, query, expected); 111 | } 112 | 113 | [Fact] 114 | public void WithComplexParameters_Works() 115 | { 116 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 117 | var schema = schemaGenerator.CreateSchema(typeof(AsyncSchema)); 118 | 119 | var query = @"{ 120 | testRequest(request:{ 121 | complexRequests:[{ 122 | innerData:""345"" 123 | }] 124 | }) {value} 125 | }"; 126 | 127 | var expected = @"{ 128 | testRequest: {value:5} 129 | }"; 130 | 131 | GraphAssert.QuerySuccess(schema, query, expected); 132 | } 133 | 134 | [Fact] 135 | public void WithComplexParameters_HaveCorrectType() 136 | { 137 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 138 | var schema = schemaGenerator.CreateSchema(typeof(AsyncSchema)); 139 | 140 | var query = @"{ 141 | __type(name : ""Input_InnerRequest"") { 142 | name 143 | kind 144 | }}"; 145 | 146 | var expected = @"{ 147 | __type: { 148 | name: ""Input_InnerRequest"", 149 | kind: ""INPUT_OBJECT"" 150 | } 151 | }"; 152 | 153 | GraphAssert.QuerySuccess(schema, query, expected); 154 | } 155 | 156 | [Fact] 157 | public void WithEnumerableExample_Works() 158 | { 159 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 160 | var schema = schemaGenerator.CreateSchema(typeof(AsyncSchema)); 161 | 162 | var query = @"{ 163 | testEnumerable{value} 164 | }"; 165 | 166 | var expected = @"{ 167 | testEnumerable: [{value: 1},{value: 5}] 168 | }"; 169 | 170 | GraphAssert.QuerySuccess(schema, query, expected); 171 | } 172 | 173 | [Fact] 174 | public void WithEnum_Works() 175 | { 176 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 177 | var schema = schemaGenerator.CreateSchema(typeof(AsyncSchema)); 178 | 179 | var query = @"{ 180 | testRequest {enum} 181 | }"; 182 | 183 | var expected = @"{ 184 | testRequest: {enum:""NEWHOPE""} 185 | }"; 186 | 187 | GraphAssert.QuerySuccess(schema, query, expected); 188 | } 189 | 190 | [Fact] 191 | public void WithDateTimeOffset_Works() 192 | { 193 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 194 | var schema = schemaGenerator.CreateSchema(typeof(AsyncSchema)); 195 | 196 | var query = @"{ 197 | testRequest {date} 198 | }"; 199 | 200 | var expected = @"{ 201 | testRequest: {date:""1999-01-01T00:00:00-07:00""} 202 | }"; 203 | 204 | GraphAssert.QuerySuccess(schema, query, expected); 205 | } 206 | 207 | [Fact] 208 | public void FieldDescription_Works() 209 | { 210 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 211 | var schema = schemaGenerator.CreateSchema(typeof(AsyncSchema)); 212 | 213 | var query = @"{ 214 | __schema{ 215 | types{ 216 | name, 217 | fields { 218 | name 219 | description 220 | } 221 | } 222 | } 223 | }"; 224 | 225 | var exec = new DocumentExecuter(new GraphQLDocumentBuilder(), new DocumentValidator(), new ComplexityAnalyzer()); 226 | var result = exec.ExecuteAsync(schema, null, query, null).Result; 227 | 228 | var writer = new DocumentWriter(indent: true); 229 | var writtenResult = writer.Write(result.Data); 230 | 231 | var errors = result.Errors?.FirstOrDefault(); 232 | 233 | Assert.Null(errors?.Message); 234 | Assert.True(writtenResult.Contains("{VerifyComment}")); 235 | Assert.False(writtenResult.Contains("\"Task\"")); 236 | Assert.False(writtenResult.Contains("\"MethodBase\"")); 237 | } 238 | 239 | [Fact] 240 | public void WithNull_Works() 241 | { 242 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 243 | var schema = schemaGenerator.CreateSchema(typeof(AsyncSchema)); 244 | 245 | var query = @"{ 246 | testRequest {nullValue} 247 | }"; 248 | 249 | var expected = @"{ 250 | testRequest: {nullValue:null} 251 | }"; 252 | 253 | GraphAssert.QuerySuccess(schema, query, expected); 254 | } 255 | 256 | [Fact] 257 | public void WithTask_Works() 258 | { 259 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 260 | var schema = schemaGenerator.CreateSchema(typeof(AsyncSchema)); 261 | 262 | var query = @"{ 263 | notRecommendedToReturnATask 264 | }"; 265 | 266 | var expected = @"{ 267 | notRecommendedToReturnATask:{} 268 | }"; 269 | 270 | GraphAssert.QuerySuccess(schema, query, expected); 271 | } 272 | 273 | 274 | [Fact] 275 | public void WithTaskReturnType_HidesTask() 276 | { 277 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 278 | var schema = schemaGenerator.CreateSchema(typeof(AsyncSchema)); 279 | 280 | var query = @"{ 281 | testRequest {nullValue} 282 | }"; 283 | 284 | var expected = @"{ 285 | testRequest: {nullValue:null} 286 | }"; 287 | 288 | GraphAssert.QuerySuccess(schema, query, expected); 289 | } 290 | 291 | [Fact] 292 | public void WithDictionary_Works() 293 | { 294 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 295 | var schema = schemaGenerator.CreateSchema(typeof(AsyncSchema)); 296 | 297 | var query = @"{ 298 | testRequest { 299 | values{ 300 | key 301 | value{ 302 | complicatedResponse{ 303 | echo 304 | } 305 | } 306 | } 307 | } 308 | }"; 309 | 310 | var expected = @"{ 311 | testRequest: { 312 | values: [ 313 | { 314 | key: ""99"", 315 | value: { 316 | complicatedResponse: { 317 | echo: 99 318 | } 319 | } 320 | }, 321 | { 322 | key: ""59"", 323 | value: { 324 | complicatedResponse: { 325 | echo: 59 326 | } 327 | } 328 | }, 329 | { 330 | key: ""null"", 331 | value: null 332 | } 333 | ] 334 | } 335 | }"; 336 | 337 | GraphAssert.QuerySuccess(schema, query, expected); 338 | } 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Tests/DateTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GraphQL.SchemaGenerator.Tests.Helpers; 3 | using GraphQL.SchemaGenerator.Tests.Mocks; 4 | using GraphQL.SchemaGenerator.Tests.Schemas; 5 | using Xunit; 6 | 7 | namespace GraphQL.SchemaGenerator.Tests.Tests 8 | { 9 | public class DateTests 10 | { 11 | 12 | [Fact] 13 | public void GetTimeSpan_IsSafe() 14 | { 15 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 16 | var schema = schemaGenerator.CreateSchema(typeof(DateSchema)); 17 | 18 | var query = @" 19 | {dates(dates:{timeSpan:""1.23:59:59""}) { 20 | timeSpan 21 | }} 22 | "; 23 | 24 | var expected = @"{ 25 | dates: { 26 | timeSpan:""1.23:59:59"" 27 | } 28 | }"; 29 | 30 | GraphAssert.QuerySuccess(schema, query, expected); 31 | } 32 | 33 | [Fact] 34 | public void GetTimeSpanAsVariable_IsSafe() 35 | { 36 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 37 | var schema = schemaGenerator.CreateSchema(typeof(DateSchema)); 38 | 39 | var query = @" 40 | query($ts:TimeSpan!) 41 | { 42 | dates(dates:{timeSpan:$ts}) { 43 | timeSpan 44 | } 45 | } 46 | "; 47 | 48 | var variables = @"{ 49 | ts:""1.23:59:59"" 50 | }"; 51 | 52 | var expected = @"{ 53 | dates: { 54 | timeSpan:""1.23:59:59"" 55 | } 56 | }"; 57 | 58 | GraphAssert.QuerySuccess(schema, query, expected, variables); 59 | } 60 | 61 | 62 | [Fact] 63 | public void GetOffset_IsSafe() 64 | { 65 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 66 | var schema = schemaGenerator.CreateSchema(typeof(DateSchema)); 67 | 68 | var query = @" 69 | {dates(dates:{offset:""2013-07-02T09:00:00"", dateTime:""2013-07-02T09:00""}) { 70 | offset 71 | dateTime 72 | }} 73 | "; 74 | 75 | var expected = @"{ 76 | dates: { 77 | offset:""2013-07-02T09:00:00-06:00"", 78 | dateTime:""2013-07-02T09:00:00"" 79 | } 80 | }"; 81 | 82 | GraphAssert.QuerySuccess(schema, query, expected); 83 | } 84 | 85 | [Fact] 86 | public void GetUtcDates_IsSafe() 87 | { 88 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 89 | var schema = schemaGenerator.CreateSchema(typeof(DateSchema)); 90 | 91 | var query = @" 92 | {dates(dates:{offset:""2013-07-02T09:00:00Z"", dateTime:""2013-07-02T09:00Z""}) { 93 | y2k 94 | y2kUtc 95 | }} 96 | "; 97 | 98 | var expected = @"{ 99 | dates: { 100 | y2k:""2000-01-01T00:00:00"", 101 | y2kUtc:""2000-01-01T00:00:00Z"" 102 | } 103 | }"; 104 | 105 | GraphAssert.QuerySuccess(schema, query, expected); 106 | } 107 | 108 | [Fact] 109 | public void GetZuluOffset_IsSafe() 110 | { 111 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 112 | var schema = schemaGenerator.CreateSchema(typeof(DateSchema)); 113 | 114 | var query = @" 115 | {dates(dates:{offset:""2013-07-02T09:00:00Z"", dateTime:""2013-07-02T09:00Z""}) { 116 | offset 117 | dateTime 118 | dateUTC 119 | }} 120 | "; 121 | 122 | var expected = @"{ 123 | dates: { 124 | offset:""2013-07-02T03:00:00-06:00"", 125 | dateTime:""2013-07-02T09:00:00Z"", 126 | dateUTC:""2013-07-02T09:00:00Z"" 127 | } 128 | }"; 129 | 130 | GraphAssert.QuerySuccess(schema, query, expected); 131 | } 132 | 133 | [Fact] 134 | public void GetTimeZoneOffset_IsSafe() 135 | { 136 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 137 | var schema = schemaGenerator.CreateSchema(typeof(DateSchema)); 138 | 139 | var query = @" 140 | {dates(dates:{offset:""2013-07-02T09:00:00-06:00"", dateTime:""2013-07-02T09:00-06:00""}) { 141 | offset 142 | dateTime 143 | dateUTC 144 | }} 145 | "; 146 | 147 | var expected = @"{ 148 | dates: { 149 | offset:""2013-07-02T09:00:00-06:00"", 150 | dateTime:""2013-07-02T09:00:00-06:00"", 151 | dateUTC:""2013-07-02T15:00:00Z"" 152 | } 153 | }"; 154 | 155 | GraphAssert.QuerySuccess(schema, query, expected); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Tests/ErrorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using GraphQL.SchemaGenerator.Tests.Mocks; 4 | using GraphQL.SchemaGenerator.Tests.Schemas; 5 | using Xunit; 6 | 7 | namespace GraphQL.SchemaGenerator.Tests.Tests 8 | { 9 | public class ErrorTests 10 | { 11 | [Fact] 12 | public void Duplicate_Name_Throws() 13 | { 14 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 15 | Exception e = null; 16 | try 17 | { 18 | schemaGenerator.CreateSchema(typeof(DuplicateSchema)); 19 | } 20 | catch (Exception er) 21 | { 22 | e = er; 23 | } 24 | 25 | //this should pass whenever the change is pushed. 26 | Assert.NotNull(e); 27 | Assert.Contains("SameRoute", e.Message); 28 | } 29 | 30 | [Fact] 31 | public async void OperationLimit_WhenExceeded_Throws() 32 | { 33 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 34 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 35 | 36 | var query = @"{ 37 | testRequest {value} 38 | t2:testRequest {value} 39 | }"; 40 | 41 | await Assert.ThrowsAsync(() => 42 | DocumentOperations.ExecuteOperationsAsync(schema, null, query, maxOperationNodes: 1)); 43 | } 44 | 45 | [Fact] 46 | public async void OperationLimit_WhenExceededWithNodes_Throws() 47 | { 48 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 49 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 50 | 51 | var query = @"query{ 52 | t2:testRequest {value, value, value} 53 | } 54 | query{t1:testRequest{value}} 55 | "; 56 | 57 | await Assert.ThrowsAsync(() => 58 | DocumentOperations.ExecuteOperationsAsync(schema, null, query, maxOperationNodes: 1)); 59 | } 60 | 61 | [Fact] 62 | public async void OperationLimit_WhenExceededWithMutations_Throws() 63 | { 64 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 65 | var schema = schemaGenerator.CreateSchema(typeof(EchoStateSchema)); 66 | 67 | var query = @" 68 | mutation SetState{ 69 | setState (request:Open){ 70 | state 71 | } 72 | } 73 | query GetState{ 74 | getState{ 75 | state 76 | } 77 | } 78 | "; 79 | 80 | await Assert.ThrowsAsync(() => 81 | DocumentOperations.ExecuteOperationsAsync(schema, null, query, maxOperationNodes: 1)); 82 | } 83 | 84 | [Fact] 85 | public async void OperationLimit_WhenNotExceeded_Runs() 86 | { 87 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 88 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 89 | 90 | var query = @"{ 91 | testRequest {value} 92 | t2:testRequest {value} 93 | }"; 94 | 95 | await DocumentOperations.ExecuteOperationsAsync(schema, null, query, maxOperationNodes: 2); 96 | } 97 | 98 | [Fact] 99 | public async void OperationLimit_WhenNotExceededWithNodes_Runs() 100 | { 101 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 102 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 103 | 104 | var query = @"query{ 105 | t2:testRequest {value, value, value} 106 | } 107 | query{t1:testRequest{value}} 108 | "; 109 | 110 | await DocumentOperations.ExecuteOperationsAsync(schema, null, query, maxOperationNodes: 2); 111 | } 112 | 113 | [Fact] 114 | public async void OperationLimit_WhenNotExceededWithMutations_Runs() 115 | { 116 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 117 | var schema = schemaGenerator.CreateSchema(typeof(EchoStateSchema)); 118 | 119 | var query = @" 120 | mutation SetState{ 121 | setState (request:Open){ 122 | state 123 | } 124 | } 125 | query GetState{ 126 | getState{ 127 | state 128 | } 129 | } 130 | "; 131 | 132 | await DocumentOperations.ExecuteOperationsAsync(schema, null, query, maxOperationNodes: 2); 133 | } 134 | 135 | [Fact] 136 | public async void OperationLimit_WhenNotExceededWithParameters_Runs() 137 | { 138 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 139 | var schema = schemaGenerator.CreateSchema(typeof(EchoStateSchema)); 140 | 141 | var query = @" 142 | mutation SetState($begin: Date!, $end: Date!, $test: String, $test2: String){ 143 | setState (request:Open){ 144 | state 145 | } 146 | } 147 | query GetState($begin: Date!, $end: Date!, $test: String, $test2: String){ 148 | getState{ 149 | state 150 | } 151 | state2:getState{ 152 | state 153 | } 154 | } 155 | "; 156 | 157 | await DocumentOperations.ExecuteOperationsAsync(schema, null, query, maxOperationNodes: 3); 158 | } 159 | 160 | 161 | [Fact] 162 | public async void Blacklist_WithQueryAndMutation_Fails() 163 | { 164 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 165 | var schema = schemaGenerator.CreateSchema(typeof(EchoStateSchema)); 166 | 167 | var query = @" 168 | mutation SetState{ 169 | outer:setState (request:Open){ 170 | state 171 | } 172 | } 173 | query GetState{ 174 | getState{ 175 | state 176 | } 177 | } 178 | "; 179 | 180 | var blackList = new List 181 | { 182 | "setState" 183 | }; 184 | 185 | await Assert.ThrowsAsync(() => 186 | DocumentOperations.ExecuteOperationsAsync(schema, null, query, blackListedOperations:blackList)); 187 | } 188 | 189 | [Theory] 190 | [InlineData("")] 191 | [InlineData(null)] 192 | [InlineData("SetState")] 193 | [InlineData("GetState")] 194 | [InlineData("setStatev2")] 195 | [InlineData("inner")] 196 | [InlineData("outer")] 197 | public async void Blacklist_WithQueryAndMutation_Works(string testOp) 198 | { 199 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 200 | var schema = schemaGenerator.CreateSchema(typeof(EchoStateSchema)); 201 | 202 | var query = @" 203 | mutation SetState{ 204 | outer:setState (request:Open){ 205 | inner:state 206 | } 207 | } 208 | query GetState{ 209 | getState{ 210 | state 211 | } 212 | } 213 | "; 214 | 215 | var blackList = new List 216 | { 217 | testOp 218 | }; 219 | 220 | 221 | await DocumentOperations.ExecuteOperationsAsync(schema, null, query, blackListedOperations: blackList); 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Tests/GenericQueryTests.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.SchemaGenerator.Tests.Helpers; 2 | using GraphQL.SchemaGenerator.Tests.Mocks; 3 | using GraphQL.SchemaGenerator.Tests.Schemas; 4 | using Xunit; 5 | 6 | namespace GraphQL.SchemaGenerator.Tests.Tests 7 | { 8 | public class GenericQueryTests 9 | { 10 | [Fact] 11 | public void BasicExample_Works() 12 | { 13 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 14 | var schema = schemaGenerator.CreateSchema(typeof(GenericsSchema)); 15 | 16 | var query = @" 17 | { 18 | echoGenerics{data} 19 | } 20 | "; 21 | 22 | var expected = @"{ 23 | echoGenerics: { 24 | data: """" 25 | } 26 | }"; 27 | 28 | GraphAssert.QuerySuccess(schema, query, expected); 29 | } 30 | 31 | [Fact] 32 | public void BasicInputExample_Works() 33 | { 34 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 35 | var schema = schemaGenerator.CreateSchema(typeof(GenericsSchema)); 36 | 37 | var query = @" 38 | { 39 | echoGenerics( 40 | int1:{data:2} 41 | string1:{data:""test""}, 42 | ) 43 | {data} 44 | } 45 | "; 46 | 47 | var expected = @"{ 48 | echoGenerics: { 49 | data: ""2test"" 50 | } 51 | }"; 52 | 53 | GraphAssert.QuerySuccess(schema, query, expected); 54 | } 55 | 56 | [Fact] 57 | public void BasicClassInputExample_Works() 58 | { 59 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 60 | var schema = schemaGenerator.CreateSchema(typeof(GenericsSchema)); 61 | 62 | var query = @" 63 | { 64 | echoClassGenerics{ 65 | list{innerInt} 66 | } 67 | echoClassGenerics2{ 68 | list{inner2Int} 69 | } 70 | } 71 | "; 72 | 73 | var expected = @"{ 74 | echoClassGenerics: { 75 | list: [{innerInt:1}] 76 | }, 77 | echoClassGenerics2: { 78 | list: [{inner2Int:2}] 79 | } 80 | }"; 81 | 82 | GraphAssert.QuerySuccess(schema, query, expected); 83 | } 84 | 85 | [Fact] 86 | public void Schema_HasUniqueTypes() 87 | { 88 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 89 | var schema = schemaGenerator.CreateSchema(typeof(GenericsSchema)); 90 | 91 | var query = @" 92 | { 93 | __schema{ 94 | queryType { 95 | name, 96 | fields{ 97 | name 98 | type{ 99 | name 100 | kind 101 | } 102 | } 103 | } 104 | } 105 | } 106 | "; 107 | 108 | var expected = @"{ 109 | __schema: { 110 | queryType: { 111 | name: ""RootQueries"", 112 | fields: [ 113 | { 114 | name: ""echoGenerics"", 115 | type: { 116 | name: ""EchoGeneric__String"", 117 | kind: ""OBJECT"" 118 | } 119 | }, 120 | { 121 | name: ""echoClassGenerics"", 122 | type: { 123 | name: ""EchoGenericList__Inner"", 124 | kind: ""OBJECT"" 125 | } 126 | }, 127 | { 128 | name: ""echoClassGenerics2"", 129 | type: { 130 | name: ""EchoGenericList__Inner2"", 131 | kind: ""OBJECT"" 132 | } 133 | } 134 | ] 135 | } 136 | } 137 | }"; 138 | 139 | GraphAssert.QuerySuccess(schema, query, expected); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Tests/MutationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using GraphQL.SchemaGenerator.Tests.Helpers; 4 | using GraphQL.SchemaGenerator.Tests.Mocks; 5 | using GraphQL.SchemaGenerator.Tests.Schemas; 6 | using Xunit; 7 | 8 | namespace GraphQL.SchemaGenerator.Tests.Tests 9 | { 10 | public class MutationTests 11 | { 12 | [Fact] 13 | public void BasicExample_Works() 14 | { 15 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 16 | var schema = schemaGenerator.CreateSchema(typeof(EchoStateSchema)); 17 | 18 | var query = @" 19 | mutation SetData{ 20 | setData (request:4){ 21 | data 22 | } 23 | } 24 | "; 25 | 26 | var expected = @"{ 27 | setData: { 28 | data: 4 29 | } 30 | }"; 31 | 32 | GraphAssert.QuerySuccess(schema, query, expected); 33 | } 34 | 35 | [Fact] 36 | public void BasicExample_WithEnums_Works() 37 | { 38 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 39 | var schema = schemaGenerator.CreateSchema(typeof(EchoStateSchema)); 40 | 41 | var query = @" 42 | mutation SetState{ 43 | setState (request:Open){ 44 | state 45 | } 46 | } 47 | "; 48 | 49 | var expected = @"{ 50 | setState: { 51 | state: ""Open"" 52 | } 53 | }"; 54 | 55 | GraphAssert.QuerySuccess(schema, query, expected); 56 | } 57 | 58 | 59 | [Fact] 60 | public void BasicExample_WithQueryAndMutation_Works() 61 | { 62 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 63 | var schema = schemaGenerator.CreateSchema(typeof(EchoStateSchema)); 64 | 65 | var query = @" 66 | mutation SetState{ 67 | setState (request:Open){ 68 | state 69 | } 70 | } 71 | query GetState{ 72 | getState{ 73 | state 74 | } 75 | } 76 | "; 77 | 78 | var expected = @"{ 79 | SetState:{setState: { 80 | state: ""Open"" 81 | }}, 82 | GetState:{getState: { 83 | state: ""Open"" 84 | } 85 | }}"; 86 | 87 | GraphAssert.QueryOperationsSuccess(schema, query, expected); 88 | } 89 | 90 | [Fact] 91 | public void BasicExample_WithDecimal_Works() 92 | { 93 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 94 | var schema = schemaGenerator.CreateSchema(typeof(EchoStateSchema)); 95 | 96 | var query = @" 97 | mutation SetState{ 98 | set(request:{decimal:24.15, data:2}){ 99 | decimal 100 | } 101 | } 102 | "; 103 | 104 | var expected = @"{ 105 | set: { 106 | decimal: 24.15 107 | } 108 | }"; 109 | 110 | GraphAssert.QuerySuccess(schema, query, expected); 111 | } 112 | 113 | [Fact] 114 | public async void BasicExample_WithoutInt_Fails() 115 | { 116 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 117 | var schema = schemaGenerator.CreateSchema(typeof(EchoStateSchema)); 118 | 119 | var query = @" 120 | mutation SetState{ 121 | set(request:{decimal:24.15}){ 122 | decimal 123 | } 124 | } 125 | "; 126 | 127 | var result = await DocumentOperations.ExecuteOperationsAsync(schema, null, query); 128 | Assert.NotEmpty(result.Errors); 129 | } 130 | 131 | [Fact] 132 | public void SetAdvanced_WithSameQuery_WorksBackwardsCompatible() 133 | { 134 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 135 | var schema = schemaGenerator.CreateSchema(typeof(EchoStateSchema)); 136 | 137 | var query = @" 138 | mutation SetState{ 139 | set:setAdvanced(request:{decimal:24.15, data:2}){ 140 | data 141 | state 142 | } 143 | } 144 | "; 145 | 146 | var expected = @"{ 147 | set: { 148 | data: 2, 149 | state: ""Open"" 150 | } 151 | }"; 152 | 153 | GraphAssert.QuerySuccess(schema, query, expected); 154 | } 155 | 156 | [Fact] 157 | public void SetAdvanced_WithNullableParam_Works() 158 | { 159 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 160 | var schema = schemaGenerator.CreateSchema(typeof(EchoStateSchema)); 161 | 162 | var query = @" 163 | mutation SetState($dec:Decimal!, $int:Int!, $int2:Int!, $date1:Date!, $str:String){ 164 | set:setAdvanced(request:{decimal:$dec, data:$int, nonRequiredInt:$int2, nullRequiredDateTime:$date1, notRequiredString:$str}){ 165 | decimal 166 | data 167 | state 168 | } 169 | } 170 | "; 171 | 172 | var expected = @"{ 173 | set: { 174 | decimal:24.15, 175 | data: 3, 176 | state: ""Open"" 177 | } 178 | }"; 179 | 180 | GraphAssert.QuerySuccess(schema, query, expected, "{dec:24.15, int:2, int2:1, date1:\"1-1-2011\"}"); 181 | } 182 | 183 | [Theory] 184 | [InlineData("String!","String!")] 185 | [InlineData("String","String!")] 186 | [InlineData("String!","String")] 187 | [InlineData("String", "String")] 188 | public void SetAdvancedString_WithStringParam_Works(string var1, string var2) 189 | { 190 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 191 | var schema = schemaGenerator.CreateSchema(typeof(EchoStateSchema)); 192 | 193 | var query = @" 194 | mutation SetState($str:"+var1+ @", $str2:" + var2 + @"){ 195 | set:setAdvancedString(request:{requiredString:$str, notRequiredString:$str2}){ 196 | requiredString 197 | notRequiredString 198 | } 199 | } 200 | "; 201 | 202 | var expected = @"{ 203 | set: { 204 | requiredString:""Yes"", 205 | notRequiredString: """" 206 | } 207 | }"; 208 | 209 | GraphAssert.QuerySuccess(schema, query, expected, "{str:\"Yes\", str2:\"\"}"); 210 | } 211 | 212 | [Fact] 213 | public void AdvancedExample_WithEnums_Works() 214 | { 215 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 216 | var schema = schemaGenerator.CreateSchema(typeof(EchoStateSchema)); 217 | 218 | var query = @" 219 | mutation SetState{ 220 | set (request:{state:Open, data:2}){ 221 | state 222 | data 223 | } 224 | } 225 | "; 226 | 227 | var expected = @"{ 228 | set: { 229 | state: ""Open"", 230 | data: 2 231 | } 232 | }"; 233 | 234 | GraphAssert.QuerySuccess(schema, query, expected); 235 | } 236 | 237 | /// 238 | /// Introspection was not working on Inputs due to a bug in GraphQl 239 | /// 240 | [Fact] 241 | public void AdvancedExample_Introspection_Works() 242 | { 243 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 244 | var schema = schemaGenerator.CreateSchema(typeof(EchoStateSchema)); 245 | 246 | var query = @"{ 247 | __type(name:""Input_SetRequest""){ 248 | name 249 | inputFields{ 250 | name 251 | type{ 252 | kind 253 | ofType{ 254 | kind 255 | } 256 | } 257 | } 258 | } 259 | } 260 | "; 261 | 262 | var expected = @"{ 263 | __type: { 264 | name: ""Input_SetRequest"", 265 | inputFields: [ 266 | { 267 | name: ""data"", 268 | type: { 269 | kind: ""NON_NULL"", 270 | ofType: { 271 | kind: ""SCALAR"" 272 | } 273 | } 274 | }, 275 | { 276 | name: ""decimal"", 277 | type: { 278 | kind: ""SCALAR"", 279 | ofType: null 280 | } 281 | }, 282 | { 283 | name: ""state"", 284 | type: { 285 | kind: ""ENUM"", 286 | ofType: null 287 | } 288 | } 289 | ] 290 | } 291 | }"; 292 | 293 | GraphAssert.QuerySuccess(schema, query, expected); 294 | } 295 | 296 | /// 297 | /// Introspection was not working on Inputs due to a bug in GraphQl 298 | /// 299 | [Fact] 300 | public void SetRequestAdvancedString_Introspection_Works() 301 | { 302 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 303 | var schema = schemaGenerator.CreateSchema(typeof(EchoStateSchema)); 304 | 305 | var query = @"{ 306 | __type(name:""Input_SetRequestAdvancedString""){ 307 | name 308 | inputFields{ 309 | name 310 | type{ 311 | kind 312 | ofType{ 313 | kind 314 | } 315 | } 316 | } 317 | } 318 | } 319 | "; 320 | 321 | var expected = 322 | @"{""__type"":{""name"":""Input_SetRequestAdvancedString"",""inputFields"":[{""name"":""nonRequiredBool"",""type"":{""kind"":""SCALAR"",""ofType"":null}},{""name"":""nonRequiredObject"",""type"":{""kind"":""INPUT_OBJECT"",""ofType"":null}},{""name"":""notRequiredString"",""type"":{""kind"":""SCALAR"",""ofType"":null}},{""name"":""nullRequiredDateTime"",""type"":{""kind"":""SCALAR"",""ofType"":null}},{""name"":""requiredObject"",""type"":{""kind"":""INPUT_OBJECT"",""ofType"":null}},{""name"":""requiredObjects"",""type"":{""kind"":""LIST"",""ofType"":{""kind"":""INPUT_OBJECT""}}},{""name"":""requiredString"",""type"":{""kind"":""SCALAR"",""ofType"":null}}]}}"; 323 | 324 | GraphAssert.QuerySuccess(schema, query, expected); 325 | } 326 | 327 | [Fact] 328 | public void PrimitiveExample_Works() 329 | { 330 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 331 | var schema = schemaGenerator.CreateSchema(typeof(PrimitiveSchema)); 332 | 333 | var query = @" 334 | mutation Test{ 335 | testRequest (clear:true) 336 | } 337 | "; 338 | 339 | var expected = @"{testRequest:null}"; 340 | 341 | GraphAssert.QuerySuccess(schema, query, expected); 342 | } 343 | 344 | 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Tests/PerformanceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading.Tasks; 4 | using GraphQL.Execution; 5 | using GraphQL.SchemaGenerator.Tests.Helpers; 6 | using GraphQL.SchemaGenerator.Tests.Mocks; 7 | using GraphQL.SchemaGenerator.Tests.Schemas; 8 | using GraphQL.Validation; 9 | using GraphQL.Validation.Complexity; 10 | using Xunit; 11 | using Xunit.Abstractions; 12 | 13 | namespace GraphQL.SchemaGenerator.Tests.Tests 14 | { 15 | public class PerformanceTests 16 | { 17 | [Fact] 18 | public async Task LargeLists_Perform_UnderThreshold() 19 | { 20 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 21 | 22 | var schema = schemaGenerator.CreateSchema(typeof(PerformanceSchema)); 23 | 24 | var query = @"{ 25 | testList{ 26 | date 27 | enum 28 | value 29 | nullValue 30 | decimalValue 31 | timeSpan 32 | byteArray 33 | stringValue 34 | values{ 35 | value{ 36 | complicatedResponse{ 37 | echo 38 | data 39 | } 40 | } 41 | } 42 | } 43 | }"; 44 | 45 | var stopwatch = new Stopwatch(); 46 | 47 | stopwatch.Start(); 48 | 49 | var result = await DocumentOperations.ExecuteOperationsAsync(schema, null, query, validate:false); 50 | 51 | stopwatch.Stop(); 52 | 53 | _output.WriteLine($"Total Milliseconds: {stopwatch.ElapsedMilliseconds}"); 54 | 55 | Assert.True(stopwatch.Elapsed.TotalSeconds < 2); 56 | Assert.Null(result.Errors); 57 | } 58 | 59 | [Fact] 60 | public async Task Methods_Perform_Async() 61 | { 62 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 63 | 64 | var schema = schemaGenerator.CreateSchema(typeof(PerformanceSchema)); 65 | 66 | var query = @"{ 67 | slow1:slowCall{ 68 | date 69 | } 70 | slow2:slowCall{ 71 | date 72 | } 73 | slow3:slowCall{ 74 | date 75 | } 76 | 77 | }"; 78 | 79 | var stopwatch = new Stopwatch(); 80 | 81 | stopwatch.Start(); 82 | 83 | var result = await DocumentOperations.ExecuteOperationsAsync(schema, null, query, validate: false); 84 | 85 | stopwatch.Stop(); 86 | 87 | _output.WriteLine($"Total Milliseconds: {stopwatch.ElapsedMilliseconds}"); 88 | 89 | Assert.True(stopwatch.Elapsed.TotalSeconds < 2); 90 | Assert.Null(result.Errors); 91 | } 92 | 93 | private readonly ITestOutputHelper _output; 94 | 95 | public PerformanceTests(ITestOutputHelper output) 96 | { 97 | _output = output; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Tests/QueryTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using GraphQL.Execution; 3 | using GraphQL.Http; 4 | using GraphQL.SchemaGenerator.Tests.Helpers; 5 | using GraphQL.SchemaGenerator.Tests.Mocks; 6 | using GraphQL.SchemaGenerator.Tests.Schemas; 7 | using GraphQL.Validation; 8 | using GraphQL.Validation.Complexity; 9 | using Xunit; 10 | 11 | namespace GraphQL.SchemaGenerator.Tests.Tests 12 | { 13 | public class QueryTests 14 | { 15 | [Fact] 16 | public void CreateSchema_WithClassArgument_HasExpectedSchema() 17 | { 18 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 19 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 20 | 21 | var sut = schema.AllTypes; 22 | Assert.True(sut.Any(t => t.Name == "Input_Schema1Request")); 23 | } 24 | 25 | [Fact] 26 | public void BasicParameterExample_Works() 27 | { 28 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 29 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 30 | 31 | var query = @"{ 32 | testRequest {value} 33 | }"; 34 | 35 | var expected = @"{ 36 | testRequest: {value:5} 37 | }"; 38 | 39 | GraphAssert.QuerySuccess(schema, query, expected); 40 | } 41 | 42 | [Fact] 43 | public void WithParameterExample_Works() 44 | { 45 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 46 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 47 | 48 | var query = @"{ 49 | testRequest(request:{echo:1}) {value} 50 | }"; 51 | 52 | var expected = @"{ 53 | testRequest: {value:1} 54 | }"; 55 | 56 | GraphAssert.QuerySuccess(schema, query, expected); 57 | } 58 | 59 | [Fact] 60 | public void WithEnumerableParameterExample_Works() 61 | { 62 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 63 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 64 | 65 | var query = @"{ 66 | testEnumerableRequest(request:[{echo:1}]) {value} 67 | }"; 68 | 69 | var expected = @"{ 70 | testEnumerableRequest: {value:1} 71 | }"; 72 | 73 | GraphAssert.QuerySuccess(schema, query, expected); 74 | } 75 | 76 | [Fact] 77 | public void WithStringParameterExample_Works() 78 | { 79 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 80 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 81 | 82 | var query = @"{ 83 | testRequest(request:{data:""yes""}) {stringValue} 84 | }"; 85 | 86 | var expected = @"{ 87 | testRequest: {stringValue:""yes""} 88 | }"; 89 | 90 | GraphAssert.QuerySuccess(schema, query, expected); 91 | } 92 | 93 | [Fact] 94 | public void WithDictionaryParameterExample_Works() 95 | { 96 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 97 | var schema = schemaGenerator.CreateSchema(typeof(DictionarySchema)); 98 | 99 | var query = @"{ 100 | dictionaryRequest(request:{dictionary:[{key:""name"", value:""bob""}]}) {key,value} 101 | }"; 102 | 103 | var expected = @"{ 104 | dictionaryRequest: [{""key"":""name"",""value"":""bob""}] 105 | }"; 106 | 107 | GraphAssert.QuerySuccess(schema, query, expected); 108 | } 109 | 110 | [Fact] 111 | public void WithStringEscapedParameterExample_Works() 112 | { 113 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 114 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 115 | 116 | //data = y\es m"am 117 | var query = @"{ 118 | testRequest(request:{data:""y\\es m\""am""}) {stringValue} 119 | }"; 120 | 121 | var expected = @"{ 122 | testRequest: {stringValue:""y\\es m\""am""} 123 | }"; 124 | 125 | GraphAssert.QuerySuccess(schema, query, expected); 126 | } 127 | 128 | [Fact] 129 | public void WithComplexParameters_Works() 130 | { 131 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 132 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 133 | 134 | var query = @"{ 135 | testRequest(request:{ 136 | complexRequests:[{ 137 | innerData:""345"" 138 | }] 139 | }) {value} 140 | }"; 141 | 142 | var expected = @"{ 143 | testRequest: {value:5} 144 | }"; 145 | 146 | GraphAssert.QuerySuccess(schema, query, expected); 147 | } 148 | 149 | [Fact] 150 | public void WithComplexParameters_HaveCorrectType() 151 | { 152 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 153 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 154 | 155 | var query = @"{ 156 | __type(name : ""Input_InnerRequest"") { 157 | name 158 | kind 159 | }}"; 160 | 161 | var expected = @"{ 162 | __type: { 163 | name: ""Input_InnerRequest"", 164 | kind: ""INPUT_OBJECT"" 165 | } 166 | }"; 167 | 168 | GraphAssert.QuerySuccess(schema, query, expected); 169 | } 170 | 171 | [Fact] 172 | public void WithEnumerableExample_Works() 173 | { 174 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 175 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 176 | 177 | var query = @"{ 178 | testEnumerable{value} 179 | }"; 180 | 181 | var expected = @"{ 182 | testEnumerable: [{value: 1},{value: 5}] 183 | }"; 184 | 185 | GraphAssert.QuerySuccess(schema, query, expected); 186 | } 187 | 188 | [Fact] 189 | public void WithEnum_Works() 190 | { 191 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 192 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 193 | 194 | var query = @"{ 195 | testRequest {enum} 196 | }"; 197 | 198 | var expected = @"{ 199 | testRequest: {enum:""NEWHOPE""} 200 | }"; 201 | 202 | GraphAssert.QuerySuccess(schema, query, expected); 203 | } 204 | 205 | [Fact] 206 | public void WithDateTimeOffset_Works() 207 | { 208 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 209 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 210 | 211 | var query = @"{ 212 | testRequest {date} 213 | }"; 214 | 215 | var expected = @"{ 216 | testRequest: {date:""1999-01-01T00:00:00-07:00""} 217 | }"; 218 | 219 | GraphAssert.QuerySuccess(schema, query, expected); 220 | } 221 | 222 | [Fact] 223 | public void FieldDescription_Works() 224 | { 225 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 226 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 227 | 228 | var query = @"{ 229 | __schema{ 230 | types{ 231 | name, 232 | fields { 233 | name 234 | description 235 | } 236 | } 237 | } 238 | }"; 239 | 240 | var exec = new DocumentExecuter(new GraphQLDocumentBuilder(), new DocumentValidator(), new ComplexityAnalyzer()); 241 | var result = exec.ExecuteAsync(schema, null, query, null).Result; 242 | 243 | var writer = new DocumentWriter(indent: true); 244 | var writtenResult = writer.Write(result.Data); 245 | 246 | var errors = result.Errors?.FirstOrDefault(); 247 | 248 | Assert.Null(errors?.Message); 249 | Assert.True(writtenResult.Contains("{VerifyComment}")); 250 | } 251 | 252 | [Fact] 253 | public void WithNull_Works() 254 | { 255 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 256 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 257 | 258 | var query = @"{ 259 | testRequest {nullValue} 260 | }"; 261 | 262 | var expected = @"{ 263 | testRequest: {nullValue:null} 264 | }"; 265 | 266 | GraphAssert.QuerySuccess(schema, query, expected); 267 | } 268 | 269 | [Fact] 270 | public void WithDictionary_Works() 271 | { 272 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 273 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 274 | 275 | var query = @"{ 276 | testRequest { 277 | values{ 278 | key 279 | value{ 280 | complicatedResponse{ 281 | echo 282 | } 283 | } 284 | } 285 | } 286 | }"; 287 | 288 | var expected = @"{ 289 | testRequest: { 290 | values: [ 291 | { 292 | key: ""99"", 293 | value: { 294 | complicatedResponse: { 295 | echo: 99 296 | } 297 | } 298 | }, 299 | { 300 | key: ""59"", 301 | value: { 302 | complicatedResponse: { 303 | echo: 59 304 | } 305 | } 306 | }, 307 | { 308 | key: ""null"", 309 | value: null 310 | } 311 | ] 312 | } 313 | }"; 314 | 315 | GraphAssert.QuerySuccess(schema, query, expected); 316 | } 317 | 318 | [Fact] 319 | public void PrimitiveString_Works() 320 | { 321 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 322 | var schema = schemaGenerator.CreateSchema(typeof(PrimitiveSchema)); 323 | 324 | var query = @" 325 | { 326 | testString 327 | } 328 | "; 329 | 330 | var expected = @"{testString:""Hi""}"; 331 | 332 | GraphAssert.QuerySuccess(schema, query, expected); 333 | } 334 | 335 | [Fact] 336 | public void GetNullDecimalAsVariable_IsSafe() 337 | { 338 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 339 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 340 | 341 | var query = @" 342 | query($ts:Decimal!) 343 | { 344 | testRequest(request:{decimal:$ts}){decimalValue} 345 | } 346 | "; 347 | 348 | var variables = @"{ 349 | ts:1.23 350 | }"; 351 | 352 | var expected = @"{ 353 | testRequest: { 354 | decimalValue:1.23 355 | } 356 | }"; 357 | 358 | GraphAssert.QuerySuccess(schema, query, expected, variables); 359 | } 360 | 361 | //todo: 362 | //[Fact] 363 | public void BasicInterfaceExample_Works() 364 | { 365 | var schemaGenerator = new SchemaGenerator(new MockServiceProvider()); 366 | var schema = schemaGenerator.CreateSchema(typeof(EchoSchema)); 367 | 368 | var query = @"{ 369 | testInterface{value} 370 | }"; 371 | 372 | var expected = @"{ 373 | testInterface: {value:8} 374 | }"; 375 | 376 | GraphAssert.QuerySuccess(schema, query, expected); 377 | } 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/Tests/TypeHelperTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using GraphQL.SchemaGenerator.Helpers; 4 | using GraphQL.SchemaGenerator.Tests.Schemas; 5 | using Xunit; 6 | 7 | namespace GraphQL.SchemaGenerator.Tests.Tests 8 | { 9 | public class TypeHelperTests 10 | { 11 | [Fact] 12 | public void GetFullName_With_NestedDictionary_IsSafe() 13 | { 14 | var data = new SchemaResponse() 15 | { 16 | NestDictionary = new Dictionary>() 17 | }; 18 | var type = data.NestDictionary.GetType(); 19 | 20 | var sut = TypeHelper.GetFullName(type); 21 | 22 | var prohibitedCharacters = new List {'~', ' ', ','}; 23 | 24 | Assert.NotNull(sut); 25 | Assert.True(!sut.Any(t=> prohibitedCharacters.Contains(t))); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Attributes/GraphKnownTypeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GraphQL.SchemaGenerator.Schema; 3 | 4 | namespace GraphQL.SchemaGenerator.Attributes 5 | { 6 | [AttributeUsage(AttributeTargets.Interface, AllowMultiple = true, Inherited = true)] 7 | public class GraphKnownTypeAttribute : Attribute, IDomainSchemaTypeMapping 8 | { 9 | public Type DomainType { get; set; } 10 | public Type SchemaType { get; set; } 11 | 12 | public GraphKnownTypeAttribute(Type domainType, Type schemaType) 13 | { 14 | DomainType = domainType; 15 | SchemaType = schemaType; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Attributes/GraphNotRequiredAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GraphQL.SchemaGenerator.Models; 3 | 4 | namespace GraphQL.SchemaGenerator.Attributes 5 | { 6 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field)] 7 | public class GraphNotRequiredAttribute : Attribute 8 | { 9 | public RequiredType Type { get; set; } 10 | 11 | public GraphNotRequiredAttribute(RequiredType type = RequiredType.NotRequired) 12 | { 13 | Type = type; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Attributes/GraphRouteAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GraphQL.SchemaGenerator.Attributes 4 | { 5 | /// 6 | /// Attribute provided on an api endpoint can be converted into graph ql. 7 | /// 8 | [AttributeUsage(AttributeTargets.Method)] 9 | public class GraphRouteAttribute : Attribute 10 | { 11 | /// 12 | /// 13 | /// 14 | public string Name { get; set; } 15 | 16 | /// 17 | /// 18 | /// 19 | public bool IsMutation { get; set; } 20 | 21 | 22 | /// 23 | /// 24 | /// 25 | public GraphRouteAttribute() 26 | { 27 | 28 | } 29 | 30 | /// 31 | /// 32 | /// 33 | /// 34 | /// 35 | public GraphRouteAttribute(string name = null, bool isMutation = false) 36 | { 37 | Name = name; 38 | IsMutation = isMutation; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Attributes/GraphTypeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GraphQL.SchemaGenerator.Attributes 4 | { 5 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field)] 6 | public class GraphTypeAttribute : Attribute 7 | { 8 | public Type Type { get; set; } 9 | 10 | public bool Exclude { get; set; } 11 | 12 | public GraphTypeAttribute(Type type = null, bool exclude = false) 13 | { 14 | Type = type; 15 | Exclude = exclude; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Attributes/NotNullAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GraphQL.SchemaGenerator.Attributes 4 | { 5 | public class NotNullAttribute : Attribute 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #Change Log 2 | 3 | ## [0.1.0.6] - 10/17/2017 4 | 5 | - Switched to a fork of graph ql dot net for significant performance improvements: https://github.com/holm0563/graphql-dotnet/tree/performance2withrollback 6 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/DeepDictionaryRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Dynamic; 4 | using System.Linq; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Linq; 7 | 8 | namespace GraphQL.SchemaGenerator 9 | { 10 | public class DeepDictionaryRequest : JsonConverter 11 | { 12 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 13 | { 14 | WriteValue(writer, value); 15 | } 16 | 17 | private void WriteValue(JsonWriter writer, object value) 18 | { 19 | var t = JToken.FromObject(value); 20 | switch (t.Type) 21 | { 22 | case JTokenType.Object: 23 | WriteObject(writer, value); 24 | break; 25 | case JTokenType.Array: 26 | WriteArray(writer, value); 27 | break; 28 | default: 29 | writer.WriteValue(value); 30 | break; 31 | } 32 | } 33 | 34 | private void WriteObject(JsonWriter writer, object value) 35 | { 36 | writer.WriteStartObject(); 37 | var obj = value as IDictionary; 38 | foreach (var kvp in obj) 39 | { 40 | writer.WritePropertyName(kvp.Key); 41 | WriteValue(writer, kvp.Value); 42 | } 43 | writer.WriteEndObject(); 44 | } 45 | 46 | private void WriteArray(JsonWriter writer, object value) 47 | { 48 | var array = value as IEnumerable; 49 | var didConvert = false; 50 | foreach (var o in array) 51 | { 52 | var d = o as IDictionary; 53 | if (d != null && d.Count == 2) 54 | { 55 | if (!didConvert) 56 | { 57 | writer.WriteStartObject(); 58 | } 59 | 60 | writer.WritePropertyName(d.First().Value as string); 61 | WriteValue(writer, d.Last().Value); 62 | didConvert = true; 63 | } 64 | } 65 | 66 | if (didConvert) 67 | { 68 | writer.WriteEndObject(); 69 | return; 70 | } 71 | 72 | writer.WriteStartArray(); 73 | 74 | foreach (var o in array) 75 | WriteValue(writer, o); 76 | 77 | writer.WriteEndArray(); 78 | } 79 | 80 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, 81 | JsonSerializer serializer) 82 | { 83 | return ReadValue(reader); 84 | } 85 | 86 | private object ReadValue(JsonReader reader) 87 | { 88 | while (reader.TokenType == JsonToken.Comment) 89 | if (!reader.Read()) 90 | throw new JsonSerializationException( 91 | "Unexpected Token when converting IDictionary"); 92 | 93 | switch (reader.TokenType) 94 | { 95 | case JsonToken.StartObject: 96 | return ReadObject(reader); 97 | case JsonToken.StartArray: 98 | return ReadArray(reader); 99 | case JsonToken.Integer: 100 | case JsonToken.Float: 101 | case JsonToken.String: 102 | case JsonToken.Boolean: 103 | case JsonToken.Undefined: 104 | case JsonToken.Null: 105 | case JsonToken.Date: 106 | case JsonToken.Bytes: 107 | return reader.Value; 108 | default: 109 | throw new JsonSerializationException 110 | (string.Format("Unexpected token when converting IDictionary: {0}", 111 | reader.TokenType)); 112 | } 113 | } 114 | 115 | private object ReadArray(JsonReader reader) 116 | { 117 | IList list = new List(); 118 | 119 | while (reader.Read()) 120 | switch (reader.TokenType) 121 | { 122 | case JsonToken.Comment: 123 | break; 124 | default: 125 | var v = ReadValue(reader); 126 | 127 | list.Add(v); 128 | break; 129 | case JsonToken.EndArray: 130 | return list; 131 | } 132 | 133 | throw new JsonSerializationException("Unexpected end when reading IDictionary"); 134 | } 135 | 136 | private object ReadObject(JsonReader reader) 137 | { 138 | var obj = new Dictionary(); 139 | 140 | while (reader.Read()) 141 | switch (reader.TokenType) 142 | { 143 | case JsonToken.PropertyName: 144 | var propertyName = reader.Value.ToString(); 145 | 146 | if (!reader.Read()) 147 | throw new JsonSerializationException( 148 | "Unexpected end when reading IDictionary"); 149 | 150 | var v = ReadValue(reader); 151 | 152 | obj[propertyName] = v; 153 | break; 154 | case JsonToken.Comment: 155 | break; 156 | case JsonToken.EndObject: 157 | return obj; 158 | } 159 | 160 | throw new JsonSerializationException("Unexpected end when reading IDictionary"); 161 | } 162 | 163 | public override bool CanConvert(Type objectType) 164 | { 165 | return typeof(IDictionary).IsAssignableFrom(objectType); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/DocumentOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using GraphQL.Execution; 7 | using GraphQL.Language.AST; 8 | using GraphQL.Types; 9 | using GraphQL.Validation; 10 | using GraphQL.Validation.Complexity; 11 | 12 | namespace GraphQL.SchemaGenerator 13 | { 14 | /// 15 | /// Handles multiple operations. 16 | /// 17 | public static class DocumentOperations 18 | { 19 | private static ComplexityAnalyzer ComplexityAnalyzerInstance { get; } = new ComplexityAnalyzer(); 20 | private static DocumentValidator DocumentValidator { get; } = new DocumentValidator(); 21 | 22 | /// 23 | /// Execute all operations async. 24 | /// 25 | /// Aggregated results. 26 | public static async Task ExecuteOperationsAsync( 27 | ISchema schema, 28 | object root, 29 | string query, 30 | Inputs inputs = null, 31 | CancellationToken cancellationToken = default(CancellationToken), 32 | IEnumerable rules = null, 33 | bool validate = true, 34 | IDocumentBuilder documentBuilder = null, 35 | int? maxOperationNodes = null, 36 | int? maxTasksAllowed = null, 37 | IList blackListedOperations = null 38 | ) 39 | { 40 | var savedDocument = new SavedDocumentBuilder(query, documentBuilder); 41 | 42 | if (savedDocument.OperationNodes > maxOperationNodes) 43 | { 44 | throw new InvalidOperationException($"Graph query contains more than the allowed operation limit ({maxOperationNodes}) for one request."); 45 | } 46 | 47 | if (blackListedOperations != null && blackListedOperations.Any()) 48 | { 49 | var selections = savedDocument.Document.Operations.SelectMany(i => i.SelectionSet.Selections); 50 | foreach (var selection in selections) 51 | { 52 | 53 | if (selection != null) 54 | { 55 | var name = (selection as Field)?.Name; 56 | if (blackListedOperations.Contains(name)) 57 | { 58 | throw new InvalidOperationException($"Graph query contains a restricted operation '{name}'."); 59 | } 60 | } 61 | } 62 | } 63 | 64 | if (savedDocument.Document.Operations == null || savedDocument.Document.Operations.Count() <= 1) 65 | { 66 | //run the typical way. 67 | var defaultBuilder = new DocumentExecuter(savedDocument, DocumentValidator, ComplexityAnalyzerInstance, maxTasksAllowed); 68 | 69 | return 70 | await 71 | defaultBuilder.ExecuteAsync( 72 | new ExecutionOptions 73 | { 74 | Schema = schema, 75 | Root = root, 76 | Query = query, 77 | Inputs = inputs, 78 | CancellationToken = cancellationToken, 79 | ValidationRules = rules, 80 | EnableDocumentValidation = validate, 81 | EnableMetrics = false, 82 | SetFieldMiddleware = false 83 | }); 84 | } 85 | 86 | var result = new ExecutionResult(); 87 | var nonValidatedExecutionar = 88 | new DocumentExecuter(savedDocument, DocumentValidator, ComplexityAnalyzerInstance, maxTasksAllowed); 89 | var aggregateData = new Dictionary(); 90 | 91 | try 92 | { 93 | if (validate) 94 | { 95 | var validationResult = DocumentValidator.Validate(query, schema, savedDocument.Document, rules); 96 | 97 | if (!validationResult.IsValid) 98 | { 99 | result.Data = aggregateData; 100 | result.Errors = validationResult.Errors; 101 | 102 | return result; 103 | } 104 | } 105 | 106 | foreach (var operation in savedDocument.Document.Operations) 107 | { 108 | var opResult = 109 | await nonValidatedExecutionar.ExecuteAsync(new ExecutionOptions 110 | { 111 | Schema = schema, 112 | Root = root, 113 | Query = query, 114 | Inputs = inputs, 115 | CancellationToken = cancellationToken, 116 | ValidationRules = rules, 117 | OperationName = operation.Name, 118 | EnableDocumentValidation = false, 119 | EnableMetrics = false, 120 | SetFieldMiddleware = false 121 | }); 122 | 123 | if (opResult.Errors != null && opResult.Errors.Any()) 124 | return opResult; 125 | 126 | aggregateData.Add(operation.Name, opResult.Data); 127 | } 128 | } 129 | catch (Exception exc) 130 | { 131 | if (result.Errors == null) 132 | result.Errors = new ExecutionErrors(); 133 | 134 | result.Data = null; 135 | result.Errors.Add(new ExecutionError(exc.Message, exc)); 136 | return result; 137 | } 138 | 139 | result.Data = aggregateData; 140 | 141 | return result; 142 | } 143 | } 144 | 145 | 146 | /// 147 | /// Caches a saved document for reuse. 148 | /// 149 | public class SavedDocumentBuilder : IDocumentBuilder 150 | { 151 | public SavedDocumentBuilder(string query, IDocumentBuilder builder) 152 | { 153 | builder = builder ?? new GraphQLDocumentBuilder(); 154 | 155 | Document = builder.Build(query); 156 | } 157 | 158 | /// 159 | /// The saved document. 160 | /// 161 | public Document Document { get; } 162 | 163 | public Document Build(string body) 164 | { 165 | return Document; 166 | } 167 | 168 | public int OperationNodes 169 | { 170 | get 171 | { 172 | if (Document?.Operations == null) 173 | { 174 | return 0; 175 | } 176 | 177 | var count = Document.Operations.Sum(t => t.SelectionSet.Selections.Count()); 178 | 179 | return count; 180 | } 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Extensions/AssignableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace GraphQL.SchemaGenerator.Extensions 5 | { 6 | public static class AssignableExtensions 7 | { 8 | /// 9 | /// Determines whether the is assignable from 10 | /// taking into account generic definitions 11 | /// 12 | public static bool IsAssignableToGenericType(this Type givenType, Type genericType) 13 | { 14 | if (givenType == null || genericType == null) 15 | { 16 | return false; 17 | } 18 | 19 | return givenType == genericType 20 | || givenType.mapsToGenericTypeDefinition(genericType) 21 | || givenType.hasInterfaceThatMapsToGenericTypeDefinition(genericType) 22 | || givenType.BaseType.IsAssignableToGenericType(genericType); 23 | } 24 | 25 | private static bool hasInterfaceThatMapsToGenericTypeDefinition(this Type givenType, Type genericType) 26 | { 27 | return Enumerable.Any(givenType 28 | .GetInterfaces() 29 | .Where(it => it.IsGenericType), it => it.GetGenericTypeDefinition() == genericType); 30 | } 31 | 32 | private static bool mapsToGenericTypeDefinition(this Type givenType, Type genericType) 33 | { 34 | return genericType.IsGenericTypeDefinition 35 | && givenType.IsGenericType 36 | && givenType.GetGenericTypeDefinition() == genericType; 37 | } 38 | 39 | /// 40 | /// Determines whether the is assignable from 41 | /// taking into account generic definitions 42 | /// 43 | public static Type GetGenericType(this Type givenType, Type genericType) 44 | { 45 | if (givenType == null || genericType == null) 46 | { 47 | return null; 48 | } 49 | 50 | if (givenType == genericType) 51 | { 52 | return givenType; 53 | } 54 | else 55 | { 56 | var resultType = givenType.getInterfaceThatMapsToGenericTypeDefinition(genericType); 57 | if (resultType == null) 58 | { 59 | resultType = givenType.BaseType.GetGenericType(genericType); 60 | } 61 | 62 | return resultType; 63 | } 64 | } 65 | 66 | private static Type getInterfaceThatMapsToGenericTypeDefinition(this Type givenType, Type genericType) 67 | { 68 | return givenType 69 | .GetInterfaces() 70 | .Where(it => it.IsGenericType) 71 | .FirstOrDefault(it => it.GetGenericTypeDefinition() == genericType); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Extensions/ResolveFieldContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using GraphQL.SchemaGenerator.Models; 4 | using GraphQL.Types; 5 | using Newtonsoft.Json; 6 | 7 | namespace GraphQL.SchemaGenerator.Extensions 8 | { 9 | /// 10 | /// Extensions for resolve field context. 11 | /// 12 | internal static class ResolveFieldContextExtensions 13 | { 14 | /// 15 | /// Generate the parameters for the field. 16 | /// 17 | /// Extension. 18 | /// Field information. 19 | /// 20 | public static object[] Parameters(this ResolveFieldContext type, FieldInformation field) 21 | { 22 | if (field == null) 23 | { 24 | return null; 25 | } 26 | 27 | var routeArguments = new List(); 28 | foreach (var parameter in field.Method.GetParameters()) 29 | { 30 | if (!type.Arguments.TryGetValue(parameter.Name, out var arg)) 31 | { 32 | routeArguments.Add(null); 33 | continue; 34 | } 35 | 36 | if (arg == null) 37 | { 38 | routeArguments.Add(null); 39 | continue; 40 | } 41 | 42 | if (typeof(IDictionary).IsAssignableFrom(arg.GetType()) || 43 | typeof(IEnumerable).IsAssignableFrom(arg.GetType()) 44 | ) 45 | { 46 | try 47 | { 48 | var json = JsonConvert.SerializeObject(arg); 49 | arg = JsonConvert.DeserializeObject(json, parameter.ParameterType); 50 | } 51 | catch 52 | { 53 | var json = JsonConvert.SerializeObject(arg, new DeepDictionaryRequest()); 54 | arg = JsonConvert.DeserializeObject(json, parameter.ParameterType); 55 | } 56 | } 57 | else if (parameter.ParameterType == typeof(char)) 58 | { 59 | arg = arg.ToString()[0]; 60 | } 61 | 62 | routeArguments.Add(arg); 63 | } 64 | 65 | return routeArguments.Any() ? routeArguments.ToArray() : null; 66 | } 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Runtime.Serialization; 5 | using GraphQL.SchemaGenerator.Attributes; 6 | 7 | namespace GraphQL.SchemaGenerator.Extensions 8 | { 9 | //todo: allow easier customizations of this fuctionality 10 | public static class TypeExtensions 11 | { 12 | public static bool ShouldIncludeInGraph(this Type type) 13 | { 14 | var types = type.GetCustomAttributes(typeof(GraphTypeAttribute), false); 15 | var dataContracts = type.GetCustomAttributes(typeof(DataContractAttribute), false); 16 | 17 | return types.Any() || dataContracts.Any(); 18 | } 19 | 20 | public static bool ShouldIncludeMemberInGraph(this FieldInfo field) 21 | { 22 | return ShouldIncludeMemberInGraph(field.GetCustomAttributes(false)); 23 | } 24 | 25 | public static bool ShouldIncludeMemberInGraph(this PropertyInfo property) 26 | { 27 | return ShouldIncludeMemberInGraph(property.GetCustomAttributes(false)); 28 | } 29 | 30 | public static bool ShouldIncludeMemberInGraph(object[] attributes) 31 | { 32 | if (Enumerable.Any(attributes.Where(t => t is GraphTypeAttribute), i=> ((GraphTypeAttribute)i).Exclude)) 33 | { 34 | return false; 35 | } 36 | 37 | var exclude = Enumerable.Any(attributes, a => a.GetType().Name.StartsWith("JsonIgnore", StringComparison.OrdinalIgnoreCase)); 38 | 39 | return !exclude; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/GraphQl.SchemaGenerator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | GraphQL.SchemaGenerator 5 | GraphQL.SchemaGenerator 6 | Copyright © 2020 7 | 0.2.0.5 8 | 0.2.0.5 9 | 0.2.0.5 10 | bin\$(Configuration)\ 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug\ 17 | DEBUG;TRACE 18 | prompt 19 | 4 20 | 21 | 22 | pdbonly 23 | true 24 | bin\Release\ 25 | TRACE 26 | prompt 27 | 4 28 | 29 | 30 | true 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ..\..\lib\netstandard2.0\GraphQL.dll 55 | 56 | 57 | true 58 | true 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/GraphTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using GraphQL.SchemaGenerator.Extensions; 4 | using GraphQL.SchemaGenerator.Models; 5 | using GraphQL.SchemaGenerator.Types; 6 | using GraphQL.SchemaGenerator.Wrappers; 7 | using GraphQL.Types; 8 | 9 | namespace GraphQL.SchemaGenerator 10 | { 11 | public class GraphTypeConverter 12 | { 13 | public static Type ConvertTypeToGraphType(Type propertyType, RequiredType requiredType = RequiredType.Default, bool isInputType = false) 14 | { 15 | if (typeof(GraphType).IsAssignableFrom(propertyType)) 16 | { 17 | return propertyType; 18 | } 19 | 20 | if (requiredType == RequiredType.Default && propertyType.IsValueType) 21 | { 22 | if (propertyType.IsAssignableToGenericType(typeof(Nullable<>))) 23 | { 24 | requiredType = RequiredType.NotRequired; 25 | propertyType = propertyType.GetGenericArguments()[0]; 26 | } 27 | else 28 | { 29 | requiredType = RequiredType.Required; 30 | } 31 | } 32 | 33 | var graphType = BaseGraphType(propertyType, isInputType); 34 | 35 | if (propertyType == typeof(string) && isInputType) 36 | { 37 | return graphType; 38 | } 39 | 40 | if (graphType.IsAssignableToGenericType(typeof(InputObjectGraphTypeWrapper<>))) 41 | { 42 | return graphType; 43 | } 44 | 45 | if (graphType != null && requiredType == RequiredType.Required) 46 | { 47 | if (!typeof(NonNullGraphType).IsAssignableFrom(graphType)) 48 | { 49 | return typeof(NonNullGraphType<>).MakeGenericType(graphType); 50 | } 51 | } 52 | 53 | return graphType; 54 | } 55 | 56 | /// 57 | /// Get the base graph type not worrying about null. 58 | /// 59 | /// Property type. 60 | /// Is an input type. 61 | /// Type 62 | /// Cannot support IEnumerable when wrapping an object with GraphQL 63 | private static Type BaseGraphType(Type propertyType, bool isInputType = false) 64 | { 65 | if (propertyType == typeof(EnumValues)) 66 | { 67 | return typeof(EnumerationGraphType); 68 | } 69 | 70 | if (propertyType.IsEnum) 71 | { 72 | return typeof(EnumerationGraphTypeWrapper<>).MakeGenericType(propertyType); 73 | } 74 | 75 | if (propertyType == typeof(void)) 76 | { 77 | return null; 78 | } 79 | 80 | if (propertyType == typeof(string) || 81 | propertyType == typeof(char)) 82 | { 83 | return typeof(StringGraphType); 84 | } 85 | 86 | if (propertyType == typeof(Guid)) 87 | { 88 | return typeof(StringGraphType); 89 | } 90 | 91 | if (IsIntegerType(propertyType)) 92 | { 93 | return typeof(IntGraphType); 94 | } 95 | 96 | if (IsDecimalType(propertyType)) 97 | { 98 | return typeof(DecimalGraphType); 99 | } 100 | 101 | if (propertyType == typeof(bool)) 102 | { 103 | return typeof(BooleanGraphType); 104 | } 105 | 106 | if (propertyType == typeof(DateTime) 107 | || propertyType == typeof(DateTimeOffset)) 108 | { 109 | return typeof(OriginalDateGraphType); 110 | } 111 | 112 | if (propertyType == typeof(DateTime?) 113 | || propertyType == typeof(DateTimeOffset?)) 114 | { 115 | return typeof(OriginalDateGraphType); 116 | } 117 | 118 | if (propertyType == typeof(TimeSpan)) 119 | { 120 | return typeof(TimeSpanGraphType); 121 | } 122 | 123 | if (propertyType == typeof(byte[])) 124 | { 125 | return typeof(ByteArrayGraphType); 126 | } 127 | 128 | if (propertyType.IsAssignableToGenericType(typeof(IDictionary<,>))) 129 | { 130 | var genericArgs = propertyType.GetGenericArguments(); 131 | var keyGraphType = ConvertTypeToGraphType(genericArgs[0], isInputType: isInputType); 132 | var valueGraphType = ConvertTypeToGraphType(genericArgs[1], isInputType: isInputType); 133 | var keyValuePairGraphType = typeof(KeyValuePairGraphType<,>).MakeGenericType( 134 | keyGraphType, valueGraphType); 135 | 136 | if (isInputType) 137 | { 138 | var inputPairGraphType = typeof(KeyValuePairInputGraphType<,>).MakeGenericType( 139 | keyGraphType, valueGraphType); 140 | 141 | return typeof(ListGraphType<>).MakeGenericType(inputPairGraphType); 142 | } 143 | 144 | return typeof(ListGraphType<>).MakeGenericType(keyValuePairGraphType); 145 | } 146 | 147 | if (propertyType.IsArray) 148 | { 149 | var itemType = propertyType.GetElementType(); 150 | var itemGraphType = ConvertTypeToGraphType(itemType, isInputType: isInputType); 151 | if (itemGraphType != null) 152 | { 153 | return typeof(ListGraphType<>).MakeGenericType(itemGraphType); 154 | } 155 | } 156 | 157 | if (propertyType.IsAssignableToGenericType(typeof(IEnumerable<>))) 158 | { 159 | var itemType = propertyType.GetGenericArguments()[0]; 160 | var itemGraphType = ConvertTypeToGraphType(itemType, isInputType: isInputType); 161 | if (itemGraphType != null) 162 | { 163 | return typeof(ListGraphType<>).MakeGenericType(itemGraphType); 164 | } 165 | } 166 | 167 | if (propertyType.IsInterface || propertyType.IsAbstract) 168 | { 169 | return typeof(InterfaceGraphTypeWrapper<>).MakeGenericType(propertyType); 170 | } 171 | 172 | if (isInputType) 173 | { 174 | return typeof(InputObjectGraphTypeWrapper<>).MakeGenericType(propertyType); 175 | } 176 | 177 | return typeof(ObjectGraphTypeWrapper<>).MakeGenericType(propertyType); 178 | } 179 | 180 | private static bool IsDecimalType(Type type) 181 | { 182 | var typeCode = Type.GetTypeCode(type); 183 | 184 | switch (typeCode) 185 | { 186 | case TypeCode.Single: 187 | case TypeCode.Double: 188 | case TypeCode.Decimal: 189 | return true; 190 | default: 191 | return false; 192 | } 193 | } 194 | 195 | private static bool IsIntegerType(Type type) 196 | { 197 | var typeCode = Type.GetTypeCode(type); 198 | 199 | switch (typeCode) 200 | { 201 | case TypeCode.Int16: 202 | case TypeCode.Int32: 203 | case TypeCode.Int64: 204 | case TypeCode.SByte: 205 | case TypeCode.Byte: 206 | case TypeCode.Single: 207 | case TypeCode.UInt16: 208 | case TypeCode.UInt32: 209 | case TypeCode.UInt64: 210 | return true; 211 | default: 212 | return false; 213 | } 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/GraphTypeResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GraphQL.SchemaGenerator.Wrappers; 3 | using GraphQL.Types; 4 | 5 | namespace GraphQL.SchemaGenerator 6 | { 7 | public class GraphTypeResolver : IGraphTypeResolver 8 | { 9 | /// 10 | /// Resolve a type into a graph type. 11 | /// 12 | /// 13 | /// 14 | public GraphType ResolveType(Type type) 15 | { 16 | if (type.IsInterface || type.IsAbstract) 17 | { 18 | return null; 19 | } 20 | 21 | if (type.IsGenericType) 22 | { 23 | if (type.GetGenericTypeDefinition() == typeof(InterfaceGraphTypeWrapper<>)) 24 | { 25 | return Activator.CreateInstance(type) as GraphType; 26 | } 27 | 28 | if (type.GetGenericTypeDefinition() == typeof(InputObjectGraphTypeWrapper<>)) 29 | { 30 | return Activator.CreateInstance(type) as GraphType; 31 | } 32 | 33 | if (type.GetGenericTypeDefinition() == typeof(KeyValuePairGraphType<,>)) 34 | { 35 | return Activator.CreateInstance(type) as GraphType; 36 | } 37 | 38 | if (type.GetGenericTypeDefinition() == typeof(EnumerationGraphTypeWrapper<>)) 39 | { 40 | return Activator.CreateInstance(type) as GraphType; 41 | } 42 | 43 | if (type.GetGenericTypeDefinition() == typeof(ObjectGraphTypeWrapper<>)) 44 | { 45 | return Activator.CreateInstance(type) as GraphType; 46 | } 47 | } 48 | 49 | if (type.IsAssignableFrom(typeof(GraphType)) || type.IsSubclassOf(typeof(GraphType))) 50 | { 51 | return Activator.CreateInstance(type) as GraphType; 52 | } 53 | 54 | var graphType = GraphTypeConverter.ConvertTypeToGraphType(type); 55 | 56 | if (graphType == null) 57 | { 58 | return null; 59 | } 60 | 61 | var generic = typeof(ObjectGraphTypeWrapper<>).MakeGenericType(graphType); 62 | 63 | return Activator.CreateInstance(generic) as GraphType; 64 | } 65 | 66 | public T Resolve() 67 | { 68 | return (T) Resolve(typeof(T)); 69 | } 70 | 71 | public object Resolve(Type type) 72 | { 73 | return ResolveType(type); 74 | } 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Helpers/StringHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace GraphQL.SchemaGenerator.Helpers 6 | { 7 | public static class StringHelper 8 | { 9 | /// 10 | /// Convert to camel case. ExampleWord -> exampleWord. 11 | /// 12 | public static string ConvertToCamelCase(string name) 13 | { 14 | if (name == null || name.Length <= 1) 15 | { 16 | return name; 17 | } 18 | 19 | return Char.ToLower(name[0]) + name.Substring(1); 20 | } 21 | 22 | /// 23 | /// Remove special characters. Example@!#$23 -> Example23 24 | /// 25 | public static string SafeString(string name) 26 | { 27 | if (name == null) 28 | { 29 | return null; 30 | } 31 | 32 | return Regex.Replace(name, "[^0-9a-zA-Z]+", ""); 33 | } 34 | 35 | /// 36 | /// Get the graph name for this string. 37 | /// 38 | /// Camel case, safe string. 39 | public static string GraphName(string name) 40 | { 41 | return SafeString(ConvertToCamelCase(name)); 42 | } 43 | 44 | /// 45 | /// Get the real type name. 46 | /// 47 | /// 48 | /// 49 | public static string GetRealTypeName(Type t) 50 | { 51 | if (!t.IsGenericType) 52 | return SafeString(t.Name); 53 | 54 | StringBuilder sb = new StringBuilder(); 55 | sb.Append(SafeString(t.Name.Substring(0, t.Name.IndexOf('`')))); 56 | sb.Append("__"); 57 | bool appendComma = false; 58 | foreach (Type arg in t.GetGenericArguments()) 59 | { 60 | if (appendComma) sb.Append('_'); 61 | sb.Append(GetRealTypeName(arg)); 62 | appendComma = true; 63 | } 64 | return sb.ToString(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Helpers/TypeHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using GraphQL.SchemaGenerator.Attributes; 9 | using GraphQL.SchemaGenerator.Extensions; 10 | using GraphQL.SchemaGenerator.Models; 11 | 12 | namespace GraphQL.SchemaGenerator.Helpers 13 | { 14 | public static class TypeHelper 15 | { 16 | public static string GetFullName(Type t) 17 | { 18 | if (!t.IsGenericType) 19 | { 20 | return StringHelper.SafeString(t.Name); 21 | } 22 | 23 | var sb = new StringBuilder(); 24 | 25 | string typePrefix = StringHelper.SafeString(t.Name.Substring(0, t.Name.LastIndexOf("`"))); 26 | if (!typePrefix.EndsWith("Wrapper")) 27 | { 28 | sb.Append(typePrefix); 29 | } 30 | sb.Append(t.GetGenericArguments().Aggregate("_", 31 | (string aggregate, Type type) => 32 | { 33 | return StringHelper.SafeString(aggregate) + (aggregate == "_" ? "" : "_") + GetFullName(type); 34 | } 35 | )); 36 | 37 | return sb.ToString(); 38 | } 39 | 40 | public static IEnumerable GetGraphKnownTypes(Type graphType) 41 | { 42 | if (graphType != null) 43 | { 44 | foreach (GraphKnownTypeAttribute attr in graphType.GetCustomAttributes(typeof(GraphKnownTypeAttribute), true)) 45 | { 46 | yield return attr; 47 | } 48 | } 49 | } 50 | 51 | public static Type GetGraphType(FieldInfo field) 52 | { 53 | var graphTypeAttribute = field.GetCustomAttribute(); 54 | return getGraphType(graphTypeAttribute, field.FieldType); 55 | } 56 | 57 | public static Type GetGraphType(PropertyInfo property) 58 | { 59 | var graphTypeAttribute = property.GetCustomAttribute(); 60 | return getGraphType(graphTypeAttribute, property.PropertyType); 61 | } 62 | 63 | public static Type GetGraphType(MethodInfo method) 64 | { 65 | var graphTypeAttribute = method.GetCustomAttribute(); 66 | return getGraphType(graphTypeAttribute, method.ReturnType); 67 | } 68 | 69 | public static Type GetGraphType(Type type) 70 | { 71 | var graphTypeAttribute = type.GetCustomAttribute(); 72 | if (graphTypeAttribute != null) 73 | { 74 | return graphTypeAttribute.Type ?? type; 75 | } 76 | 77 | return null; 78 | } 79 | 80 | public static Type GetGraphType(ParameterInfo parameter) 81 | { 82 | return GetGraphType(parameter.ParameterType); 83 | } 84 | 85 | private static Type getGraphType(GraphTypeAttribute graphTypeAttribute, Type type) 86 | { 87 | if (graphTypeAttribute == null) 88 | { 89 | return type != null ? GetGraphType(type) : null; 90 | } 91 | 92 | return graphTypeAttribute.Type; 93 | } 94 | 95 | public static string GetDescription(PropertyInfo property) 96 | { 97 | return getDescriptionValue(property.GetCustomAttribute()); 98 | } 99 | 100 | public static string GetDescription(MethodInfo method) 101 | { 102 | return getDescriptionValue(method.GetCustomAttribute()); 103 | } 104 | 105 | public static string GetDescription(ParameterInfo parameter) 106 | { 107 | return getDescriptionValue(parameter.GetCustomAttribute()); 108 | } 109 | 110 | private static string getDescriptionValue(DescriptionAttribute desc) 111 | { 112 | if (desc != null) 113 | { 114 | return desc.Description; 115 | } 116 | 117 | return String.Empty; 118 | } 119 | 120 | public static string GetDeprecationReason(PropertyInfo property) 121 | { 122 | var obsoleteAttr = property.GetCustomAttribute(); 123 | return GetObsoleteMessage(obsoleteAttr); 124 | } 125 | 126 | public static string GetDeprecationReason(FieldInfo field) 127 | { 128 | var obsoleteAttr = field.GetCustomAttribute(); 129 | return GetObsoleteMessage(obsoleteAttr); 130 | } 131 | 132 | public static string GetDeprecationReason(MethodInfo method) 133 | { 134 | var obsoleteAttr = method.GetCustomAttribute(); 135 | return GetObsoleteMessage(obsoleteAttr); 136 | } 137 | 138 | private static string GetObsoleteMessage(ObsoleteAttribute obsoleteAttr) 139 | { 140 | return obsoleteAttr?.Message; 141 | } 142 | 143 | public static object GetDefaultValue(PropertyInfo property) 144 | { 145 | var defaultValueAttr = property.GetCustomAttribute(); 146 | return getDefaultValue(defaultValueAttr); 147 | } 148 | 149 | public static object GetDefaultValue(ParameterInfo parameter) 150 | { 151 | var defaultValueAttr = parameter.GetCustomAttribute(); 152 | return getDefaultValue(defaultValueAttr); 153 | } 154 | 155 | private static object getDefaultValue(DefaultValueAttribute defaultValueAttr) 156 | { 157 | if (defaultValueAttr != null) 158 | { 159 | return defaultValueAttr.Value; 160 | } 161 | 162 | return null; 163 | } 164 | 165 | public static RequiredType IsNotNull(MethodInfo method) 166 | { 167 | var explicitSetting = method.GetCustomAttribute(); 168 | if (explicitSetting != null) 169 | { 170 | return explicitSetting.Type; 171 | } 172 | 173 | if (method.GetCustomAttribute() != null || 174 | method.GetCustomAttribute() != null) 175 | { 176 | return RequiredType.Required; 177 | } 178 | 179 | return RequiredType.Default; 180 | } 181 | 182 | public static RequiredType IsNotNull(FieldInfo field) 183 | { 184 | var explicitSetting = field.GetCustomAttribute(); 185 | if (explicitSetting != null) 186 | { 187 | return explicitSetting.Type; 188 | } 189 | 190 | if (field.GetCustomAttribute() != null || 191 | (field.GetCustomAttribute() != null && 192 | ShouldBeNotNullWithRequiredAttribute(field.FieldType))) 193 | { 194 | return RequiredType.Required; 195 | } 196 | 197 | return RequiredType.Default; 198 | } 199 | 200 | public static RequiredType IsNotNull(PropertyInfo property) 201 | { 202 | var explicitSetting = property.GetCustomAttribute(); 203 | if (explicitSetting != null) 204 | { 205 | return explicitSetting.Type; 206 | } 207 | 208 | if (property.GetCustomAttribute() != null || 209 | (property.GetCustomAttribute() != null && 210 | ShouldBeNotNullWithRequiredAttribute(property.PropertyType))) 211 | { 212 | return RequiredType.Required; 213 | } 214 | 215 | return RequiredType.Default; 216 | } 217 | 218 | public static bool ShouldBeNotNullWithRequiredAttribute(Type type) 219 | { 220 | if (type.IsAssignableToGenericType(typeof(Nullable<>))) 221 | { 222 | return false; 223 | } 224 | 225 | if (type.IsAssignableToGenericType(typeof(IDictionary<,>))) 226 | { 227 | return false; 228 | } 229 | 230 | if (type.IsAssignableToGenericType(typeof(IEnumerable<>))) 231 | { 232 | return false; 233 | } 234 | 235 | return true; 236 | } 237 | 238 | public static RequiredType IsNotNull(ParameterInfo parameter) 239 | { 240 | var explicitSetting = parameter.GetCustomAttribute(); 241 | if (explicitSetting != null) 242 | { 243 | return explicitSetting.Type; 244 | } 245 | 246 | if (parameter.GetCustomAttribute() != null || 247 | (parameter.GetCustomAttribute() != null && 248 | !parameter.ParameterType.IsAssignableToGenericType(typeof(Nullable<>)))) 249 | { 250 | return RequiredType.Required; 251 | } 252 | 253 | return RequiredType.Default; 254 | } 255 | 256 | public static string GetDisplayName(Type type) 257 | { 258 | var displayNameAttr = type.GetCustomAttribute(); 259 | if (displayNameAttr != null) 260 | { 261 | return StringHelper.SafeString(displayNameAttr.DisplayName); 262 | } 263 | 264 | return StringHelper.GetRealTypeName(type); 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/IGraphTypeResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GraphQL.Types; 3 | 4 | namespace GraphQL.SchemaGenerator 5 | { 6 | /// 7 | /// Converts an unknown type to a graph type. 8 | /// 9 | public interface IGraphTypeResolver: IDependencyResolver 10 | { 11 | GraphType ResolveType(Type type); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Models/FieldDefinition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GraphQL.Types; 3 | 4 | namespace GraphQL.SchemaGenerator.Models 5 | { 6 | /// 7 | /// Field definition needed for a schema. 8 | /// 9 | public class FieldDefinition 10 | { 11 | /// 12 | /// Type of response. 13 | /// 14 | public FieldInformation Field { get; } 15 | 16 | /// 17 | /// Resolve function to get the object. 18 | /// 19 | public Func, object> Resolve { get; } 20 | 21 | public FieldDefinition(FieldInformation field, Func, object> resolve) 22 | { 23 | Field = field; 24 | Resolve = resolve; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Models/FieldInformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using GraphQL.Types; 4 | 5 | namespace GraphQL.SchemaGenerator.Models 6 | { 7 | /// 8 | /// Field definition needed for a schema. 9 | /// 10 | public class FieldInformation 11 | { 12 | /// 13 | /// Type of response. 14 | /// 15 | public Type Response { get; set; } 16 | 17 | /// 18 | /// Name of field. 19 | /// 20 | public string Name { get; set; } 21 | 22 | /// 23 | /// Query arguments. 24 | /// 25 | public QueryArguments Arguments { get; set; } 26 | 27 | /// 28 | /// Is a mutation. 29 | /// 30 | public bool IsMutation { get; set; } 31 | 32 | /// 33 | /// The method information this field is defined from. 34 | /// 35 | public MethodInfo Method { get; set; } 36 | 37 | /// 38 | /// The obsolete attribute message if any. 39 | /// 40 | public string ObsoleteReason { get; set; } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Models/GraphControllerDefinition.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace GraphQl.SchemaGenerator.Models 4 | { 5 | public class GraphControllerDefinition 6 | { 7 | public IEnumerable Routes { get; private set; } 8 | public GraphControllerDefinition(IEnumerable routes) 9 | { 10 | Routes = routes; 11 | } 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Models/GraphRouteDefinition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace GraphQl.SchemaGenerator.Models 6 | { 7 | public class GraphRouteDefinition 8 | { 9 | public bool IsMutation { get; set; } 10 | public string Name { get; } 11 | 12 | public string MethodName { get; } 13 | 14 | public Type ResponseType { get; } 15 | 16 | public IEnumerable Parameters { get; } 17 | 18 | public Type ControllerType { get; internal set; } 19 | 20 | public GraphRouteDefinition(Type controllerType, 21 | string name, 22 | string methodName, 23 | Type returnType, 24 | IEnumerable parameters) 25 | { 26 | ControllerType = controllerType; 27 | Name = name; 28 | MethodName = methodName; 29 | Parameters = parameters; 30 | ResponseType = returnType; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Models/RequiredType.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.SchemaGenerator.Models 2 | { 3 | public enum RequiredType 4 | { 5 | // Default required based on a nullable type 6 | Default = 0, 7 | 8 | // It is required based on attributes 9 | Required = 1, 10 | 11 | // It is not required based on attributes 12 | NotRequired 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/ObjectGraphTypeBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Reflection; 8 | using GraphQL.SchemaGenerator.Attributes; 9 | using GraphQL.SchemaGenerator.Extensions; 10 | using GraphQL.SchemaGenerator.Helpers; 11 | using GraphQL.Types; 12 | 13 | namespace GraphQL.SchemaGenerator 14 | { 15 | public static class ObjectGraphTypeBuilder 16 | { 17 | public static void Build(ObjectGraphType graphType, Type type) 18 | { 19 | if (type.IsInterface || type.IsAbstract) 20 | { 21 | throw new InvalidOperationException("type must not be an abstract type or an interface"); 22 | } 23 | 24 | ProcessObjectType(graphType, type); 25 | 26 | bool hasDataContract = type.ShouldIncludeInGraph(); 27 | 28 | // KnownTypeAttribute could be used when SchemaType and DomainType are the same 29 | ProcessType(graphType, type); 30 | ProcessProperties(graphType, GetProperties(hasDataContract, type)); 31 | ProcessFields(graphType, GetFields(hasDataContract, type)); 32 | ProcessMethods(graphType, type, type.GetMethods()); 33 | } 34 | 35 | public static void Build(InterfaceGraphType graphType, Type type) 36 | { 37 | if (!type.IsInterface && !type.IsAbstract) 38 | { 39 | throw new InvalidOperationException("type must be an abstract type or an interface"); 40 | } 41 | 42 | ProcessInterfaceType(graphType, type); 43 | 44 | bool hasDataContract = type.ShouldIncludeInGraph(); 45 | 46 | // KnownTypeAttribute could be used when SchemaType and DomainType are the same 47 | ProcessType(graphType, type); 48 | ProcessProperties(graphType, GetProperties(hasDataContract, type)); 49 | ProcessFields(graphType, GetFields(hasDataContract, type)); 50 | ProcessMethods(graphType, type, type.GetMethods()); 51 | } 52 | 53 | public static void Build(InputObjectGraphType graphType, Type type) 54 | { 55 | ProcessType(graphType, type); 56 | bool hasDataContract = type.ShouldIncludeInGraph(); 57 | ProcessProperties(graphType, GetProperties(hasDataContract, type), true); 58 | ProcessFields(graphType, GetFields(hasDataContract, type), true); 59 | ProcessMethods(graphType, type, type.GetMethods()); 60 | } 61 | 62 | private static IEnumerable GetProperties(bool hasDataContract, Type type) 63 | { 64 | var properties = type.GetProperties(); 65 | if (hasDataContract) 66 | { 67 | return properties.Where(p => p.ShouldIncludeMemberInGraph()); 68 | } 69 | else 70 | { 71 | return properties; 72 | } 73 | } 74 | 75 | private static IEnumerable GetFields(bool hasDataContract, Type type) 76 | { 77 | var fields = type.GetFields(); 78 | if (hasDataContract) 79 | { 80 | return fields.Where(f => f.ShouldIncludeMemberInGraph()); 81 | } 82 | else 83 | { 84 | return fields; 85 | } 86 | } 87 | 88 | private static void ProcessInterfaceType(InterfaceGraphType interfaceGraphType, Type type) 89 | { 90 | interfaceGraphType.ResolveType = CreateResolveType(type); 91 | } 92 | 93 | private static void ProcessObjectType(ObjectGraphType objectGraphType, Type type) 94 | { 95 | var interfaces = new List(); 96 | foreach (var @interface in type.GetInterfaces()) 97 | { 98 | if (!IsGraphType(@interface)) 99 | { 100 | continue; 101 | } 102 | interfaces.Add(GraphTypeConverter.ConvertTypeToGraphType(type)); 103 | } 104 | 105 | objectGraphType.Interfaces = interfaces; 106 | } 107 | 108 | private static bool IsGraphType(Type @interface) 109 | { 110 | return TypeHelper.GetGraphType(@interface) != null || 111 | @interface.ShouldIncludeInGraph(); 112 | } 113 | 114 | private static void ProcessType(GraphType graphType, Type type) 115 | { 116 | graphType.Name = TypeHelper.GetDisplayName(type); 117 | 118 | var descAttr = type.GetCustomAttribute(); 119 | if (descAttr != null) 120 | { 121 | graphType.Description = descAttr.Description; 122 | } 123 | // explicit - include with DataMember, implicit - exclude with GraphIgnore 124 | } 125 | 126 | private static Func CreateResolveType(Type type) 127 | { 128 | var expressions = new List(); 129 | var knownTypes = TypeHelper.GetGraphKnownTypes(type); 130 | 131 | var instanceParam = Expression.Parameter(typeof(object), "instance"); 132 | var returnLabel = Expression.Label(typeof(ObjectGraphType)); 133 | 134 | foreach (var knownType in knownTypes) 135 | { 136 | var graphType = GraphTypeConverter.ConvertTypeToGraphType(knownType.SchemaType); 137 | var lookup = Expression.IfThen( 138 | Expression.TypeIs(instanceParam, knownType.DomainType), 139 | Expression.Return(returnLabel, Expression.Convert(Expression.New(graphType), typeof(ObjectGraphType))) 140 | ); 141 | 142 | expressions.Add(lookup); 143 | } 144 | 145 | var result = Expression.Convert(Expression.Constant(null), typeof(ObjectGraphType)); 146 | expressions.Add(Expression.Label(returnLabel, result)); 147 | var body = Expression.Block(expressions); 148 | 149 | return Expression.Lambda>( 150 | body, 151 | instanceParam).Compile(); 152 | } 153 | 154 | private static void ProcessProperties(IComplexGraphType graphType, IEnumerable properties, bool isInputType = false) 155 | { 156 | foreach (var property in properties.OrderBy(p => p.Name)) 157 | { 158 | var required = TypeHelper.IsNotNull(property); 159 | 160 | var propertyGraphType = TypeHelper.GetGraphType(property); 161 | if (propertyGraphType != null) 162 | { 163 | propertyGraphType = GraphTypeConverter.ConvertTypeToGraphType(propertyGraphType, required, isInputType); 164 | propertyGraphType = EnsureList(property.PropertyType, propertyGraphType); 165 | } 166 | else 167 | { 168 | propertyGraphType = GraphTypeConverter.ConvertTypeToGraphType(property.PropertyType, required, isInputType); 169 | } 170 | 171 | var name = StringHelper.GraphName(property.Name); 172 | var field = graphType.AddField(new FieldType { 173 | Type=propertyGraphType, 174 | Name = name, 175 | Description = TypeHelper.GetDescription(property) 176 | }); 177 | 178 | field.DefaultValue = TypeHelper.GetDefaultValue(property); 179 | field.DeprecationReason = TypeHelper.GetDeprecationReason(property); 180 | } 181 | } 182 | 183 | private static void ProcessFields(IComplexGraphType graphType, IEnumerable fields, bool isInputType = false) 184 | { 185 | foreach (var field in fields.OrderBy(f => f.Name)) 186 | { 187 | var required = TypeHelper.IsNotNull(field); 188 | 189 | var fieldGraphType = TypeHelper.GetGraphType(field); 190 | if (fieldGraphType != null) 191 | { 192 | fieldGraphType = GraphTypeConverter.ConvertTypeToGraphType(fieldGraphType, required, isInputType); 193 | fieldGraphType = EnsureList(field.FieldType, fieldGraphType); 194 | } 195 | else 196 | { 197 | fieldGraphType = GraphTypeConverter.ConvertTypeToGraphType(field.FieldType, required, isInputType); 198 | } 199 | 200 | var addedField = graphType.AddField(new FieldType { 201 | Type = fieldGraphType, 202 | Name = StringHelper.GraphName(field.Name) 203 | }); 204 | 205 | addedField.DeprecationReason = TypeHelper.GetDeprecationReason(field); 206 | 207 | } 208 | } 209 | 210 | private static void ProcessMethods(IComplexGraphType graphType, Type type, IEnumerable methods) 211 | { 212 | if (!typeof(GraphType).IsAssignableFrom(type) && 213 | !type.IsDefined(typeof(GraphTypeAttribute))) 214 | { 215 | return; 216 | } 217 | foreach (var method in methods.OrderBy(m => m.Name)) 218 | { 219 | if (IsSpecialMethod(method)) 220 | { 221 | continue; 222 | } 223 | 224 | var required = TypeHelper.IsNotNull(method); 225 | var returnGraphType = TypeHelper.GetGraphType(method); 226 | var methodGraphType = returnGraphType; 227 | if (methodGraphType != null) 228 | { 229 | methodGraphType = GraphTypeConverter.ConvertTypeToGraphType(methodGraphType, required); 230 | methodGraphType = EnsureList(method.ReturnType, methodGraphType); 231 | } 232 | else 233 | { 234 | methodGraphType = GraphTypeConverter.ConvertTypeToGraphType(method.ReturnType, required); 235 | } 236 | 237 | var arguments = 238 | new QueryArguments( 239 | method.GetParameters() 240 | .Where(p => p.ParameterType != typeof(ResolveFieldContext)) 241 | .Select(CreateArgument)); 242 | 243 | // todo: need to fix method execution - not called currently so lower priority 244 | graphType.AddField(new FieldType { 245 | Type = methodGraphType, 246 | Name = StringHelper.GraphName(method.Name), 247 | Arguments = arguments, 248 | DeprecationReason = TypeHelper.GetDeprecationReason(method), 249 | //Resolver = new AsyncFuncFieldResolver(()=>ResolveField(context, field)) 250 | }); 251 | } 252 | } 253 | 254 | private static Type EnsureList(Type type, Type methodGraphType) 255 | { 256 | if (typeof(IEnumerable).IsAssignableFrom(type)) 257 | { 258 | methodGraphType = typeof(ListGraphType<>).MakeGenericType(methodGraphType); 259 | } 260 | 261 | return methodGraphType; 262 | } 263 | 264 | public static bool IsSpecialMethod(MethodInfo method) 265 | { 266 | return method.IsSpecialName || method.DeclaringType == typeof(object); 267 | } 268 | 269 | private static QueryArgument CreateArgument(ParameterInfo parameter) 270 | { 271 | var required = TypeHelper.IsNotNull(parameter); 272 | var parameterGraphType = TypeHelper.GetGraphType(parameter); 273 | if (parameterGraphType != null) 274 | { 275 | parameterGraphType = GraphTypeConverter.ConvertTypeToGraphType(parameterGraphType, required); 276 | parameterGraphType = EnsureList(parameter.ParameterType, parameterGraphType); 277 | } 278 | else 279 | { 280 | parameterGraphType = GraphTypeConverter.ConvertTypeToGraphType(parameter.ParameterType, required); 281 | } 282 | 283 | return new QueryArgument(parameterGraphType) 284 | { 285 | Name = parameter.Name, 286 | DefaultValue = TypeHelper.GetDefaultValue(parameter), 287 | Description = TypeHelper.GetDescription(parameter), 288 | }; 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "GraphQL.SchemaGenerator": { 4 | "commandName": "Project", 5 | "nativeDebugging": true 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/README.md: -------------------------------------------------------------------------------- 1 | ## Schema Generator 2 | The shema generator will automatically create a schema on existing c# models. This includes every response model, request model, and composed classes in these models. This can save a lot of time with an existing SDK or API project that is adding graph ql support. 3 | 4 | ``` 5 | /// 6 | /// An example of the sdk that could be exposed. This is decorated with attributes to self generate a graph schema. 7 | /// 8 | public class StarWarsAttributeSchema 9 | { 10 | private readonly StarWarsData _data = new StarWarsData(); 11 | 12 | /// 13 | /// Get the current hero. 14 | /// 15 | /// 16 | /// Example of graph ql attribute using the defaults. 17 | /// 18 | /// Droid. 19 | [GraphRoute] 20 | public Droid Hero() 21 | { 22 | var item = _data.GetDroidByIdAsync("3").Result; 23 | 24 | return item; 25 | } 26 | } 27 | ``` 28 | 29 | Would be equivalent to: 30 | 31 | 32 | ```csharp 33 | public class StarWarsSchema : Schema 34 | { 35 | public StarWarsSchema() 36 | { 37 | Query = new StarWarsQuery(); 38 | } 39 | } 40 | 41 | public class StarWarsQuery : ObjectGraphType 42 | { 43 | public StarWarsQuery() 44 | { 45 | var data = new StarWarsData(); 46 | Name = "Query"; 47 | Field( 48 | "hero", 49 | resolve: context => data.GetDroidById("3") 50 | ); 51 | } 52 | } 53 | 54 | public class DroidType : ObjectGraphType 55 | { 56 | public DroidType() 57 | { 58 | var data = new StarWarsData(); 59 | Name = "Droid"; 60 | Field>("id", "The id of the droid."); 61 | Field>("name", "The name of the droid."); 62 | Field>( 63 | "friends", 64 | resolve: context => data.GetFriends(context.Source as StarWarsCharacter) 65 | ); 66 | IsTypeOf = value => value is Droid; 67 | } 68 | } 69 | ``` 70 | 71 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Schema/GraphTypesLookup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using GraphQL.Introspection; 5 | using GraphQL.SchemaGenerator.Extensions; 6 | using GraphQL.SchemaGenerator.Wrappers; 7 | using GraphQL.Types; 8 | 9 | namespace GraphQL.SchemaGenerator.Schema 10 | { 11 | // Modified from GraphQL to add NonNullGraphTypes 12 | public class GraphTypesLookup 13 | { 14 | private readonly Dictionary _types = new Dictionary(); 15 | 16 | public GraphTypesLookup() 17 | { 18 | AddType(); 19 | AddType(); 20 | AddType(); 21 | AddType(); 22 | AddType(); 23 | AddType(); 24 | AddType(); 25 | 26 | AddType>(); 27 | AddType>(); 28 | AddType>(); 29 | AddType>(); 30 | AddType>(); 31 | AddType>(); 32 | AddType>(); 33 | 34 | AddType<__Schema>(); 35 | AddType<__Type>(); 36 | AddType<__Field>(); 37 | AddType<__EnumValue>(); 38 | AddType<__InputValue>(); 39 | AddType<__TypeKind>(); 40 | AddType<__Directive>(); 41 | } 42 | 43 | public static GraphTypesLookup Create(IEnumerable types, Func resolveType) 44 | { 45 | var lookup = new GraphTypesLookup(); 46 | 47 | var ctx = new TypeCollectionContext(resolveType, (name, graphType, context) => 48 | { 49 | if (lookup[name] == null) 50 | { 51 | lookup.AddType(graphType, context); 52 | } 53 | }); 54 | 55 | foreach (var type in types) 56 | { 57 | lookup.AddType(type, ctx); 58 | }; 59 | 60 | return lookup; 61 | } 62 | 63 | public void Clear() 64 | { 65 | _types.Clear(); 66 | } 67 | 68 | public IEnumerable All() 69 | { 70 | return _types.Values; 71 | } 72 | 73 | public GraphType this[string typeName] 74 | { 75 | get 76 | { 77 | GraphType result; 78 | _types.TryGetValue(typeName, out result); 79 | return result; 80 | } 81 | set { _types[typeName] = value; } 82 | } 83 | 84 | public GraphType this[Type type] 85 | { 86 | get 87 | { 88 | var result = _types.FirstOrDefault(x => x.Value.GetType() == type); 89 | return result.Value; 90 | } 91 | } 92 | 93 | public IEnumerable FindImplemenationsOf(Type type) 94 | { 95 | return _types 96 | .Values 97 | .OfType() 98 | .Where(t => Enumerable.Any(t.Interfaces, i => i == type)) 99 | .ToList(); 100 | } 101 | 102 | public void AddType() 103 | where TType : GraphType, new() 104 | { 105 | var context = new TypeCollectionContext( 106 | type => (GraphType)Activator.CreateInstance(type), 107 | (name, type, _) => 108 | { 109 | _types[name] = type; 110 | _?.AddType(name, type, null); 111 | }); 112 | 113 | AddType(context); 114 | } 115 | 116 | public void AddType(TypeCollectionContext context) 117 | where TType : GraphType 118 | { 119 | var instance = context.ResolveType(typeof(TType)); 120 | AddType(instance, context); 121 | } 122 | 123 | public void AddType(GraphType type, TypeCollectionContext context) 124 | { 125 | if (type == null) 126 | { 127 | return; 128 | } 129 | 130 | var name = type.CollectTypes(context); 131 | _types[name] = type; 132 | 133 | bool isInput = type is InputObjectGraphType; 134 | 135 | foreach (var field in type.Fields) 136 | { 137 | field.Type = updateInputFieldType(isInput, field.Type); 138 | AddTypeIfNotRegistered(field.Type, context); 139 | 140 | if (field.Arguments != null) 141 | { 142 | foreach (var arg in field.Arguments) 143 | { 144 | AddTypeIfNotRegistered(arg.Type, context); 145 | } 146 | } 147 | } 148 | 149 | if (type is ObjectGraphType) 150 | { 151 | var obj = (ObjectGraphType)type; 152 | foreach (var objectInterface in obj.Interfaces) 153 | { 154 | AddTypeIfNotRegistered(objectInterface, context); 155 | 156 | var interfaceInstance = this[objectInterface] as InterfaceGraphType; 157 | if (interfaceInstance != null) 158 | { 159 | interfaceInstance.AddPossibleType(obj); 160 | 161 | if (interfaceInstance.ResolveType == null && obj.IsTypeOf == null) 162 | { 163 | throw new ExecutionError(( 164 | "Interface type {0} does not provide a \"resolveType\" function" + 165 | "and possible Type \"{1}\" does not provide a \"isTypeOf\" function. " + 166 | "There is no way to resolve this possible type during execution.") 167 | .ToFormat(interfaceInstance, obj)); 168 | } 169 | } 170 | } 171 | } 172 | 173 | if (type is UnionGraphType) 174 | { 175 | var union = (UnionGraphType)type; 176 | 177 | if (!union.Types.Any()) 178 | { 179 | throw new ExecutionError("Must provide types for Union {0}.".ToFormat(union)); 180 | } 181 | 182 | union.Types.Apply(unionedType => 183 | { 184 | AddTypeIfNotRegistered(unionedType, context); 185 | 186 | var objType = this[unionedType] as ObjectGraphType; 187 | 188 | if (union.ResolveType == null && objType != null && objType.IsTypeOf == null) 189 | { 190 | throw new ExecutionError(( 191 | "Union type {0} does not provide a \"resolveType\" function" + 192 | "and possible Type \"{1}\" does not provide a \"isTypeOf\" function. " + 193 | "There is no way to resolve this possible type during execution.") 194 | .ToFormat(union, objType)); 195 | } 196 | 197 | union.AddPossibleType(objType); 198 | }); 199 | } 200 | } 201 | 202 | private Type updateInputFieldType(bool isInput, Type type) 203 | { 204 | var newType = type; 205 | if (isInput) 206 | { 207 | if (type.IsAssignableToGenericType(typeof(ObjectGraphTypeWrapper<>))) 208 | { 209 | newType = typeof(InputObjectGraphTypeWrapper<>).MakeGenericType(type.GetGenericArguments()[0]); 210 | } 211 | else if (type.IsAssignableToGenericType(typeof(KeyValuePairGraphType<,>))) 212 | { 213 | var genericArgs = type.GetGenericArguments(); 214 | newType = typeof(KeyValuePairInputGraphType<,>).MakeGenericType( 215 | genericArgs[0], 216 | genericArgs[1]); 217 | } 218 | else if (typeof(ObjectGraphType).IsAssignableFrom(type)) 219 | { 220 | newType = typeof(InputObjectGraphType); 221 | } 222 | else if (type.IsAssignableToGenericType(typeof(InterfaceGraphTypeWrapper<>))) 223 | { 224 | newType = typeof(InputObjectGraphTypeWrapper<>).MakeGenericType(type); 225 | } 226 | else if (typeof(InterfaceGraphType).IsAssignableFrom(type)) 227 | { 228 | newType = typeof(InputObjectGraphType); 229 | } 230 | else if (typeof(ListGraphType).IsAssignableFrom(type)) 231 | { 232 | var itemType = type.GetGenericArguments()[0]; 233 | var newItemType = updateInputFieldType(isInput, itemType); 234 | if (itemType != newItemType) 235 | { 236 | newType = typeof(ListGraphType<>).MakeGenericType(newItemType); 237 | } 238 | } 239 | else if (typeof(EnumerationGraphType).IsAssignableFrom(type)) 240 | { 241 | newType = typeof(IntGraphType); 242 | } 243 | else if (type.IsAssignableToGenericType(typeof(EnumerationGraphTypeWrapper<>))) 244 | { 245 | newType = typeof(IntGraphType); 246 | } 247 | } 248 | 249 | return newType; 250 | } 251 | 252 | private void AddTypeIfNotRegistered(Type type, TypeCollectionContext context) 253 | { 254 | var foundType = this[type]; 255 | if (foundType == null) 256 | { 257 | AddType(context.ResolveType(type), context); 258 | } 259 | } 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Schema/IDomainSchemaTypeMapping.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GraphQL.SchemaGenerator.Schema 4 | { 5 | public interface IDomainSchemaTypeMapping 6 | { 7 | Type DomainType { get; } 8 | Type SchemaType { get; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Schema/ISchemaFactory.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Types; 2 | 3 | namespace GraphQL.SchemaGenerator.Schema 4 | { 5 | public interface ISchemaFactory 6 | { 7 | ISchema GetOrCreateSchema(); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Schema/SchemaBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Reflection; 4 | using GraphQL.SchemaGenerator.Helpers; 5 | using GraphQL.SchemaGenerator.Wrappers; 6 | using GraphQL.Types; 7 | 8 | namespace GraphQL.SchemaGenerator.Schema 9 | { 10 | public class SchemaBuilder 11 | { 12 | private Type _interfaceWrapper = typeof(InterfaceGraphTypeWrapper<>); 13 | private Type _objectWrapper = typeof(ObjectGraphTypeWrapper<>); 14 | 15 | public ISchema BuildSchema(Type schemaType) 16 | { 17 | var schema = new GraphQL.Types.Schema(); 18 | 19 | var queryProperty = schemaType.GetProperty("Query"); 20 | if (queryProperty != null) 21 | { 22 | var graphType = TypeHelper.GetGraphType(queryProperty); 23 | schema.Query = Activator.CreateInstance(_objectWrapper.MakeGenericType(graphType)) as ObjectGraphType; 24 | } 25 | 26 | var mutationProperty = schemaType.GetProperty("Mutation"); 27 | if (mutationProperty != null) 28 | { 29 | var graphType = TypeHelper.GetGraphType(queryProperty); 30 | schema.Mutation = Activator.CreateInstance(_objectWrapper.MakeGenericType(graphType)) as ObjectGraphType; 31 | } 32 | 33 | return schema; 34 | } 35 | 36 | public void BuildEnum(EnumerationGraphType enumGraphType, Type enumType) 37 | { 38 | enumGraphType.Name = TypeHelper.GetDisplayName(enumType) ?? enumType.Name; 39 | foreach (var e in Enum.GetValues(enumType)) 40 | { 41 | string name = e.ToString(); 42 | var value = Convert.ChangeType(e, typeof(int)); 43 | string description = $"{name}:{value}"; 44 | var field = enumType.GetField(name); 45 | if (field != null) 46 | { 47 | var desc = field.GetCustomAttribute(); 48 | if (desc != null) 49 | { 50 | description = desc.Description; 51 | } 52 | } 53 | enumGraphType.AddValue(name, description, value); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/SchemaGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Threading.Tasks; 6 | using GraphQL.SchemaGenerator.Attributes; 7 | using GraphQL.SchemaGenerator.Extensions; 8 | using GraphQL.SchemaGenerator.Helpers; 9 | using GraphQL.SchemaGenerator.Models; 10 | using GraphQL.Types; 11 | 12 | namespace GraphQL.SchemaGenerator 13 | { 14 | /// 15 | /// Dynamically provides graph ql schema information. 16 | /// 17 | public class SchemaGenerator 18 | { 19 | /// 20 | /// Create field definitions based off a type. 21 | /// 22 | /// 23 | /// 24 | public IEnumerable CreateDefinitions(params Type[] types) 25 | { 26 | var definitions = new List(); 27 | 28 | foreach (var type in types) 29 | foreach (var method in type.GetMethods()) 30 | { 31 | var graphRoute = method.GetCustomAttributes(typeof(GraphRouteAttribute), true) 32 | .OfType() 33 | .FirstOrDefault(); 34 | 35 | if (graphRoute == null) 36 | continue; 37 | 38 | var parameters = method.GetParameters(); 39 | var arguments = CreateArguments(parameters); 40 | var response = method.ReturnType; 41 | 42 | if (response.IsGenericType && response.GetGenericTypeDefinition() == typeof(Task<>)) 43 | response = response.GenericTypeArguments.First(); 44 | 45 | var field = new FieldInformation 46 | { 47 | IsMutation = graphRoute.IsMutation, 48 | Arguments = arguments, 49 | Name = 50 | !string.IsNullOrWhiteSpace(graphRoute.Name) 51 | ? graphRoute.Name 52 | : StringHelper.ConvertToCamelCase(method.Name), 53 | Response = response, 54 | Method = method, 55 | ObsoleteReason = TypeHelper.GetDeprecationReason(method) 56 | }; 57 | 58 | var definition = new FieldDefinition(field, context => ResolveField(context, field)); 59 | 60 | definitions.Add(definition); 61 | } 62 | 63 | return definitions; 64 | } 65 | 66 | /// 67 | /// Resolve the value from a field. 68 | /// 69 | public object ResolveField(ResolveFieldContext context, FieldInformation field) 70 | { 71 | var classObject = ServiceProvider.GetService(field.Method.DeclaringType); 72 | var parameters = context.Parameters(field); 73 | 74 | if (classObject == null) 75 | throw new Exception($"Can't resolve class from: {field.Method.DeclaringType}"); 76 | 77 | try 78 | { 79 | var result = field.Method.Invoke(classObject, parameters); 80 | 81 | return result; 82 | } 83 | catch (Exception ex) 84 | { 85 | var stringParams = parameters?.ToList().Select(t => string.Concat(t.ToString(), ":")); 86 | 87 | throw new Exception($"Cant invoke {field.Method.DeclaringType} with parameters {stringParams}", ex); 88 | } 89 | } 90 | 91 | /// 92 | /// Helper method to create schema from types. 93 | /// 94 | /// 95 | /// 96 | public GraphQL.Types.Schema CreateSchema(params Type[] types) 97 | { 98 | return CreateSchema(CreateDefinitions(types)); 99 | } 100 | 101 | /// 102 | /// Create schema from the field definitions. 103 | /// 104 | public GraphQL.Types.Schema CreateSchema( 105 | IEnumerable definitions) 106 | { 107 | var mutation = new ObjectGraphType 108 | { 109 | Name = "RootMutations" 110 | }; 111 | var query = new ObjectGraphType 112 | { 113 | Name = "RootQueries" 114 | }; 115 | 116 | foreach (var definition in definitions) 117 | { 118 | if (definition.Field == null) 119 | continue; 120 | 121 | var type = EnsureGraphType(definition.Field.Response); 122 | 123 | if (definition.Field.IsMutation) 124 | mutation.FieldAsync( 125 | type, 126 | definition.Field.Name, 127 | TypeHelper.GetDescription(definition.Field.Method), 128 | definition.Field.Arguments, 129 | definition.Resolve, 130 | definition.Field.ObsoleteReason); 131 | else 132 | query.FieldAsync( 133 | type, 134 | definition.Field.Name, 135 | TypeHelper.GetDescription(definition.Field.Method), 136 | definition.Field.Arguments, 137 | definition.Resolve, 138 | definition.Field.ObsoleteReason); 139 | } 140 | 141 | var schema = new GraphQL.Types.Schema(TypeResolver) 142 | { 143 | Mutation = mutation.Fields.Any() ? mutation : null, 144 | Query = query.Fields.Any() ? query : null 145 | }; 146 | 147 | return schema; 148 | } 149 | 150 | /// 151 | /// Ensure graph type. Can return null if the type can't be used. 152 | /// 153 | /// 154 | /// 155 | public static Type EnsureGraphType(Type parameterType) 156 | { 157 | if (parameterType == null || parameterType == typeof(void) || parameterType == typeof(Task)) 158 | return typeof(StringGraphType); 159 | 160 | if (typeof(GraphType).IsAssignableFrom(parameterType)) 161 | return parameterType; 162 | 163 | var type = GraphTypeConverter.ConvertTypeToGraphType(parameterType); 164 | 165 | if (type == null) 166 | type = typeof(ScalarGraphType); 167 | 168 | return type; 169 | } 170 | 171 | /// 172 | /// Dynamically create query arguments. 173 | /// 174 | public static QueryArguments CreateArguments(IEnumerable parameters) 175 | { 176 | var arguments = new List(); 177 | 178 | foreach (var parameter in parameters) 179 | { 180 | var argument = CreateArgument(parameter); 181 | arguments.Add(argument); 182 | } 183 | 184 | return new QueryArguments(arguments); 185 | } 186 | 187 | private static QueryArgument CreateArgument(ParameterInfo parameter) 188 | { 189 | var requestArgumentType = GetRequestArgumentType(parameter.ParameterType); 190 | 191 | var argument = (QueryArgument) Activator.CreateInstance(requestArgumentType); 192 | argument.Name = parameter.Name; 193 | 194 | return argument; 195 | } 196 | 197 | private static Type GetRequestArgumentType(Type parameterType) 198 | { 199 | var requestType = GraphTypeConverter.ConvertTypeToGraphType(parameterType, RequiredType.NotRequired, true); 200 | var requestArgumentType = typeof(QueryArgument<>).MakeGenericType(requestType); 201 | 202 | return requestArgumentType; 203 | } 204 | 205 | #region Dependencies 206 | 207 | private IServiceProvider ServiceProvider { get; } 208 | private IGraphTypeResolver TypeResolver { get; } 209 | 210 | public SchemaGenerator(IServiceProvider serviceProvider, IGraphTypeResolver typeResolver) 211 | { 212 | ServiceProvider = serviceProvider; 213 | TypeResolver = typeResolver; 214 | } 215 | 216 | public SchemaGenerator(IServiceProvider serviceProvider) 217 | { 218 | ServiceProvider = serviceProvider; 219 | TypeResolver = new GraphTypeResolver(); 220 | } 221 | 222 | #endregion 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Types/OriginalDateGraphType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using GraphQL.Language.AST; 4 | 5 | namespace GraphQL.Types 6 | { 7 | /// 8 | /// Keep dates in the original format. 9 | /// 10 | public class OriginalDateGraphType : ScalarGraphType 11 | { 12 | public OriginalDateGraphType() 13 | { 14 | Name = "Date"; 15 | Description = 16 | "The `Date` scalar type represents a timestamp provided in UTC. `Date` expects timestamps " + 17 | "to be formatted in accordance with the [ISO-8601](https://en.wikipedia.org/wiki/ISO_8601) standard."; 18 | } 19 | 20 | public override object Serialize(object value) 21 | { 22 | return ParseValue(value); 23 | } 24 | 25 | public override object ParseValue(object value) 26 | { 27 | if (value is DateTime) 28 | { 29 | return (DateTime) value; 30 | } 31 | 32 | var inputValue = value?.ToString().Trim('"'); 33 | 34 | DateTime outputValue; 35 | if (DateTime.TryParse( 36 | inputValue, 37 | CultureInfo.CurrentCulture, 38 | DateTimeStyles.RoundtripKind, 39 | out outputValue)) 40 | { 41 | return outputValue; 42 | } 43 | 44 | return null; 45 | } 46 | 47 | public override object ParseLiteral(IValue value) 48 | { 49 | var timeValue = value as DateTimeValue; 50 | if (timeValue != null) 51 | { 52 | return ParseValue(timeValue.Value); 53 | } 54 | 55 | var stringValue = value as StringValue; 56 | if (stringValue != null) 57 | { 58 | return ParseValue(stringValue.Value); 59 | } 60 | 61 | return null; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Types/TimeSpanGraphType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GraphQL.Language.AST; 3 | using GraphQL.Types; 4 | 5 | namespace GraphQL.SchemaGenerator.Types 6 | { 7 | /// 8 | /// Time span graph type. 9 | /// 10 | public class TimeSpanGraphType : ScalarGraphType 11 | { 12 | #region Constructor 13 | 14 | /// 15 | /// Constructor 16 | /// 17 | public TimeSpanGraphType() 18 | { 19 | Name = "TimeSpan"; 20 | Description = "The `TimeSpan` scalar type represents a timespan in the format HH::mm::ss"; 21 | } 22 | 23 | #endregion 24 | 25 | #region Methods 26 | 27 | /// 28 | /// Serialize. 29 | /// 30 | /// 31 | /// 32 | public override object Serialize(object value) 33 | { 34 | return ParseValue(value).ToString(); 35 | } 36 | 37 | /// 38 | /// Parse value. 39 | /// 40 | /// 41 | /// 42 | public override object ParseValue(object value) 43 | { 44 | string inputValue; 45 | TimeSpan timeSpan; 46 | 47 | if (value is DateTime) 48 | { 49 | inputValue = ((DateTime) value).ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFF'Z'"); 50 | } 51 | else 52 | { 53 | inputValue = value?.ToString().Trim('"') ?? string.Empty; 54 | } 55 | 56 | if (TimeSpan.TryParse(inputValue, out timeSpan)) 57 | { 58 | return timeSpan; 59 | } 60 | return null; 61 | } 62 | 63 | /// 64 | /// Parse literal. 65 | /// 66 | /// 67 | /// 68 | public override object ParseLiteral(IValue value) 69 | { 70 | var dateTimeVal = value as StringValue; 71 | 72 | if (dateTimeVal == null) 73 | { 74 | return null; 75 | } 76 | 77 | return ParseValue(dateTimeVal.Value); 78 | } 79 | 80 | #endregion 81 | } 82 | } -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Wrappers/ByteArrayGraphType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GraphQL.Language.AST; 3 | using GraphQL.Types; 4 | 5 | namespace GraphQL.SchemaGenerator.Wrappers 6 | { 7 | /// 8 | /// Byte array graph type. 9 | /// 10 | public class ByteArrayGraphType : ScalarGraphType 11 | { 12 | #region Constructor 13 | 14 | /// 15 | /// Constructor. 16 | /// 17 | public ByteArrayGraphType() 18 | { 19 | Name = "Base64"; 20 | Description = "Byte array"; 21 | } 22 | 23 | #endregion 24 | 25 | #region Methods 26 | 27 | /// 28 | /// Parse value. 29 | /// 30 | /// 31 | /// 32 | public override object ParseValue(object value) 33 | { 34 | var bytes = value as byte[]; 35 | if (bytes != null) 36 | { 37 | return Convert.ToBase64String(bytes); 38 | } 39 | 40 | return null; 41 | } 42 | 43 | /// 44 | /// Serialize. 45 | /// 46 | /// 47 | /// 48 | public override object Serialize(object value) 49 | { 50 | return ParseValue(value); 51 | } 52 | 53 | /// 54 | /// Parse literal. 55 | /// 56 | /// 57 | /// 58 | public override object ParseLiteral(IValue value) 59 | { 60 | var val = value as StringValue; 61 | 62 | if (val == null) 63 | { 64 | return null; 65 | } 66 | 67 | return ParseValue(val.Value); 68 | } 69 | 70 | #endregion 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Wrappers/EnumerationGraphTypeWrapper.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.SchemaGenerator.Schema; 2 | using GraphQL.Types; 3 | 4 | namespace GraphQL.SchemaGenerator.Wrappers 5 | { 6 | public class EnumerationGraphTypeWrapper : EnumerationGraphType, IIgnore 7 | { 8 | public EnumerationGraphTypeWrapper() 9 | { 10 | new SchemaBuilder().BuildEnum(this, typeof(T)); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Wrappers/IIGnore.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.SchemaGenerator.Wrappers 2 | { 3 | public interface IIgnore 4 | { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Wrappers/InputObjectGraphTypeWrapper.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Types; 2 | 3 | namespace GraphQL.SchemaGenerator.Wrappers 4 | { 5 | public class InputObjectGraphTypeWrapper : InputObjectGraphType, IIgnore 6 | { 7 | public InputObjectGraphTypeWrapper() 8 | { 9 | ObjectGraphTypeBuilder.Build(this, typeof(T)); 10 | Name = "Input_" + Name; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Wrappers/InterfaceGraphTypeWrapper.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Types; 2 | 3 | namespace GraphQL.SchemaGenerator.Wrappers 4 | { 5 | public class InterfaceGraphTypeWrapper : InterfaceGraphType, IIgnore 6 | { 7 | public InterfaceGraphTypeWrapper() 8 | { 9 | ObjectGraphTypeBuilder.Build(this, typeof(T)); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Wrappers/KeyValuePairGraphType.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.SchemaGenerator.Helpers; 2 | using GraphQL.Types; 3 | 4 | namespace GraphQL.SchemaGenerator.Wrappers 5 | { 6 | public class KeyValuePairGraphType : ObjectGraphType 7 | where TKey : GraphType 8 | where TValue : GraphType 9 | { 10 | public KeyValuePairGraphType() 11 | { 12 | Name = "KeyValuePair_" + TypeHelper.GetFullName(typeof(TKey)) + "_" + TypeHelper.GetFullName(typeof(TValue)); 13 | 14 | Field("key"); 15 | Field("value"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Wrappers/KeyValuePairInputGraphType.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.SchemaGenerator.Helpers; 2 | using GraphQL.Types; 3 | 4 | namespace GraphQL.SchemaGenerator.Wrappers 5 | { 6 | public class KeyValuePairInputGraphType : InputObjectGraphType 7 | where TKey : GraphType 8 | where TValue : GraphType 9 | { 10 | public KeyValuePairInputGraphType() 11 | { 12 | Name = "Input_KeyValuePair_" + TypeHelper.GetFullName(typeof(TKey)) + "_" + TypeHelper.GetFullName(typeof(TValue)); 13 | 14 | Field("key"); 15 | Field("value"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/Wrappers/ObjectGraphTypeWrapper.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Types; 2 | 3 | namespace GraphQL.SchemaGenerator.Wrappers 4 | { 5 | public class ObjectGraphTypeWrapper : ObjectGraphType, IIgnore 6 | { 7 | public ObjectGraphTypeWrapper() 8 | { 9 | ObjectGraphTypeBuilder.Build(this, typeof(T)); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/GraphQl.SchemaGenerator/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test-local.ps1: -------------------------------------------------------------------------------- 1 | # run tests 2 | $output = (.\packages\Fixie.1.0.0.33\lib\net45\fixie.console.exe .\src\GraphQL.Tests\bin\debug\GraphQL.Tests.dll --xUnitXml .\fixie-results.xml) -join "`r`n" 3 | 4 | $testResult = $LASTEXITCODE 5 | 6 | if ($testResult -eq 0) 7 | { 8 | Write-Host $output 9 | } 10 | 11 | if ($testResult -ne 0) { 12 | throw "{0}." -f ([string] $output) 13 | } 14 | -------------------------------------------------------------------------------- /test.ps1: -------------------------------------------------------------------------------- 1 | # run tests 2 | $output = (.\packages\Fixie.1.0.0.33\lib\net45\fixie.console.exe .\src\GraphQL.Tests\bin\debug\GraphQL.Tests.dll --xUnitXml .\fixie-results.xml) -join "`r`n" 3 | 4 | $testResult = $LASTEXITCODE 5 | 6 | if ($testResult -eq 0) 7 | { 8 | Write-Host $output 9 | } 10 | 11 | # upload results to AppVeyor 12 | $wc = New-Object 'System.Net.WebClient' 13 | $wc.UploadFile("https://ci.appveyor.com/api/testresults/xunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\fixie-results.xml)) 14 | 15 | if ($testResult -ne 0) { 16 | throw "{0}." -f ([string] $output) 17 | } 18 | --------------------------------------------------------------------------------