├── .editorconfig ├── .gitignore ├── Directory.Build.props ├── GraphQlClientGenerator.sln ├── GraphQlLogo.png ├── License.md ├── README.md ├── src ├── GraphQlClientGenerator.Console │ ├── Commands.cs │ ├── GraphQlCSharpFileHelper.cs │ ├── GraphQlClientGenerator.Console.csproj │ ├── Program.cs │ └── ProgramOptions.cs └── GraphQlClientGenerator │ ├── BaseClasses.cs │ ├── CSharpHelper.cs │ ├── Extensions.cs │ ├── GenerationContext.cs │ ├── GraphQlClientGenerator.csproj │ ├── GraphQlClientSourceGenerator.cs │ ├── GraphQlGenerator.cs │ ├── GraphQlGeneratorConfiguration.cs │ ├── GraphQlIntrospection.cs │ ├── GraphQlIntrospectionSchema.cs │ ├── GraphQlTypeKind.cs │ ├── ICodeFileEmitter.cs │ ├── IScalarFieldTypeMappingProvider.cs │ ├── KeyValueParameterParser.cs │ ├── MultipleFileGenerationContext.cs │ ├── NamingHelper.cs │ ├── OutputType.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── RegexScalarFieldTypeMappingProvider.cs │ └── SingleFileGenerationContext.cs └── test └── GraphQlClientGenerator.Test ├── CompilationHelper.cs ├── ExpectedMultipleFilesContext ├── Avatar ├── Avatar.FileScoped ├── Home ├── Home.FileScoped ├── IncludeDirective ├── IncludeDirective.FileScoped ├── MutationQueryBuilder └── MutationQueryBuilder.FileScoped ├── GlobalFixture.cs ├── GraphQlClientGenerator.Test.csproj ├── GraphQlClientSourceGeneratorTest.cs ├── GraphQlGeneratorTest.cs ├── GraphQlIntrospectionSchemaTest.cs ├── NamingHelperTest.cs ├── RegexCustomScalarFieldTypeMappingRules ├── TestSchemas ├── TestSchema ├── TestSchema2 ├── TestSchema3 ├── TestSchemaWithDeprecatedFields ├── TestSchemaWithNestedListsOfComplexObjects └── TestSchemaWithUnions └── VerifierExpectations ├── GraphQlClientSourceGeneratorTest.SourceGenerationWithRegexCustomScalarFieldTypeMappingProvider.verified.txt ├── GraphQlClientSourceGeneratorTest.SourceGeneration_scalarFieldTypeMappingProviderTypeName=False.verified.txt ├── GraphQlClientSourceGeneratorTest.SourceGeneration_scalarFieldTypeMappingProviderTypeName=True.verified.txt ├── GraphQlGeneratorTest.DeprecatedAttributes.verified.txt ├── GraphQlGeneratorTest.GenerateDataClasses.verified.txt ├── GraphQlGeneratorTest.GenerateDataClassesWithTypeConfiguration_csharpVersion=CSharp12.verified.txt ├── GraphQlGeneratorTest.GenerateDataClassesWithTypeConfiguration_csharpVersion=Compatible.verified.txt ├── GraphQlGeneratorTest.GenerateFormatMasks.verified.txt ├── GraphQlGeneratorTest.GenerateFullClientCSharpFile.verified.txt ├── GraphQlGeneratorTest.GenerateQueryBuilder.verified.txt ├── GraphQlGeneratorTest.NewCSharpSyntaxWithClassPrefixAndSuffix.verified.txt ├── GraphQlGeneratorTest.WithNestedListsOfComplexObjects.verified.txt ├── GraphQlGeneratorTest.WithNullableReferencesAndPropertyNullabilityBySchema.verified.txt └── GraphQlGeneratorTest.WithUnions.verified.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | ; Top-most EditorConfig file 2 | root = true 3 | 4 | [*.cs] 5 | indent_style = space 6 | indent_size = 4 7 | max_line_length = 220 8 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # General 3 | ############################################################################## 4 | 5 | # OS junk files 6 | [Tt]humbs.db 7 | *.DS_Store 8 | 9 | config.json 10 | project.lock.json 11 | 12 | # Visual Studio 2015 cache/options directory 13 | .vs/ 14 | 15 | # Visual Studio / MonoDevelop 16 | *.[Oo]bj 17 | *.exe 18 | *.dll 19 | *.pdb 20 | *.user 21 | *.aps 22 | *.pch 23 | *.vspscc 24 | *.vssscc 25 | *_i.c 26 | *_p.c 27 | *.ncb 28 | *.suo 29 | *.tlb 30 | *.tlh 31 | *.bak 32 | *.ilk 33 | *.log 34 | *.lib 35 | *.sbr 36 | *.sdf 37 | *.opensdf 38 | *.resources 39 | *.res 40 | ipch/ 41 | obj/ 42 | [Bb]in 43 | [Dd]ebug*/ 44 | [Rr]elease*/ 45 | Ankh.NoLoad 46 | *.gpState 47 | *.received.* 48 | 49 | # Tooling 50 | _ReSharper*/ 51 | *.resharper 52 | [Tt]est[Rr]esult* 53 | *.orig 54 | *.rej 55 | 56 | # NuGet packages 57 | !.nuget/* 58 | [Pp]ackages/* 59 | ![Pp]ackages/repositories.config 60 | 61 | # Temporary Files 62 | ~.* 63 | ~$* 64 | 65 | docker-compose.yml 66 | 67 | # Autotools-generated files 68 | /Makefile 69 | Makefile.in 70 | aclocal.m4 71 | autom4te.cache 72 | /build/ 73 | config.cache 74 | config.guess 75 | config.h 76 | config.h.in 77 | config.log 78 | config.status 79 | config.sub 80 | configure 81 | configure.scan 82 | cygconfig.h 83 | depcomp 84 | install-sh 85 | libtool 86 | ltmain.sh 87 | missing 88 | mkinstalldirs 89 | releases 90 | stamp-h 91 | stamp-h1 92 | stamp-h.in 93 | /test-driver 94 | *~ 95 | *.swp 96 | *.o 97 | .deps 98 | 99 | # Libtool 100 | libtool.m4 101 | lt~obsolete.m4 102 | ltoptions.m4 103 | ltsugar.m4 104 | ltversion.m4 105 | 106 | # Dolt (libtool replacement) 107 | doltlibtool 108 | doltcompile 109 | 110 | # pkg-config 111 | *.pc 112 | 113 | # Emacs 114 | semantic.cache 115 | 116 | # gtags 117 | GPATH 118 | GRTAGS 119 | GSYMS 120 | GTAGS 121 | 122 | # Doxygen 123 | docs/doxygen* 124 | docs/perlmod* 125 | 126 | 127 | ############################################################################## 128 | # Mono-specific patterns 129 | ############################################################################## 130 | 131 | .dirstamp 132 | compile 133 | mono.h 134 | mono-*.tar.* 135 | tmpinst-dir.stamp 136 | msvc/scripts/inputs/ 137 | extensions-config.h 138 | 139 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | latest 5 | Copyright 2017-2025 6 | Husqvik 7 | Tibber 8 | 0.9.30 9 | MIT 10 | https://github.com/Husqvik/GraphQlClientGenerator 11 | GraphQlLogo.png 12 | https://github.com/Husqvik/GraphQlClientGenerator 13 | git 14 | true 15 | snupkg 16 | README.md 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /GraphQlClientGenerator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33122.133 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQlClientGenerator.Console", "src\GraphQlClientGenerator.Console\GraphQlClientGenerator.Console.csproj", "{D8D86371-7E15-4275-8968-2FB431EBBDE3}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C48B93CC-CA04-4372-AA5C-A8DAFA295BCF}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQlClientGenerator", "src\GraphQlClientGenerator\GraphQlClientGenerator.csproj", "{7CC22B24-8C9F-46DA-BE7C-A07BF5D73834}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{4C764379-22AF-4939-9189-9F6C617752F1}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQlClientGenerator.Test", "test\GraphQlClientGenerator.Test\GraphQlClientGenerator.Test.csproj", "{E068E024-2E58-41AD-A617-37DFA973F2E3}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4F6A0502-F97A-49DD-ABF1-E37DF50519E4}" 17 | ProjectSection(SolutionItems) = preProject 18 | Directory.Build.props = Directory.Build.props 19 | GraphQlLogo.png = GraphQlLogo.png 20 | License.md = License.md 21 | README.md = README.md 22 | EndProjectSection 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {D8D86371-7E15-4275-8968-2FB431EBBDE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {D8D86371-7E15-4275-8968-2FB431EBBDE3}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {D8D86371-7E15-4275-8968-2FB431EBBDE3}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {D8D86371-7E15-4275-8968-2FB431EBBDE3}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {7CC22B24-8C9F-46DA-BE7C-A07BF5D73834}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {7CC22B24-8C9F-46DA-BE7C-A07BF5D73834}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {7CC22B24-8C9F-46DA-BE7C-A07BF5D73834}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {7CC22B24-8C9F-46DA-BE7C-A07BF5D73834}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {E068E024-2E58-41AD-A617-37DFA973F2E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {E068E024-2E58-41AD-A617-37DFA973F2E3}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {E068E024-2E58-41AD-A617-37DFA973F2E3}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {E068E024-2E58-41AD-A617-37DFA973F2E3}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(NestedProjects) = preSolution 47 | {D8D86371-7E15-4275-8968-2FB431EBBDE3} = {C48B93CC-CA04-4372-AA5C-A8DAFA295BCF} 48 | {7CC22B24-8C9F-46DA-BE7C-A07BF5D73834} = {C48B93CC-CA04-4372-AA5C-A8DAFA295BCF} 49 | {E068E024-2E58-41AD-A617-37DFA973F2E3} = {4C764379-22AF-4939-9189-9F6C617752F1} 50 | EndGlobalSection 51 | GlobalSection(ExtensibilityGlobals) = postSolution 52 | SolutionGuid = {7BFDF2E3-B56D-4752-9A28-8AE0E3BC47E2} 53 | EndGlobalSection 54 | EndGlobal 55 | -------------------------------------------------------------------------------- /GraphQlLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Husqvik/GraphQlClientGenerator/b5212b8f6b0c83ab01d0d0b854c7fdb5a650f051/GraphQlLogo.png -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | Copyright © 2017-2025 Husqvik 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GraphQL C# client generator 2 | ======================= 3 | 4 | [![Build](https://ci.appveyor.com/api/projects/status/t4iti5bn5ubs2k3k?svg=true&pendingText=pending&passingText=passed&failingText=failed)](https://ci.appveyor.com/project/Husqvik/graphql-client-generator) 5 | [![NuGet Badge](https://badge.fury.io/nu/GraphQlClientGenerator.svg)](https://www.nuget.org/packages/GraphQlClientGenerator) 6 | 7 | This simple console app generates C# GraphQL query builder and data classes for simple, compiler checked, usage of a GraphQL API. 8 | 9 | ---------- 10 | 11 | Generator app usage 12 | ------------- 13 | 14 | ```console 15 | GraphQlClientGenerator.Console --serviceUrl --outputPath --namespace [--header
] 16 | ``` 17 | 18 | Nuget package 19 | ------------- 20 | Installation: 21 | ```console 22 | Install-Package GraphQlClientGenerator 23 | ``` 24 | 25 | dotnet tool 26 | ------------- 27 | ```console 28 | dotnet tool install GraphQlClientGenerator.Tool --global 29 | graphql-client-generator --serviceUrl --outputPath --namespace [--header
] 30 | ``` 31 | 32 | Code 33 | ------------- 34 | Code example for class generation: 35 | ```csharp 36 | var schema = await GraphQlGenerator.RetrieveSchema("https://my-graphql-api/gql"); 37 | var generator = new GraphQlGenerator(); 38 | var generatedClasses = generator.GenerateFullClientCSharpFile(schema); 39 | ``` 40 | 41 | or using full blown setup: 42 | 43 | ```csharp 44 | var schema = await GraphQlGenerator.RetrieveSchema("https://my-graphql-api/gql"); 45 | var configuration = new GraphQlGeneratorConfiguration { TargetNamespace = "MyGqlApiClient", ... }; 46 | var generator = new GraphQlGenerator(configuration); 47 | var builder = new StringBuilder(); 48 | using var writer = new StringWriter(builder); 49 | var generationContext = new SingleFileGenerationContext(schema, writer) { LogMessage = Console.WriteLine }; 50 | generator.Generate(generationContext); 51 | var csharpCode = builder.ToString(); 52 | ``` 53 | 54 | C# 9 source generator 55 | ------------- 56 | C# 9 introduced source generators that can be attached to compilation process. Generated classes will be automatically included in project. 57 | 58 | Project file example: 59 | ```xml 60 | 61 | Exe 62 | net9.0 63 | 64 | 65 | https://api.tibber.com/v1-beta/gql 66 | 67 | $(RootNamespace) 68 | Consumption:ConsumptionEntry|Production:ProductionEntry|RootMutation:TibberMutation|Query:Tibber 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | ``` 86 | 87 | Query builder usage 88 | ------------- 89 | ```csharp 90 | var builder = 91 | new QueryQueryBuilder() 92 | .WithMe( 93 | new MeQueryBuilder() 94 | .WithAllScalarFields() 95 | .WithHome( 96 | new HomeQueryBuilder() 97 | .WithAllScalarFields() 98 | .WithSubscription( 99 | new SubscriptionQueryBuilder() 100 | .WithStatus() 101 | .WithValidFrom()) 102 | .WithSignupStatus( 103 | new SignupStatusQueryBuilder().WithAllFields()) 104 | .WithDisaggregation( 105 | new DisaggregationQueryBuilder().WithAllFields()), 106 | "b420001d-189b-44c0-a3d5-d62452bfdd42") 107 | .WithEnergyStatements ("2016-06", "2016-10")); 108 | 109 | var query = builder.Build(Formatting.Indented); 110 | ``` 111 | results into 112 | ```graphql 113 | query { 114 | me { 115 | id 116 | firstName 117 | lastName 118 | fullName 119 | ssn 120 | email 121 | language 122 | tone 123 | home (id: "b420001d-189b-44c0-a3d5-d62452bfdd42") { 124 | id 125 | avatar 126 | timeZone 127 | subscription { 128 | status 129 | validFrom 130 | } 131 | signupStatus { 132 | registrationStartedTimestamp 133 | registrationCompleted 134 | registrationCompletedTimestamp 135 | checkCurrentSupplierPassed 136 | supplierSwitchConfirmationPassed 137 | startDatePassed 138 | firstReadingReceived 139 | firstBillingDone 140 | firstBillingTimestamp 141 | } 142 | disaggregation { 143 | year 144 | month 145 | fixedConsumptionKwh 146 | fixedConsumptionKwhPercent 147 | heatingConsumptionKwh 148 | heatingConsumptionKwhPercent 149 | behaviorConsumptionKwh 150 | behaviorConsumptionKwhPercent 151 | } 152 | } 153 | energyStatements(from: "2016-06", to: "2016-10") 154 | } 155 | } 156 | ``` 157 | 158 | Mutation 159 | ------------- 160 | ```csharp 161 | var mutation = 162 | new MutationQueryBuilder() 163 | .WithUpdateHome( 164 | new HomeQueryBuilder().WithAllScalarFields(), 165 | new UpdateHomeInput { HomeId = Guid.Empty, AppNickname = "My nickname", Type = HomeType.House, NumberOfResidents = 4, Size = 160, AppAvatar = HomeAvatar.Floorhouse1, PrimaryHeatingSource = HeatingSource.Electricity } 166 | ) 167 | .Build(Formatting.Indented, 2); 168 | ``` 169 | result: 170 | ```graphql 171 | mutation { 172 | updateHome (input: { 173 | homeId: "00000000-0000-0000-0000-000000000000" 174 | appNickname: "My nickname" 175 | appAvatar: FLOORHOUSE1 176 | size: 160 177 | type: HOUSE 178 | numberOfResidents: 4 179 | primaryHeatingSource: ELECTRICITY 180 | }) { 181 | id 182 | timeZone 183 | appNickname 184 | appAvatar 185 | size 186 | type 187 | numberOfResidents 188 | primaryHeatingSource 189 | hasVentilationSystem 190 | } 191 | } 192 | ``` 193 | 194 | Field exclusion 195 | ------------- 196 | Sometimes there is a need to select almost all fields of a queried object except few. In that case `Except` methods can be used often in conjunction with `WithAllFields` or `WithAllScalarFields`. 197 | ```csharp 198 | new ViewerQueryBuilder() 199 | .WithHomes( 200 | new HomeQueryBuilder() 201 | .WithAllScalarFields() 202 | .ExceptPrimaryHeatingSource() 203 | .ExceptMainFuseSize() 204 | ) 205 | .Build(Formatting.Indented); 206 | ``` 207 | result: 208 | ```graphql 209 | query { 210 | homes { 211 | id 212 | timeZone 213 | appNickname 214 | appAvatar 215 | size 216 | type 217 | numberOfResidents 218 | hasVentilationSystem 219 | } 220 | } 221 | 222 | ``` 223 | 224 | Aliases 225 | ------------- 226 | Queried fields can be freely renamed to match target data classes using GraphQL aliases. 227 | ```csharp 228 | new ViewerQueryBuilder("MyQuery") 229 | .WithHome( 230 | new HomeQueryBuilder() 231 | .WithType() 232 | .WithSize() 233 | .WithAddress(new AddressQueryBuilder().WithAddress1("primaryAddressText").WithCountry(), "primaryAddress"), 234 | Guid.NewGuid(), 235 | "primaryHome") 236 | .WithHome( 237 | new HomeQueryBuilder() 238 | .WithType() 239 | .WithSize() 240 | .WithAddress(new AddressQueryBuilder().WithAddress1("secondaryAddressText").WithCountry(), "secondaryAddress"), 241 | Guid.NewGuid(), 242 | "secondaryHome") 243 | .Build(Formatting.Indented); 244 | ``` 245 | result: 246 | ```graphql 247 | query MyQuery { 248 | primaryHome: home (id: "120efe4a-6839-45fc-beed-27455d29212f") { 249 | type 250 | size 251 | primaryAddress: address { 252 | primaryAddressText: address1 253 | country 254 | } 255 | } 256 | secondaryHome: home (id: "0c735830-be56-4a3d-a8cb-d0189037f221") { 257 | type 258 | size 259 | secondaryAddress: address { 260 | secondaryAddressText: address1 261 | country 262 | } 263 | } 264 | } 265 | ``` 266 | 267 | Query parameters 268 | ------------- 269 | ```csharp 270 | var homeIdParameter = new GraphQlQueryParameter("homeId", "ID", homeId); 271 | 272 | var builder = 273 | new TibberQueryBuilder() 274 | .WithViewer( 275 | new ViewerQueryBuilder() 276 | .WithHome(new HomeQueryBuilder().WithAllScalarFields(), homeIdParameter) 277 | ) 278 | .WithParameter(homeIdParameter); 279 | ``` 280 | result: 281 | ```graphql 282 | query ($homeId: ID = "c70dcbe5-4485-4821-933d-a8a86452737b") { 283 | viewer{ 284 | home(id: $homeId) { 285 | id 286 | timeZone 287 | appNickname 288 | appAvatar 289 | size 290 | type 291 | numberOfResidents 292 | primaryHeatingSource 293 | hasVentilationSystem 294 | mainFuseSize 295 | } 296 | } 297 | } 298 | ``` 299 | 300 | Directives 301 | ------------- 302 | ```csharp 303 | var includeDirectParameter = new GraphQlQueryParameter("direct", "Boolean", true); 304 | var includeDirective = new IncludeDirective(includeDirectParameter); 305 | var skipDirective = new SkipDirective(true); 306 | 307 | var builder = 308 | new TibberQueryBuilder() 309 | .WithViewer( 310 | new ViewerQueryBuilder() 311 | .WithName(include: includeDirective) 312 | .WithAccountType(skip: skipDirective) 313 | .WithHomes(new HomeQueryBuilder().WithId(), skip: skipDirective) 314 | ) 315 | .WithParameter(includeDirectParameter); 316 | ``` 317 | result: 318 | ```graphql 319 | query ( 320 | $direct: Boolean = true) { 321 | viewer { 322 | name @include(if: $direct) 323 | accountType @skip(if: true) 324 | homes @skip(if: true) { 325 | id 326 | } 327 | } 328 | } 329 | ``` 330 | 331 | Inline fragments 332 | ------------- 333 | ```csharp 334 | var builder = 335 | new RootQueryBuilder("InlineFragments") 336 | .WithUnion( 337 | new UnionTypeQueryBuilder() 338 | .WithConcreteType1Fragment(new ConcreteType1QueryBuilder().WithAllFields()) 339 | .WithConcreteType2Fragment(new ConcreteType2QueryBuilder().WithAllFields()) 340 | .WithConcreteType3Fragment( 341 | new ConcreteType3QueryBuilder() 342 | .WithName() 343 | .WithConcreteType3Field("alias") 344 | .WithFunction("my value", "myResult1") 345 | ) 346 | ) 347 | .WithInterface( 348 | new NamedTypeQueryBuilder() 349 | .WithName() 350 | .WithConcreteType3Fragment( 351 | new ConcreteType3QueryBuilder() 352 | .WithName() 353 | .WithConcreteType3Field() 354 | .WithFunction("my value") 355 | ), 356 | Guid.Empty 357 | ); 358 | ``` 359 | result: 360 | ```graphql 361 | query InlineFragments { 362 | union { 363 | __typename 364 | ... on ConcreteType1 { 365 | name 366 | concreteType1Field 367 | } 368 | ... on ConcreteType2 { 369 | name 370 | concreteType2Field 371 | } 372 | ... on ConcreteType3 { 373 | __typename 374 | name 375 | alias: concreteType3Field 376 | myResult1: function(value: "my value") 377 | } 378 | } 379 | interface(parameter: "00000000-0000-0000-0000-000000000000") { 380 | name 381 | ... on ConcreteType3 { 382 | __typename 383 | name 384 | concreteType3Field 385 | function(value: "my value") 386 | } 387 | } 388 | } 389 | ``` 390 | 391 | Custom scalar types 392 | ------------- 393 | GraphQL supports custom scalar types. By default these are mapped to `object` type. To ensure appropriate .NET types are generated for data class properties custom mapping interface can be used: 394 | 395 | ```csharp 396 | var configuration = new GraphQlGeneratorConfiguration(); 397 | configuration.ScalarFieldTypeMappingProvider = new MyCustomScalarFieldTypeMappingProvider(); 398 | 399 | public class MyCustomScalarFieldTypeMappingProvider : IScalarFieldTypeMappingProvider 400 | { 401 | public ScalarFieldTypeDescription GetCustomScalarFieldType(ScalarFieldTypeProviderContext context) 402 | { 403 | var unwrappedType = context.FieldType.UnwrapIfNonNull(); 404 | 405 | return 406 | unwrappedType.Name switch 407 | { 408 | "Byte" => new ScalarFieldTypeDescription { NetTypeName = GenerationContext.GetNullableNetTypeName(context, "byte", false), FormatMask = null }, 409 | "DateTime" => new ScalarFieldTypeDescription { NetTypeName = GenerationContext.GetNullableNetTypeName(context, "DateTime", false), FormatMask = null }, 410 | _ => DefaultScalarFieldTypeMappingProvider.GetFallbackFieldType(context) 411 | }; 412 | } 413 | } 414 | ``` 415 | 416 | Generated class example: 417 | ```csharp 418 | public class OrderType 419 | { 420 | public DateTime? CreatedDateTimeUtc { get; set; } 421 | public byte? SomeSmallNumber { get; set; } 422 | } 423 | ``` 424 | 425 | vs. 426 | 427 | ```csharp 428 | public class OrderType 429 | { 430 | public object CreatedDateTimeUtc { get; set; } 431 | public object SomeSmallNumber { get; set; } 432 | } 433 | ``` 434 | ### C# 9 source generator custom types 435 | 436 | Source generator supports `RegexScalarFieldTypeMappingProvider` rules using JSON configuration file. Example: 437 | ```json 438 | [ 439 | { 440 | "patternBaseType": ".+", 441 | "patternValueType": ".+", 442 | "patternValueName": "^((timestamp)|(.*(f|F)rom)|(.*(t|T)o))$", 443 | "netTypeName": "DateTimeOffset", 444 | "isReferenceType": false, 445 | "formatMask": "O" 446 | } 447 | ] 448 | ``` 449 | All pattern values must be specified. `Null` values are not accepted. 450 | 451 | The file must be named `RegexScalarFieldTypeMappingProvider.gql.config.json` and included as additional file. 452 | 453 | ```xml 454 | 455 | 456 | 457 | ``` 458 | -------------------------------------------------------------------------------- /src/GraphQlClientGenerator.Console/Commands.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | using System.CommandLine.Builder; 3 | using System.CommandLine.IO; 4 | using System.CommandLine.NamingConventionBinder; 5 | using System.CommandLine.Parsing; 6 | 7 | namespace GraphQlClientGenerator.Console; 8 | 9 | internal static class Commands 10 | { 11 | public static readonly Parser Parser = 12 | new CommandLineBuilder(SetupGenerateCommand()) 13 | .UseDefaults() 14 | .UseExceptionHandler((exception, invocationContext) => 15 | { 16 | System.Console.ForegroundColor = ConsoleColor.Red; 17 | invocationContext.Console.Error.WriteLine($"An error occurred:{Environment.NewLine}{exception}"); 18 | System.Console.ResetColor(); 19 | invocationContext.ExitCode = 2; 20 | }) 21 | .Build(); 22 | 23 | private static RootCommand SetupGenerateCommand() 24 | { 25 | var serviceUrlOption = new Option(["--serviceUrl", "-u"], "GraphQL service URL used for retrieving schema metadata"); 26 | var schemaFileOption = new Option(["--schemaFileName", "-s"], "Path to schema metadata file in JSON format"); 27 | 28 | var classMappingOption = 29 | new Option( 30 | "--classMapping", 31 | "Format: {GraphQlTypeName}:{C#ClassName}; allows to define custom class names for specific GraphQL types"); 32 | 33 | classMappingOption.AddValidator( 34 | option => 35 | option.ErrorMessage = 36 | KeyValueParameterParser.TryGetCustomClassMapping(option.Tokens.Select(t => t.Value), out _, out var errorMessage) 37 | ? null 38 | : errorMessage); 39 | 40 | var headerOption = new Option("--header", "Format: {Header}:{Value}; allows to enter custom headers required to fetch GraphQL metadata"); 41 | headerOption.AddValidator( 42 | option => 43 | option.ErrorMessage = 44 | KeyValueParameterParser.TryGetCustomHeaders(option.Tokens.Select(t => t.Value), out _, out var errorMessage) 45 | ? null 46 | : errorMessage); 47 | 48 | var regexScalarFieldTypeMappingConfigurationOption = 49 | new Option("--regexScalarFieldTypeMappingConfigurationFile", $"File name specifying rules for \"{nameof(RegexScalarFieldTypeMappingProvider)}\""); 50 | 51 | var command = 52 | new RootCommand 53 | { 54 | new Option(["--outputPath", "-o"], "Output path; include file name for single file output type; folder name for one class per file output type") { IsRequired = true }, 55 | new Option(["--namespace", "-n"], "Root namespace all classes and other members are generated into") { IsRequired = true }, 56 | serviceUrlOption, 57 | schemaFileOption, 58 | new Option("--httpMethod", () => "POST", "GraphQL schema metadata retrieval HTTP method"), 59 | headerOption, 60 | new Option("--classPrefix", "Class prefix; value \"Test\" extends class name to \"TestTypeName\""), 61 | new Option("--classSuffix", "Class suffix, for instance for version control; value \"V2\" extends class name to \"TypeNameV2\""), 62 | new Option("--csharpVersion", () => CSharpVersion.Compatible, "C# version compatibility"), 63 | new Option("--codeDocumentationType", () => CodeDocumentationType.Disabled, "Specifies code documentation generation option"), 64 | new Option("--memberAccessibility", () => MemberAccessibility.Public, "Class and interface access level"), 65 | new Option("--outputType", () => OutputType.SingleFile, "Specifies generated classes organization"), 66 | new Option("--partialClasses", () => false, "Mark classes as \"partial\""), 67 | classMappingOption, 68 | new Option("--booleanTypeMapping", () => BooleanTypeMapping.Boolean, "Specifies the .NET type generated for GraphQL built-in Boolean data type"), 69 | new Option("--floatTypeMapping", () => FloatTypeMapping.Decimal, "Specifies the .NET type generated for GraphQL built-in Float data type"), 70 | new Option("--idTypeMapping", () => IdTypeMapping.Guid, "Specifies the .NET type generated for GraphQL built-in ID data type"), 71 | new Option("--integerTypeMapping", () => IntegerTypeMapping.Int32, "Specifies the .NET type generated for GraphQL built-in Integer data type"), 72 | new Option("--jsonPropertyAttribute", () => JsonPropertyGenerationOption.CaseInsensitive, "Specifies the condition for using \"JsonPropertyAttribute\""), 73 | new Option("--enumValueNaming", () => EnumValueNamingOption.CSharp, "Use \"Original\" to avoid pretty C# name conversion for maximum deserialization compatibility"), 74 | new Option("--dataClassMemberNullability", () => DataClassMemberNullability.AlwaysNullable, "Specifies whether data class scalar properties generated always nullable (for better type reuse) or respect the GraphQL schema"), 75 | new Option("--generationOrder", () => GenerationOrder.DefinedBySchema, "Specifies whether order of generated C# classes/enums respect the GraphQL schema or is enforced to alphabetical for easier change tracking"), 76 | new Option("--inputObjectMode", () => InputObjectMode.Rich, "Specifies whether input objects are generated as POCOs or they have support of GraphQL parameter references and explicit null values"), 77 | new Option("--includeDeprecatedFields", () => false, "Includes deprecated fields in generated query builders and data classes"), 78 | new Option("--nullableReferences", () => false, "Enables nullable references"), 79 | new Option("--fileScopedNamespaces", () => false, "Specifies whether file-scoped namespaces should be used in generated files (C# 10 or later)"), 80 | new Option("--ignoreServiceUrlCertificateErrors", () => false, "Ignores HTTPS errors when retrieving GraphQL metadata from an URL; typically when using self signed certificates"), 81 | regexScalarFieldTypeMappingConfigurationOption 82 | }; 83 | 84 | command.TreatUnmatchedTokensAsErrors = true; 85 | command.Name = "GraphQlClientGenerator.Console"; 86 | command.Description = "A tool for generating C# GraphQL query builders and data classes"; 87 | command.Handler = CommandHandler.Create(GraphQlCSharpFileHelper.GenerateClientSourceCode); 88 | command.AddValidator( 89 | option => 90 | option.ErrorMessage = 91 | option.FindResultFor(serviceUrlOption) is not null && option.FindResultFor(schemaFileOption) is not null 92 | ? "\"serviceUrl\" and \"schemaFileName\" parameters are mutually exclusive. " 93 | : null); 94 | 95 | command.AddValidator( 96 | option => 97 | option.ErrorMessage = 98 | option.FindResultFor(serviceUrlOption) is null && option.FindResultFor(schemaFileOption) is null 99 | ? "Either \"serviceUrl\" or \"schemaFileName\" parameter must be specified. " 100 | : null); 101 | 102 | command.AddValidator( 103 | option => 104 | { 105 | var regexScalarFieldTypeMappingConfigurationFileName = option.FindResultFor(regexScalarFieldTypeMappingConfigurationOption)?.GetValueOrDefault(); 106 | if (regexScalarFieldTypeMappingConfigurationFileName is null) 107 | return; 108 | 109 | try 110 | { 111 | RegexScalarFieldTypeMappingProvider.ParseRulesFromJson(File.ReadAllText(regexScalarFieldTypeMappingConfigurationFileName)); 112 | } 113 | catch (Exception exception) 114 | { 115 | option.ErrorMessage = exception.Message; 116 | } 117 | }); 118 | 119 | return command; 120 | } 121 | } -------------------------------------------------------------------------------- /src/GraphQlClientGenerator.Console/GraphQlCSharpFileHelper.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | 3 | namespace GraphQlClientGenerator.Console; 4 | 5 | internal static class GraphQlCSharpFileHelper 6 | { 7 | public static async Task GenerateClientSourceCode(IConsole console, ProgramOptions options) 8 | { 9 | GraphQlSchema schema; 10 | 11 | if (String.IsNullOrWhiteSpace(options.ServiceUrl)) 12 | { 13 | var schemaJson = await File.ReadAllTextAsync(options.SchemaFileName); 14 | console.WriteLine($"GraphQL schema file {options.SchemaFileName} loaded ({schemaJson.Length:N0} B). "); 15 | schema = GraphQlGenerator.DeserializeGraphQlSchema(schemaJson); 16 | } 17 | else 18 | { 19 | if (!KeyValueParameterParser.TryGetCustomHeaders(options.Header, out var headers, out var headerParsingErrorMessage)) 20 | throw new InvalidOperationException(headerParsingErrorMessage); 21 | 22 | using var httpClientHandler = GraphQlGenerator.CreateDefaultHttpClientHandler(); 23 | if (options.IgnoreServiceUrlCertificateErrors) 24 | httpClientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; 25 | 26 | schema = await GraphQlGenerator.RetrieveSchema(new HttpMethod(options.HttpMethod), options.ServiceUrl, headers, httpClientHandler, GraphQlWellKnownDirective.None); 27 | console.WriteLine($"GraphQL Schema retrieved from {options.ServiceUrl}. "); 28 | } 29 | 30 | var generatorConfiguration = 31 | new GraphQlGeneratorConfiguration 32 | { 33 | TargetNamespace = options.Namespace, 34 | CSharpVersion = options.CSharpVersion, 35 | ClassPrefix = options.ClassPrefix, 36 | ClassSuffix = options.ClassSuffix, 37 | CodeDocumentationType = options.CodeDocumentationType, 38 | GeneratePartialClasses = options.PartialClasses, 39 | MemberAccessibility = options.MemberAccessibility, 40 | IdTypeMapping = options.IdTypeMapping, 41 | FloatTypeMapping = options.FloatTypeMapping, 42 | IntegerTypeMapping = options.IntegerTypeMapping, 43 | BooleanTypeMapping = options.BooleanTypeMapping, 44 | JsonPropertyGeneration = options.JsonPropertyAttribute, 45 | EnumValueNaming = options.EnumValueNaming, 46 | DataClassMemberNullability = options.DataClassMemberNullability, 47 | GenerationOrder = options.GenerationOrder, 48 | InputObjectMode = options.InputObjectMode, 49 | IncludeDeprecatedFields = options.IncludeDeprecatedFields, 50 | EnableNullableReferences = options.NullableReferences, 51 | FileScopedNamespaces = options.FileScopedNamespaces 52 | }; 53 | 54 | if (!KeyValueParameterParser.TryGetCustomClassMapping(options.ClassMapping, out var customMapping, out var customMappingParsingErrorMessage)) 55 | throw new InvalidOperationException(customMappingParsingErrorMessage); 56 | 57 | foreach (var kvp in customMapping) 58 | generatorConfiguration.CustomClassNameMapping.Add(kvp); 59 | 60 | if (!String.IsNullOrEmpty(options.RegexScalarFieldTypeMappingConfigurationFile)) 61 | { 62 | generatorConfiguration.ScalarFieldTypeMappingProvider = 63 | new RegexScalarFieldTypeMappingProvider( 64 | RegexScalarFieldTypeMappingProvider.ParseRulesFromJson(await File.ReadAllTextAsync(options.RegexScalarFieldTypeMappingConfigurationFile))); 65 | 66 | console.WriteLine($"Scalar field type mapping configuration file {options.RegexScalarFieldTypeMappingConfigurationFile} loaded. "); 67 | } 68 | 69 | var generator = new GraphQlGenerator(generatorConfiguration); 70 | 71 | if (options.OutputType is OutputType.SingleFile) 72 | { 73 | await File.WriteAllTextAsync(options.OutputPath, generator.GenerateFullClientCSharpFile(schema, console.WriteLine)); 74 | console.WriteLine($"File {options.OutputPath} generated successfully ({new FileInfo(options.OutputPath).Length:N0} B). "); 75 | } 76 | else 77 | { 78 | var projectFileInfo = 79 | options.OutputPath is not null && options.OutputPath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase) 80 | ? new FileInfo(options.OutputPath) 81 | : null; 82 | 83 | var codeFileEmitter = new FileSystemEmitter(projectFileInfo?.DirectoryName ?? options.OutputPath); 84 | var multipleFileGenerationContext = 85 | new MultipleFileGenerationContext(schema, codeFileEmitter, projectFileInfo?.Name) 86 | { 87 | LogMessage = console.WriteLine 88 | }; 89 | 90 | generator.Generate(multipleFileGenerationContext); 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /src/GraphQlClientGenerator.Console/GraphQlClientGenerator.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | GraphQL C# Client Generator Console App 5 | A simple strongly typed C# GraphQL client generator console app 6 | Exe 7 | net8.0;net9.0 8 | GraphQL C# Client Generator Console App 9 | GraphQlClientGenerator.Tool 10 | GraphQL Client Generator Tool Console 11 | 12 | 15 | 16 | true 17 | graphql-client-generator 18 | latest 19 | enable 20 | 21 | 22 | 23 | 24 | 25 | true 26 | all 27 | 28 | 29 | 30 | 31 | true 32 | true 33 | $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | True 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/GraphQlClientGenerator.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine.Parsing; 2 | using GraphQlClientGenerator.Console; 3 | 4 | return await Commands.Parser.InvokeAsync(args); -------------------------------------------------------------------------------- /src/GraphQlClientGenerator.Console/ProgramOptions.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQlClientGenerator.Console; 2 | 3 | public class ProgramOptions 4 | { 5 | public string OutputPath { get; set; } 6 | public string Namespace { get; set; } 7 | public bool FileScopedNamespaces { get; set; } 8 | public bool IgnoreServiceUrlCertificateErrors { get; set; } 9 | public string ServiceUrl { get; set; } 10 | public string SchemaFileName { get; set; } 11 | public string HttpMethod { get; set; } 12 | public ICollection Header { get; set; } 13 | public string ClassPrefix { get; set; } 14 | public string ClassSuffix { get; set; } 15 | public CSharpVersion CSharpVersion { get; set; } 16 | public CodeDocumentationType CodeDocumentationType { get; set; } 17 | public MemberAccessibility MemberAccessibility { get; set; } 18 | public OutputType OutputType { get; set; } 19 | public bool PartialClasses { get; set; } 20 | public ICollection ClassMapping { get; set; } 21 | public string RegexScalarFieldTypeMappingConfigurationFile { get; set; } 22 | public IdTypeMapping IdTypeMapping { get; set; } 23 | public FloatTypeMapping FloatTypeMapping { get; set; } 24 | public IntegerTypeMapping IntegerTypeMapping { get; set; } 25 | public BooleanTypeMapping BooleanTypeMapping { get; set; } 26 | public JsonPropertyGenerationOption JsonPropertyAttribute { get; set; } 27 | public EnumValueNamingOption EnumValueNaming { get; set; } 28 | public DataClassMemberNullability DataClassMemberNullability { get; set; } 29 | public GenerationOrder GenerationOrder { get; set; } 30 | public InputObjectMode InputObjectMode { get; set; } 31 | public bool IncludeDeprecatedFields { get; set; } 32 | public bool NullableReferences { get; set; } 33 | } -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/CSharpHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace GraphQlClientGenerator; 4 | 5 | public static class CSharpHelper 6 | { 7 | private static readonly HashSet CSharpKeywords = 8 | [ 9 | "abstract", 10 | "as", 11 | "base", 12 | "bool", 13 | "break", 14 | "byte", 15 | "case", 16 | "catch", 17 | "char", 18 | "checked", 19 | "class", 20 | "const", 21 | "continue", 22 | "decimal", 23 | "default", 24 | "delegate", 25 | "do", 26 | "double", 27 | "else", 28 | "enum", 29 | "event", 30 | "explicit", 31 | "extern", 32 | "false", 33 | "finally", 34 | "fixed", 35 | "float", 36 | "for", 37 | "foreach", 38 | "goto", 39 | "if", 40 | "implicit", 41 | "in", 42 | "int", 43 | "interface", 44 | "internal", 45 | "is", 46 | "lock", 47 | "long", 48 | "namespace", 49 | "new", 50 | "null", 51 | "object", 52 | "operator", 53 | "out", 54 | "override", 55 | "params", 56 | "private", 57 | "protected", 58 | "public", 59 | "readonly", 60 | "ref", 61 | "return", 62 | "sbyte", 63 | "sealed", 64 | "short", 65 | "sizeof", 66 | "stackalloc", 67 | "static", 68 | "string", 69 | "struct", 70 | "switch", 71 | "this", 72 | "throw", 73 | "true", 74 | "try", 75 | "typeof", 76 | "uint", 77 | "ulong", 78 | "unchecked", 79 | "unsafe", 80 | "ushort", 81 | "using", 82 | "void", 83 | "volatile", 84 | "while" 85 | ]; 86 | 87 | public static string EnsureCSharpQuoting(string name) => CSharpKeywords.Contains(name) ? $"@{name}" : name; 88 | 89 | public static bool IsValidIdentifier(string value) 90 | { 91 | var nextMustBeStartChar = true; 92 | 93 | if (value.Length == 0) 94 | return false; 95 | 96 | foreach (var ch in value) 97 | { 98 | var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(ch); 99 | switch (unicodeCategory) 100 | { 101 | case UnicodeCategory.UppercaseLetter: 102 | case UnicodeCategory.LowercaseLetter: 103 | case UnicodeCategory.TitlecaseLetter: 104 | case UnicodeCategory.ModifierLetter: 105 | case UnicodeCategory.LetterNumber: 106 | case UnicodeCategory.OtherLetter: 107 | nextMustBeStartChar = false; 108 | break; 109 | 110 | case UnicodeCategory.NonSpacingMark: 111 | case UnicodeCategory.SpacingCombiningMark: 112 | case UnicodeCategory.ConnectorPunctuation: 113 | case UnicodeCategory.DecimalDigitNumber: 114 | if (nextMustBeStartChar && ch != '_') 115 | return false; 116 | 117 | nextMustBeStartChar = false; 118 | break; 119 | default: 120 | return false; 121 | } 122 | } 123 | 124 | return true; 125 | } 126 | 127 | public static bool IsValidNamespace(string @namespace) 128 | { 129 | if (String.IsNullOrWhiteSpace(@namespace)) 130 | return false; 131 | 132 | var namespaceElements = @namespace.Split('.'); 133 | return namespaceElements.All(e => IsValidIdentifier(e.Trim())); 134 | } 135 | 136 | public static void ValidateClassName(string className) 137 | { 138 | if (!IsValidIdentifier(className)) 139 | throw new InvalidOperationException($"Resulting class name \"{className}\" is not valid. "); 140 | } 141 | 142 | internal static bool IsTargetTypedNewSupported(this CSharpVersion cSharpVersion) => 143 | cSharpVersion >= CSharpVersion.CSharp12; 144 | 145 | internal static bool IsCollectionExpressionSupported(this CSharpVersion cSharpVersion) => 146 | cSharpVersion >= CSharpVersion.CSharp12; 147 | 148 | internal static bool IsSystemTextJsonSupported(this CSharpVersion cSharpVersion) => 149 | cSharpVersion >= CSharpVersion.CSharp12; 150 | 151 | internal static bool IsFieldKeywordSupported(this CSharpVersion cSharpVersion) => 152 | cSharpVersion >= CSharpVersion.CSharp12; 153 | } -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/Extensions.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQlClientGenerator; 2 | 3 | public static class Extensions 4 | { 5 | public static GraphQlFieldType UnwrapIfNonNull(this GraphQlFieldType graphQlType) => 6 | graphQlType.Kind == GraphQlTypeKind.NonNull ? graphQlType.OfType : graphQlType; 7 | 8 | internal static bool IsComplex(this GraphQlTypeKind graphQlTypeKind) => 9 | graphQlTypeKind is GraphQlTypeKind.Object or GraphQlTypeKind.Interface or GraphQlTypeKind.Union; 10 | 11 | internal static IEnumerable GetComplexTypes(this GraphQlSchema schema) => 12 | schema.Types.Where(t => t.Kind.IsComplex() && !t.IsBuiltIn()); 13 | 14 | internal static IEnumerable GetInputObjectTypes(this GraphQlSchema schema) => 15 | schema.Types.Where(t => t.Kind == GraphQlTypeKind.InputObject && !t.IsBuiltIn()); 16 | 17 | internal static bool IsBuiltIn(this GraphQlType graphQlType) => graphQlType.Name is not null && graphQlType.Name.StartsWith("__"); 18 | 19 | internal static string ToSetterAccessibilityPrefix(this PropertyAccessibility accessibility) => 20 | accessibility switch 21 | { 22 | PropertyAccessibility.Public => null, 23 | PropertyAccessibility.Protected => "protected ", 24 | PropertyAccessibility.Internal => "internal ", 25 | PropertyAccessibility.ProtectedInternal => "protected internal ", 26 | PropertyAccessibility.Private => "private ", 27 | _ => throw new NotSupportedException() 28 | }; 29 | 30 | internal static string EscapeXmlElementText(this string text) => text?.Replace("&", "&").Replace("<", "<").Replace(">", ">"); 31 | } -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/GraphQlClientGenerator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | GraphQL C# Client Generator 5 | A simple strongly typed C# GraphQL client generator library 6 | true 7 | $(NoWarn);1591;RS1035 8 | netstandard2.0 9 | GraphQL C# Client Generator 10 | GraphQL Client Generator 11 | 12 | 15 | 16 | true 17 | true 18 | latest 19 | enable 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | true 31 | true 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | True 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/GraphQlClientSourceGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using Microsoft.CodeAnalysis.Text; 6 | 7 | namespace GraphQlClientGenerator; 8 | 9 | [Generator] 10 | public class GraphQlClientSourceGenerator : ISourceGenerator 11 | { 12 | private const string ApplicationCode = "GRAPHQLGEN"; 13 | private const string FileNameGraphQlClientSource = "GraphQlClient.cs"; 14 | private const string FileNameRegexScalarFieldTypeMappingProviderConfiguration = "RegexScalarFieldTypeMappingProvider.gql.config.json"; 15 | private const string BuildPropertyKeyPrefix = "build_property.GraphQlClientGenerator_"; 16 | 17 | private static readonly DiagnosticDescriptor DescriptorParameterError = CreateDiagnosticDescriptor(DiagnosticSeverity.Error, 1000); 18 | private static readonly DiagnosticDescriptor DescriptorGenerationError = CreateDiagnosticDescriptor(DiagnosticSeverity.Error, 1001); 19 | private static readonly DiagnosticDescriptor DescriptorInfo = CreateDiagnosticDescriptor(DiagnosticSeverity.Info, 3000); 20 | 21 | public void Initialize(GeneratorInitializationContext context) 22 | { 23 | } 24 | 25 | public void Execute(GeneratorExecutionContext context) 26 | { 27 | if (context.Compilation is not CSharpCompilation compilation) 28 | { 29 | context.ReportDiagnostic(Diagnostic.Create(DescriptorParameterError, Location.None, $"incompatible language: {context.Compilation.Language}")); 30 | return; 31 | } 32 | 33 | try 34 | { 35 | context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKey("ServiceUrl"), out var serviceUrl); 36 | var isServiceUrlMissing = String.IsNullOrWhiteSpace(serviceUrl); 37 | var graphQlSchemaFiles = context.AdditionalFiles.Where(f => Path.GetFileName(f.Path).EndsWith(".gql.schema.json", StringComparison.OrdinalIgnoreCase)).ToList(); 38 | var regexScalarFieldTypeMappingProviderConfigurationJson = 39 | context.AdditionalFiles 40 | .SingleOrDefault(f => String.Equals(Path.GetFileName(f.Path), FileNameRegexScalarFieldTypeMappingProviderConfiguration, StringComparison.OrdinalIgnoreCase)) 41 | ?.GetText() 42 | ?.ToString(); 43 | 44 | var regexScalarFieldTypeMappingProviderRules = 45 | regexScalarFieldTypeMappingProviderConfigurationJson is not null 46 | ? RegexScalarFieldTypeMappingProvider.ParseRulesFromJson(regexScalarFieldTypeMappingProviderConfigurationJson) 47 | : null; 48 | 49 | var isSchemaFileSpecified = graphQlSchemaFiles.Any(); 50 | if (isServiceUrlMissing && !isSchemaFileSpecified) 51 | { 52 | context.ReportDiagnostic( 53 | Diagnostic.Create( 54 | DescriptorInfo, 55 | Location.None, 56 | "Neither \"GraphQlClientGenerator_ServiceUrl\" parameter nor GraphQL JSON schema additional file specified; terminating. ")); 57 | 58 | return; 59 | } 60 | 61 | if (!isServiceUrlMissing && isSchemaFileSpecified) 62 | { 63 | context.ReportDiagnostic( 64 | Diagnostic.Create( 65 | DescriptorParameterError, 66 | Location.None, 67 | "\"GraphQlClientGenerator_ServiceUrl\" parameter and GraphQL JSON schema additional file are mutually exclusive. ")); 68 | 69 | return; 70 | } 71 | 72 | context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKey("Namespace"), out var @namespace); 73 | if (String.IsNullOrWhiteSpace(@namespace)) 74 | { 75 | var root = (CompilationUnitSyntax)compilation.SyntaxTrees.FirstOrDefault()?.GetRoot(); 76 | var namespaceIdentifier = (IdentifierNameSyntax)root?.Members.OfType().FirstOrDefault()?.Name; 77 | if (namespaceIdentifier is null) 78 | { 79 | context.ReportDiagnostic(Diagnostic.Create(DescriptorParameterError, Location.None, "\"GraphQlClientGenerator_Namespace\" required")); 80 | return; 81 | } 82 | 83 | @namespace = namespaceIdentifier.Identifier.ValueText; 84 | 85 | context.ReportDiagnostic(Diagnostic.Create(DescriptorInfo, Location.None, $"\"GraphQlClientGenerator_Namespace\" not specified; using \"{@namespace}\"")); 86 | } 87 | 88 | var configuration = new GraphQlGeneratorConfiguration { TargetNamespace = @namespace }; 89 | 90 | context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKey(nameof(configuration.ClassPrefix)), out var classPrefix); 91 | configuration.ClassPrefix = classPrefix; 92 | 93 | context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKey(nameof(configuration.ClassSuffix)), out var classSuffix); 94 | configuration.ClassSuffix = classSuffix; 95 | 96 | if (compilation.LanguageVersion >= LanguageVersion.CSharp12) 97 | configuration.CSharpVersion = CSharpVersion.CSharp12; 98 | else if (compilation.LanguageVersion >= LanguageVersion.CSharp6) 99 | configuration.CSharpVersion = CSharpVersion.CSharp6; 100 | 101 | context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKey(nameof(configuration.IncludeDeprecatedFields)), out var includeDeprecatedFieldsRaw); 102 | configuration.IncludeDeprecatedFields = Boolean.TryParse(includeDeprecatedFieldsRaw, out var includeDeprecatedFields) && includeDeprecatedFields; 103 | 104 | context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKey(nameof(configuration.EnableNullableReferences)), out var enableNullableReferencesRaw); 105 | configuration.EnableNullableReferences = Boolean.TryParse(enableNullableReferencesRaw, out var enableNullableReferences) && enableNullableReferences; 106 | 107 | if (configuration.EnableNullableReferences && compilation.Options.NullableContextOptions is NullableContextOptions.Disable) 108 | { 109 | context.ReportDiagnostic(Diagnostic.Create(DescriptorInfo, Location.None, "compilation nullable references disabled")); 110 | configuration.EnableNullableReferences = false; 111 | } 112 | 113 | if (!context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKey("HttpMethod"), out var httpMethod)) 114 | httpMethod = "POST"; 115 | 116 | SetConfigurationEnumValue(context, nameof(CodeDocumentationType), CodeDocumentationType.XmlSummary, v => configuration.CodeDocumentationType = v); 117 | SetConfigurationEnumValue(context, nameof(FloatTypeMapping), FloatTypeMapping.Decimal, v => configuration.FloatTypeMapping = v); 118 | SetConfigurationEnumValue(context, nameof(BooleanTypeMapping), BooleanTypeMapping.Boolean, v => configuration.BooleanTypeMapping = v); 119 | SetConfigurationEnumValue(context, nameof(IdTypeMapping), IdTypeMapping.Guid, v => configuration.IdTypeMapping = v); 120 | SetConfigurationEnumValue(context, "JsonPropertyGeneration", JsonPropertyGenerationOption.CaseInsensitive, v => configuration.JsonPropertyGeneration = v); 121 | SetConfigurationEnumValue(context, "EnumValueNaming", EnumValueNamingOption.CSharp, v => configuration.EnumValueNaming = v); 122 | SetConfigurationEnumValue(context, nameof(DataClassMemberNullability), DataClassMemberNullability.AlwaysNullable, v => configuration.DataClassMemberNullability = v); 123 | SetConfigurationEnumValue(context, nameof(GenerationOrder), GenerationOrder.DefinedBySchema, v => configuration.GenerationOrder = v); 124 | SetConfigurationEnumValue(context, nameof(InputObjectMode), InputObjectMode.Rich, v => configuration.InputObjectMode = v); 125 | 126 | var outputType = OutputType.SingleFile; 127 | SetConfigurationEnumValue(context, nameof(OutputType), OutputType.SingleFile, v => outputType = v); 128 | 129 | context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKey("CustomClassMapping"), out var customClassMappingRaw); 130 | if (!KeyValueParameterParser.TryGetCustomClassMapping( 131 | customClassMappingRaw?.Split(['|', ';', ' '], StringSplitOptions.RemoveEmptyEntries), 132 | out var customMapping, 133 | out var customMappingParsingErrorMessage)) 134 | { 135 | context.ReportDiagnostic(Diagnostic.Create(DescriptorParameterError, Location.None, customMappingParsingErrorMessage)); 136 | return; 137 | } 138 | 139 | foreach (var kvp in customMapping) 140 | configuration.CustomClassNameMapping.Add(kvp); 141 | 142 | context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKey("Headers"), out var headersRaw); 143 | if (!KeyValueParameterParser.TryGetCustomHeaders( 144 | headersRaw?.Split(['|'], StringSplitOptions.RemoveEmptyEntries), 145 | out var headers, 146 | out var headerParsingErrorMessage)) 147 | { 148 | context.ReportDiagnostic(Diagnostic.Create(DescriptorParameterError, Location.None, headerParsingErrorMessage)); 149 | return; 150 | } 151 | 152 | if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKey("ScalarFieldTypeMappingProvider"), out var scalarFieldTypeMappingProviderName)) 153 | { 154 | if (regexScalarFieldTypeMappingProviderRules is not null) 155 | { 156 | context.ReportDiagnostic(Diagnostic.Create(DescriptorParameterError, Location.None, "\"GraphQlClientGenerator_ScalarFieldTypeMappingProvider\" and RegexScalarFieldTypeMappingProviderConfiguration are mutually exclusive")); 157 | return; 158 | } 159 | 160 | if (String.IsNullOrWhiteSpace(scalarFieldTypeMappingProviderName)) 161 | { 162 | context.ReportDiagnostic(Diagnostic.Create(DescriptorParameterError, Location.None, "\"GraphQlClientGenerator_ScalarFieldTypeMappingProvider\" value missing")); 163 | return; 164 | } 165 | 166 | var scalarFieldTypeMappingProviderType = Type.GetType(scalarFieldTypeMappingProviderName); 167 | if (scalarFieldTypeMappingProviderType is null) 168 | { 169 | context.ReportDiagnostic(Diagnostic.Create(DescriptorParameterError, Location.None, $"ScalarFieldTypeMappingProvider \"{scalarFieldTypeMappingProviderName}\" not found")); 170 | return; 171 | } 172 | 173 | var scalarFieldTypeMappingProvider = (IScalarFieldTypeMappingProvider)Activator.CreateInstance(scalarFieldTypeMappingProviderType); 174 | configuration.ScalarFieldTypeMappingProvider = scalarFieldTypeMappingProvider; 175 | } 176 | else if (regexScalarFieldTypeMappingProviderRules?.Count > 0) 177 | configuration.ScalarFieldTypeMappingProvider = new RegexScalarFieldTypeMappingProvider(regexScalarFieldTypeMappingProviderRules); 178 | 179 | var graphQlSchemas = new List<(string TargetFileName, GraphQlSchema Schema)>(); 180 | if (isSchemaFileSpecified) 181 | { 182 | foreach (var schemaFile in graphQlSchemaFiles) 183 | { 184 | var targetFileName = $"{Path.GetFileNameWithoutExtension(schemaFile.Path)}.cs"; 185 | graphQlSchemas.Add((targetFileName, GraphQlGenerator.DeserializeGraphQlSchema(schemaFile.GetText().ToString()))); 186 | } 187 | } 188 | else 189 | { 190 | using var httpClientHandler = GraphQlGenerator.CreateDefaultHttpClientHandler(); 191 | var ignoreServiceUrlCertificateErrors = 192 | context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKey("IgnoreServiceUrlCertificateErrors"), out var ignoreServiceUrlCertificateErrorsRaw) && 193 | !String.IsNullOrWhiteSpace(ignoreServiceUrlCertificateErrorsRaw) && Convert.ToBoolean(ignoreServiceUrlCertificateErrorsRaw); 194 | 195 | if (ignoreServiceUrlCertificateErrors) 196 | httpClientHandler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true; 197 | 198 | var graphQlSchema = 199 | GraphQlGenerator.RetrieveSchema(new HttpMethod(httpMethod), serviceUrl, headers, httpClientHandler, GraphQlWellKnownDirective.None) 200 | .GetAwaiter() 201 | .GetResult(); 202 | 203 | graphQlSchemas.Add((FileNameGraphQlClientSource, graphQlSchema)); 204 | context.ReportDiagnostic(Diagnostic.Create(DescriptorInfo, Location.None, $"GraphQl schema fetched successfully from {serviceUrl}")); 205 | } 206 | 207 | context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKey(nameof(configuration.FileScopedNamespaces)), out var fileScopedNamespacesRaw); 208 | configuration.FileScopedNamespaces = !String.IsNullOrWhiteSpace(fileScopedNamespacesRaw) && Convert.ToBoolean(fileScopedNamespacesRaw); 209 | 210 | var generator = new GraphQlGenerator(configuration); 211 | 212 | foreach (var (targetFileName, schema) in graphQlSchemas) 213 | { 214 | if (outputType is OutputType.SingleFile) 215 | { 216 | var builder = new StringBuilder(); 217 | using (var writer = new StringWriter(builder)) 218 | generator.WriteFullClientCSharpFile(schema, writer); 219 | 220 | context.AddSource(targetFileName, SourceText.From(builder.ToString(), Encoding.UTF8)); 221 | } 222 | else 223 | { 224 | var multipleFileGenerationContext = new MultipleFileGenerationContext(schema, new SourceGeneratorFileEmitter(context)); 225 | generator.Generate(multipleFileGenerationContext); 226 | } 227 | } 228 | 229 | context.ReportDiagnostic(Diagnostic.Create(DescriptorInfo, Location.None, "GraphQlClientGenerator task completed successfully. ")); 230 | } 231 | catch (Exception exception) 232 | { 233 | context.ReportDiagnostic(Diagnostic.Create(DescriptorGenerationError, Location.None, exception.Message)); 234 | } 235 | } 236 | 237 | private static void SetConfigurationEnumValue( 238 | GeneratorExecutionContext context, 239 | string parameterName, 240 | TEnum defaultValue, 241 | Action valueSetter) where TEnum : Enum 242 | { 243 | context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(BuildPropertyKey(parameterName), out var enumStringValue); 244 | var value = 245 | String.IsNullOrWhiteSpace(enumStringValue) 246 | ? defaultValue 247 | : (TEnum)Enum.Parse(typeof(TEnum), enumStringValue, true); 248 | 249 | valueSetter(value); 250 | } 251 | 252 | private static string BuildPropertyKey(string parameterName) => $"{BuildPropertyKeyPrefix}{parameterName}"; 253 | 254 | private static DiagnosticDescriptor CreateDiagnosticDescriptor(DiagnosticSeverity severity, int code) => 255 | new( 256 | $"{ApplicationCode}{code}", 257 | $"{severity} {ApplicationCode}{code}", 258 | "{0}", 259 | "GraphQlClientGenerator", 260 | severity, 261 | true); 262 | } 263 | 264 | public class SourceGeneratorFileEmitter(GeneratorExecutionContext sourceGeneratorContext) : ICodeFileEmitter 265 | { 266 | public CodeFile CreateFile(string fileName) => new(fileName, new MemoryStream()); 267 | 268 | public CodeFileInfo CollectFileInfo(CodeFile codeFile) 269 | { 270 | if (codeFile.Stream is not MemoryStream memoryStream) 271 | throw new ArgumentException($"File was not created by {nameof(SourceGeneratorFileEmitter)}.", nameof(codeFile)); 272 | 273 | codeFile.Writer.Flush(); 274 | sourceGeneratorContext.AddSource(codeFile.FileName, SourceText.From(Encoding.UTF8.GetString(memoryStream.ToArray()), Encoding.UTF8)); 275 | var fileSize = (int)codeFile.Stream.Length; 276 | codeFile.Dispose(); 277 | return new CodeFileInfo { FileName = codeFile.FileName, Length = fileSize }; 278 | } 279 | } -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/GraphQlGeneratorConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQlClientGenerator; 2 | 3 | public class GraphQlGeneratorConfiguration 4 | { 5 | private string _targetNamespace = "GraphQlApi"; 6 | 7 | public CSharpVersion CSharpVersion { get; set; } 8 | 9 | public string ClassPrefix { get; set; } 10 | 11 | public string ClassSuffix { get; set; } 12 | 13 | public string TargetNamespace 14 | 15 | { 16 | get => _targetNamespace; 17 | set 18 | { 19 | if (!CSharpHelper.IsValidNamespace(value)) 20 | throw new ArgumentException($"namespace \"{value}\" is not valid. "); 21 | 22 | _targetNamespace = value; 23 | } 24 | } 25 | 26 | /// 27 | /// Allows to define custom class names for specific GraphQL types. 28 | /// 29 | public IDictionary CustomClassNameMapping { get; } = new Dictionary(); 30 | 31 | public CodeDocumentationType CodeDocumentationType { get; set; } 32 | 33 | public bool IncludeDeprecatedFields { get; set; } 34 | 35 | public bool EnableNullableReferences { get; set; } 36 | 37 | public bool GeneratePartialClasses { get; set; } = true; 38 | 39 | /// 40 | /// Determines the .NET type generated for GraphQL Integer data type. 41 | /// 42 | /// For using custom .NET data type Custom option must be used. 43 | public IntegerTypeMapping IntegerTypeMapping { get; set; } = IntegerTypeMapping.Int32; 44 | 45 | /// 46 | /// Determines the .NET type generated for GraphQL Float data type. 47 | /// 48 | /// For using custom .NET data type Custom option must be used. 49 | public FloatTypeMapping FloatTypeMapping { get; set; } 50 | 51 | /// 52 | /// Determines the .NET type generated for GraphQL Boolean data type. 53 | /// 54 | /// For using custom .NET data type Custom option must be used. 55 | public BooleanTypeMapping BooleanTypeMapping { get; set; } 56 | 57 | /// 58 | /// Determines the .NET type generated for GraphQL ID data type. 59 | /// 60 | /// For using custom .NET data type Custom option must be used. 61 | public IdTypeMapping IdTypeMapping { get; set; } = IdTypeMapping.Guid; 62 | 63 | public PropertyGenerationOption PropertyGeneration { get; set; } = PropertyGenerationOption.AutoProperty; 64 | 65 | public JsonPropertyGenerationOption JsonPropertyGeneration { get; set; } = JsonPropertyGenerationOption.CaseInsensitive; 66 | 67 | public EnumValueNamingOption EnumValueNaming { get; set; } 68 | 69 | /// 70 | /// Determines builder class, data class and interfaces accessibility level. 71 | /// 72 | public MemberAccessibility MemberAccessibility { get; set; } 73 | 74 | /// 75 | /// This property is used for mapping GraphQL scalar type into specific .NET type. By default, any custom GraphQL scalar type is mapped into . 76 | /// 77 | public IScalarFieldTypeMappingProvider ScalarFieldTypeMappingProvider { get; set; } 78 | 79 | public bool FileScopedNamespaces { get; set; } 80 | 81 | public DataClassMemberNullability DataClassMemberNullability { get; set; } 82 | 83 | public GenerationOrder GenerationOrder { get; set; } 84 | 85 | public InputObjectMode InputObjectMode { get; set; } 86 | 87 | public GraphQlGeneratorConfiguration() => Reset(); 88 | 89 | public void Reset() 90 | { 91 | ClassPrefix = null; 92 | ClassSuffix = null; 93 | CustomClassNameMapping.Clear(); 94 | CSharpVersion = CSharpVersion.Compatible; 95 | ScalarFieldTypeMappingProvider = DefaultScalarFieldTypeMappingProvider.Instance; 96 | CodeDocumentationType = CodeDocumentationType.Disabled; 97 | IncludeDeprecatedFields = false; 98 | EnableNullableReferences = false; 99 | FloatTypeMapping = FloatTypeMapping.Decimal; 100 | BooleanTypeMapping = BooleanTypeMapping.Boolean; 101 | IntegerTypeMapping = IntegerTypeMapping.Int32; 102 | IdTypeMapping = IdTypeMapping.Guid; 103 | GeneratePartialClasses = true; 104 | MemberAccessibility = MemberAccessibility.Public; 105 | JsonPropertyGeneration = JsonPropertyGenerationOption.CaseInsensitive; 106 | PropertyGeneration = PropertyGenerationOption.AutoProperty; 107 | EnumValueNaming = EnumValueNamingOption.CSharp; 108 | FileScopedNamespaces = false; 109 | DataClassMemberNullability = DataClassMemberNullability.AlwaysNullable; 110 | GenerationOrder = GenerationOrder.DefinedBySchema; 111 | InputObjectMode = InputObjectMode.Rich; 112 | } 113 | } 114 | 115 | public enum EnumValueNamingOption 116 | { 117 | CSharp, 118 | Original 119 | } 120 | 121 | public enum CSharpVersion 122 | { 123 | Compatible, 124 | CSharp6, 125 | CSharp12 126 | } 127 | 128 | public enum FloatTypeMapping 129 | { 130 | Decimal, 131 | Float, 132 | Double, 133 | Custom 134 | } 135 | 136 | public enum BooleanTypeMapping 137 | { 138 | Boolean, 139 | Custom 140 | } 141 | 142 | public enum IntegerTypeMapping 143 | { 144 | Int16, 145 | Int32, 146 | Int64, 147 | Custom 148 | } 149 | 150 | public enum IdTypeMapping 151 | { 152 | Guid, 153 | String, 154 | Object, 155 | Custom 156 | } 157 | 158 | public enum MemberAccessibility 159 | { 160 | Public, 161 | Internal 162 | } 163 | 164 | public enum JsonPropertyGenerationOption 165 | { 166 | Never, 167 | Always, 168 | UseDefaultAlias, 169 | CaseInsensitive, 170 | CaseSensitive 171 | } 172 | 173 | public enum PropertyGenerationOption 174 | { 175 | AutoProperty, 176 | BackingField 177 | } 178 | 179 | [Flags] 180 | public enum CodeDocumentationType 181 | { 182 | Disabled = 0, 183 | XmlSummary = 1, 184 | DescriptionAttribute = 2 185 | } 186 | 187 | public enum DataClassMemberNullability 188 | { 189 | AlwaysNullable, 190 | DefinedBySchema 191 | } 192 | 193 | public enum GenerationOrder 194 | { 195 | DefinedBySchema, 196 | Alphabetical 197 | } 198 | 199 | public enum InputObjectMode 200 | { 201 | /// 202 | /// Supports GraphQL parameter references and explicit nulls 203 | /// 204 | Rich, 205 | Poco 206 | } -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/GraphQlIntrospection.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQlClientGenerator; 2 | 3 | public static class GraphQlIntrospection 4 | { 5 | public const string QuerySupportedDirectives = 6 | """ 7 | query DirectiveIntrospection { 8 | __schema { 9 | directives { 10 | name 11 | } 12 | } 13 | } 14 | """; 15 | 16 | public static string QuerySchemaMetadata(GraphQlWellKnownDirective directive) => 17 | $$""" 18 | query FullIntrospection { 19 | __schema { 20 | queryType { name } 21 | mutationType { name } 22 | subscriptionType { name } 23 | types { 24 | ...FullType 25 | } 26 | directives { 27 | name 28 | description 29 | locations 30 | args { 31 | ...InputValue 32 | } 33 | } 34 | } 35 | } 36 | 37 | fragment FullType on __Type { 38 | kind 39 | name 40 | description 41 | fields(includeDeprecated: true) { 42 | name 43 | description 44 | args { 45 | ...InputValue 46 | } 47 | type { 48 | ...TypeRef 49 | } 50 | isDeprecated 51 | deprecationReason 52 | } 53 | inputFields { 54 | ...InputValue 55 | } 56 | interfaces { 57 | ...TypeRef 58 | } 59 | enumValues(includeDeprecated: true) { 60 | name 61 | description 62 | isDeprecated 63 | deprecationReason 64 | } 65 | possibleTypes { 66 | ...TypeRef 67 | }{{(directive.HasFlag(GraphQlWellKnownDirective.OneOf) ? $"{Environment.NewLine}isOneOf" : null)}} 68 | } 69 | 70 | fragment InputValue on __InputValue { 71 | name 72 | description 73 | type { ...TypeRef } 74 | defaultValue 75 | } 76 | 77 | fragment TypeRef on __Type { 78 | kind 79 | name 80 | ofType { 81 | kind 82 | name 83 | ofType { 84 | kind 85 | name 86 | ofType { 87 | kind 88 | name 89 | ofType { 90 | kind 91 | name 92 | ofType { 93 | kind 94 | name 95 | ofType { 96 | kind 97 | name 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } 105 | """; 106 | } 107 | 108 | [Flags] 109 | public enum GraphQlWellKnownDirective 110 | { 111 | None = 0, 112 | OneOf = 1 113 | } -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/GraphQlIntrospectionSchema.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.Serialization; 3 | using Newtonsoft.Json; 4 | 5 | namespace GraphQlClientGenerator; 6 | 7 | public class GraphQlResult 8 | { 9 | public GraphQlData Data { get; set; } 10 | } 11 | 12 | public class GraphQlData 13 | { 14 | [JsonProperty("__schema")] 15 | public GraphQlSchema Schema { get; set; } 16 | } 17 | 18 | public class GraphQlSchema 19 | { 20 | public GraphQlRequestType QueryType { get; set; } 21 | public IList Types { get; set; } 22 | public IList Directives { get; set; } 23 | public GraphQlRequestType MutationType { get; set; } 24 | public GraphQlRequestType SubscriptionType { get; set; } 25 | } 26 | 27 | [DebuggerDisplay($"{nameof(GraphQlDirective)} ({nameof(Name)}={{{nameof(Name)},nq}}; {nameof(Description)}={{{nameof(Description)},nq}})")] 28 | public class GraphQlDirective : GraphQlTypeBase 29 | { 30 | public string Description { get; set; } 31 | public ICollection Locations { get; set; } 32 | public IList Args { get; set; } 33 | } 34 | 35 | public class GraphQlRequestType 36 | { 37 | public string Name { get; set; } 38 | } 39 | 40 | [DebuggerDisplay($"{nameof(GraphQlType)} ({nameof(Name)}={{{nameof(Name)},nq}}; {nameof(Kind)}={{{nameof(Kind)}}}; {nameof(Description)}={{{nameof(Description)},nq}})")] 41 | public class GraphQlType : GraphQlTypeBase 42 | { 43 | public string Description { get; set; } 44 | public IList Fields { get; set; } 45 | public IList InputFields { get; set; } 46 | public IList Interfaces { get; set; } 47 | public IList EnumValues { get; set; } 48 | public IList PossibleTypes { get; set; } 49 | } 50 | 51 | public abstract class GraphQlValueBase 52 | { 53 | public string Name { get; set; } 54 | public string Description { get; set; } 55 | } 56 | 57 | [DebuggerDisplay($"{nameof(GraphQlEnumValue)} ({nameof(Name)}={{{nameof(Name)},nq}}; {nameof(Description)}={{{nameof(Description)},nq}})")] 58 | public class GraphQlEnumValue : GraphQlValueBase 59 | { 60 | public bool IsDeprecated { get; set; } 61 | public string DeprecationReason { get; set; } 62 | } 63 | 64 | [DebuggerDisplay($"{nameof(GraphQlField)} ({nameof(Name)}={{{nameof(Name)},nq}}; {nameof(Description)}={{{nameof(Description)},nq}})")] 65 | public class GraphQlField : GraphQlEnumValue, IGraphQlMember 66 | { 67 | public IList Args { get; set; } 68 | public GraphQlFieldType Type { get; set; } 69 | } 70 | 71 | [DebuggerDisplay($"{nameof(GraphQlArgument)} ({nameof(Name)}={{{nameof(Name)},nq}}; {nameof(Description)}={{{nameof(Description)},nq}})")] 72 | public class GraphQlArgument : GraphQlValueBase, IGraphQlMember 73 | { 74 | public GraphQlFieldType Type { get; set; } 75 | public object DefaultValue { get; set; } 76 | } 77 | 78 | [DebuggerDisplay($"{nameof(GraphQlFieldType)} ({nameof(Name)}={{{nameof(Name)},nq}}; {nameof(Kind)}={{{nameof(Kind)}}})")] 79 | public class GraphQlFieldType : GraphQlTypeBase 80 | { 81 | public GraphQlFieldType OfType { get; set; } 82 | 83 | private bool Equals(GraphQlFieldType other) => 84 | Kind == other.Kind && Name == other.Name && (OfType is null && other.OfType is null || OfType is not null && other.OfType is not null && OfType.Equals(other.OfType)); 85 | 86 | public override bool Equals(object obj) 87 | { 88 | if (obj is null) 89 | return false; 90 | 91 | if (ReferenceEquals(this, obj)) 92 | return true; 93 | 94 | return obj.GetType() == GetType() && Equals((GraphQlFieldType)obj); 95 | } 96 | 97 | public override int GetHashCode() 98 | { 99 | unchecked 100 | { 101 | var hashCode = OfType is null ? 0 : OfType.GetHashCode(); 102 | hashCode = (hashCode * 397) ^ (int)Kind; 103 | hashCode = (hashCode * 397) ^ (Name is null ? 0 : Name.GetHashCode()); 104 | return hashCode; 105 | } 106 | } 107 | } 108 | 109 | public abstract class GraphQlTypeBase 110 | { 111 | public const string GraphQlTypeScalarBoolean = "Boolean"; 112 | public const string GraphQlTypeScalarFloat = "Float"; 113 | public const string GraphQlTypeScalarId = "ID"; 114 | public const string GraphQlTypeScalarInteger = "Int"; 115 | public const string GraphQlTypeScalarString = "String"; 116 | 117 | public GraphQlTypeKind Kind { get; set; } 118 | public string Name { get; set; } 119 | 120 | [JsonExtensionData] 121 | public IDictionary Extensions { get; } = new Dictionary(StringComparer.Ordinal); 122 | } 123 | 124 | public interface IGraphQlMember 125 | { 126 | string Name { get; } 127 | string Description { get; } 128 | GraphQlFieldType Type { get; } 129 | } 130 | 131 | public enum GraphQlDirectiveLocation 132 | { 133 | /// 134 | /// Location adjacent to a query operation. 135 | /// 136 | [EnumMember(Value = "QUERY")] Query, 137 | /// 138 | /// Location adjacent to a mutation operation. 139 | /// 140 | [EnumMember(Value = "MUTATION")] Mutation, 141 | /// 142 | /// Location adjacent to a subscription operation. 143 | /// 144 | [EnumMember(Value = "SUBSCRIPTION")] Subscription, 145 | /// 146 | /// Location adjacent to a field. 147 | /// 148 | [EnumMember(Value = "FIELD")] Field, 149 | /// 150 | /// Location adjacent to a fragment definition. 151 | /// 152 | [EnumMember(Value = "FRAGMENT_DEFINITION")] FragmentDefinition, 153 | /// 154 | /// Location adjacent to a fragment spread. 155 | /// 156 | [EnumMember(Value = "FRAGMENT_SPREAD")] FragmentSpread, 157 | /// 158 | /// Location adjacent to an inline fragment. 159 | /// 160 | [EnumMember(Value = "INLINE_FRAGMENT")] InlineFragment, 161 | /// 162 | /// Location adjacent to a variable definition. 163 | /// 164 | [EnumMember(Value = "VARIABLE_DEFINITION")] VariableDefinition, 165 | /// 166 | /// Location adjacent to a schema definition. 167 | /// 168 | [EnumMember(Value = "SCHEMA")] Schema, 169 | /// 170 | /// Location adjacent to a scalar definition. 171 | /// 172 | [EnumMember(Value = "SCALAR")] Scalar, 173 | /// 174 | /// Location adjacent to an object type definition. 175 | /// 176 | [EnumMember(Value = "OBJECT")] Object, 177 | /// 178 | /// Location adjacent to a field definition. 179 | /// 180 | [EnumMember(Value = "FIELD_DEFINITION")] FieldDefinition, 181 | /// 182 | /// Location adjacent to an argument definition. 183 | /// 184 | [EnumMember(Value = "ARGUMENT_DEFINITION")] ArgumentDefinition, 185 | /// 186 | /// Location adjacent to an interface definition. 187 | /// 188 | [EnumMember(Value = "INTERFACE")] Interface, 189 | /// 190 | /// Location adjacent to a union definition. 191 | /// 192 | [EnumMember(Value = "UNION")] Union, 193 | /// 194 | /// Location adjacent to an enum definition. 195 | /// 196 | [EnumMember(Value = "ENUM")] Enum, 197 | /// 198 | /// Location adjacent to an enum value definition. 199 | /// 200 | [EnumMember(Value = "ENUM_VALUE")] EnumValue, 201 | /// 202 | /// Location adjacent to an input object type definition. 203 | /// 204 | [EnumMember(Value = "INPUT_OBJECT")] InputObject, 205 | /// 206 | /// Location adjacent to an input field definition. 207 | /// 208 | [EnumMember(Value = "INPUT_FIELD_DEFINITION")] InputFieldDefinition 209 | } -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/GraphQlTypeKind.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace GraphQlClientGenerator; 4 | 5 | public enum GraphQlTypeKind 6 | { 7 | [EnumMember(Value = "SCALAR")] Scalar, 8 | [EnumMember(Value = "ENUM")] Enum, 9 | [EnumMember(Value = "OBJECT")] Object, 10 | [EnumMember(Value = "INPUT_OBJECT")] InputObject, 11 | [EnumMember(Value = "UNION")] Union, 12 | [EnumMember(Value = "INTERFACE")] Interface, 13 | [EnumMember(Value = "LIST")] List, 14 | [EnumMember(Value = "NON_NULL")] NonNull 15 | } -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/ICodeFileEmitter.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace GraphQlClientGenerator; 4 | 5 | public interface ICodeFileEmitter 6 | { 7 | CodeFile CreateFile(string memberName); 8 | 9 | CodeFileInfo CollectFileInfo(CodeFile codeFile); 10 | } 11 | 12 | public struct CodeFileInfo 13 | { 14 | public string FileName { get; set; } 15 | 16 | public long Length { get; set; } 17 | } 18 | 19 | public class CodeFile : IDisposable 20 | { 21 | private Stream _stream; 22 | private StreamWriter _writer; 23 | 24 | public CodeFile(string fileName, Stream stream) 25 | { 26 | _stream = stream ?? throw new ArgumentNullException(); 27 | _writer = new StreamWriter(_stream, Encoding.UTF8); 28 | FileName = fileName; 29 | } 30 | 31 | public string FileName { get; } 32 | 33 | public Stream Stream => _stream ?? throw new ObjectDisposedException(nameof(CodeFile)); 34 | 35 | public TextWriter Writer => _writer ?? throw new ObjectDisposedException(nameof(CodeFile)); 36 | 37 | public void Dispose() 38 | { 39 | _writer?.Dispose(); 40 | _writer = null; 41 | _stream?.Dispose(); 42 | _stream = null; 43 | } 44 | } -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/IScalarFieldTypeMappingProvider.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQlClientGenerator; 2 | 3 | public interface IScalarFieldTypeMappingProvider 4 | { 5 | ScalarFieldTypeDescription GetCustomScalarFieldType(ScalarFieldTypeProviderContext context); 6 | } 7 | 8 | public sealed class DefaultScalarFieldTypeMappingProvider : IScalarFieldTypeMappingProvider 9 | { 10 | public static readonly DefaultScalarFieldTypeMappingProvider Instance = new(); 11 | 12 | public ScalarFieldTypeDescription GetCustomScalarFieldType(ScalarFieldTypeProviderContext context) 13 | { 14 | var propertyName = NamingHelper.ToPascalCase(context.FieldName); 15 | 16 | if (propertyName is "From" or "ValidFrom" or "To" or "ValidTo" or "CreatedAt" or "UpdatedAt" or "ModifiedAt" or "DeletedAt" || propertyName.EndsWith("Timestamp")) 17 | return ScalarFieldTypeDescription.FromNetTypeName(GenerationContext.GetNullableNetTypeName(context, nameof(DateTimeOffset), false)); 18 | 19 | return GetFallbackFieldType(context); 20 | } 21 | 22 | public static ScalarFieldTypeDescription GetFallbackFieldType(ScalarFieldTypeProviderContext context) 23 | { 24 | var fieldType = context.FieldType.UnwrapIfNonNull(); 25 | if (fieldType.Kind is GraphQlTypeKind.Enum) 26 | return GenerationContext.GetDefaultEnumNetType(context); 27 | 28 | var dataType = fieldType.Name is GraphQlTypeBase.GraphQlTypeScalarString ? "string" : "object"; 29 | return ScalarFieldTypeDescription.FromNetTypeName(GenerationContext.GetNullableNetTypeName(context, dataType, true)); 30 | } 31 | } -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/KeyValueParameterParser.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQlClientGenerator; 2 | 3 | public static class KeyValueParameterParser 4 | { 5 | public static bool TryGetCustomClassMapping(IEnumerable sourceParameters, out ICollection> customMapping, out string errorMessage) 6 | { 7 | customMapping = new List>(); 8 | 9 | foreach (var parameter in sourceParameters ?? Enumerable.Empty()) 10 | { 11 | var parts = parameter.Split(':'); 12 | if (parts.Length != 2) 13 | { 14 | errorMessage = "\"classMapping\" value must have format {GraphQlTypeName}:{C#ClassName}. "; 15 | return false; 16 | } 17 | 18 | var cSharpClassName = parts[1]; 19 | if (!CSharpHelper.IsValidIdentifier(cSharpClassName)) 20 | { 21 | errorMessage = $"\"{cSharpClassName}\" is not valid C# class name. "; 22 | return false; 23 | } 24 | 25 | customMapping.Add(new KeyValuePair(parts[0], cSharpClassName)); 26 | } 27 | 28 | errorMessage = null; 29 | return true; 30 | } 31 | 32 | public static bool TryGetCustomHeaders(IEnumerable sourceParameters, out ICollection> headers, out string errorMessage) 33 | { 34 | headers = new List>(); 35 | 36 | foreach (var parameter in sourceParameters ?? []) 37 | { 38 | var parts = parameter.Split([':'], 2); 39 | if (parts.Length != 2) 40 | { 41 | errorMessage = "\"header\" value must have format {Header}:{Value}. "; 42 | return false; 43 | } 44 | 45 | headers.Add(new KeyValuePair(parts[0], parts[1])); 46 | } 47 | 48 | errorMessage = null; 49 | return true; 50 | } 51 | } -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/MultipleFileGenerationContext.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQlClientGenerator; 2 | 3 | public class MultipleFileGenerationContext : GenerationContext 4 | { 5 | private const string ProjectTemplate = 6 | $""" 7 | 8 | 9 | 10 | netstandard2.0 11 | latest 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | """; 21 | 22 | private const string RequiredNamespaces = 23 | $""" 24 | using System; 25 | using System.Collections.Generic; 26 | using System.ComponentModel; 27 | using System.Globalization; 28 | using System.Runtime.Serialization; 29 | #if !{GraphQlGenerator.PreprocessorDirectiveDisableNewtonsoftJson} 30 | using Newtonsoft.Json; 31 | #endif 32 | 33 | """; 34 | 35 | private readonly ICodeFileEmitter _codeFileEmitter; 36 | private readonly string _projectFileName; 37 | 38 | private CodeFile _currentFile; 39 | 40 | protected internal override TextWriter Writer => 41 | (_currentFile ?? throw new InvalidOperationException($"\"{nameof(Writer)}\" not initialized")).Writer; 42 | 43 | public override byte IndentationSize => (byte)(Configuration.FileScopedNamespaces ? 0 : 4); 44 | 45 | public MultipleFileGenerationContext( 46 | GraphQlSchema schema, 47 | ICodeFileEmitter codeFileEmitter, 48 | string projectFileName = null, 49 | GeneratedObjectType objectTypes = GeneratedObjectType.All) 50 | : base(schema, objectTypes) 51 | { 52 | _codeFileEmitter = codeFileEmitter ?? throw new ArgumentNullException(nameof(codeFileEmitter)); 53 | 54 | if (projectFileName is not null && !projectFileName.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase)) 55 | throw new ArgumentException("Project file name must end with .csproj.", nameof(projectFileName)); 56 | 57 | _projectFileName = projectFileName; 58 | } 59 | 60 | public override void BeforeGeneration() 61 | { 62 | } 63 | 64 | public override void BeforeBaseClassGeneration() => InitializeNewSourceCodeFile("BaseClasses", GraphQlGenerator.RequiredNamespaces); 65 | 66 | public override void AfterBaseClassGeneration() => WriteNamespaceEnd(); 67 | 68 | public override void BeforeGraphQlTypeNameGeneration() => InitializeNewSourceCodeFile("GraphQlTypes"); 69 | 70 | public override void AfterGraphQlTypeNameGeneration() => WriteNamespaceEnd(); 71 | 72 | public override void BeforeEnumsGeneration() 73 | { 74 | } 75 | 76 | public override void BeforeEnumGeneration(ObjectGenerationContext context) => InitializeNewSourceCodeFile(context.CSharpTypeName); 77 | 78 | public override void AfterEnumGeneration(ObjectGenerationContext context) => WriteNamespaceEnd(); 79 | 80 | public override void AfterEnumsGeneration() 81 | { 82 | } 83 | 84 | public override void BeforeDirectivesGeneration() 85 | { 86 | } 87 | 88 | public override void BeforeDirectiveGeneration(ObjectGenerationContext context) => InitializeNewSourceCodeFile(context.CSharpTypeName); 89 | 90 | public override void AfterDirectiveGeneration(ObjectGenerationContext context) => WriteNamespaceEnd(); 91 | 92 | public override void AfterDirectivesGeneration() 93 | { 94 | } 95 | 96 | public override void BeforeQueryBuildersGeneration() 97 | { 98 | } 99 | 100 | public override void BeforeQueryBuilderGeneration(ObjectGenerationContext context) => InitializeNewSourceCodeFile(context.CSharpTypeName); 101 | 102 | public override void AfterQueryBuilderGeneration(ObjectGenerationContext context) => WriteNamespaceEnd(); 103 | 104 | public override void AfterQueryBuildersGeneration() 105 | { 106 | } 107 | 108 | public override void BeforeInputClassesGeneration() 109 | { 110 | } 111 | 112 | public override void AfterInputClassesGeneration() 113 | { 114 | } 115 | 116 | public override void BeforeDataClassesGeneration() 117 | { 118 | } 119 | 120 | public override void BeforeDataClassGeneration(ObjectGenerationContext context) => InitializeNewSourceCodeFile(context.CSharpTypeName); 121 | 122 | public override void OnDataClassConstructorGeneration(ObjectGenerationContext context) 123 | { 124 | } 125 | 126 | public override void AfterDataClassGeneration(ObjectGenerationContext context) => WriteNamespaceEnd(); 127 | 128 | public override void AfterDataClassesGeneration() 129 | { 130 | } 131 | 132 | public override void BeforeDataPropertyGeneration(PropertyGenerationContext context) 133 | { 134 | } 135 | 136 | public override void AfterDataPropertyGeneration(PropertyGenerationContext context) 137 | { 138 | } 139 | 140 | public override void AfterGeneration() 141 | { 142 | CollectCurrentFile(); 143 | 144 | if (String.IsNullOrEmpty(_projectFileName)) 145 | return; 146 | 147 | var projectFile = _codeFileEmitter.CreateFile(_projectFileName); 148 | projectFile.Writer.Write(ProjectTemplate); 149 | LogFileCreation(_codeFileEmitter.CollectFileInfo(projectFile)); 150 | } 151 | 152 | private void InitializeNewSourceCodeFile(string memberName, string requiredNamespaces = RequiredNamespaces) 153 | { 154 | CollectCurrentFile(); 155 | 156 | _currentFile = _codeFileEmitter.CreateFile($"{memberName}.cs"); 157 | 158 | var writer = _currentFile.Writer; 159 | writer.WriteLine(GraphQlGenerator.AutoGeneratedLabel); 160 | writer.WriteLine(); 161 | writer.WriteLine(requiredNamespaces); 162 | writer.Write("namespace "); 163 | writer.Write(Configuration.TargetNamespace); 164 | 165 | if (Configuration.FileScopedNamespaces) 166 | { 167 | writer.WriteLine(';'); 168 | writer.WriteLine(); 169 | } 170 | else 171 | { 172 | writer.WriteLine(); 173 | writer.WriteLine('{'); 174 | } 175 | } 176 | 177 | private void WriteNamespaceEnd() => _currentFile.Writer.WriteLine(Configuration.FileScopedNamespaces ? String.Empty : "}"); 178 | 179 | private void CollectCurrentFile() 180 | { 181 | if (_currentFile is not null) 182 | LogFileCreation(_codeFileEmitter.CollectFileInfo(_currentFile)); 183 | 184 | _currentFile = null; 185 | } 186 | 187 | private void LogFileCreation(CodeFileInfo fileInfo) => 188 | Log($"File {fileInfo.FileName} generated successfully ({fileInfo.Length:N0} B). "); 189 | } 190 | 191 | public class FileSystemEmitter : ICodeFileEmitter 192 | { 193 | private readonly string _outputDirectory; 194 | 195 | public FileSystemEmitter(string outputDirectory) 196 | { 197 | if (!Directory.Exists(outputDirectory)) 198 | throw new ArgumentException($"Directory \"{outputDirectory}\" does not exist.", nameof(outputDirectory)); 199 | 200 | _outputDirectory = outputDirectory; 201 | } 202 | 203 | public CodeFile CreateFile(string fileName) 204 | { 205 | fileName = Path.Combine(_outputDirectory, fileName); 206 | return new CodeFile(fileName, File.Create(fileName)); 207 | } 208 | 209 | public CodeFileInfo CollectFileInfo(CodeFile codeFile) 210 | { 211 | codeFile.Writer.Flush(); 212 | codeFile.Dispose(); 213 | 214 | return 215 | new CodeFileInfo 216 | { 217 | FileName = codeFile.FileName, 218 | Length = (int)new FileInfo(codeFile.FileName).Length 219 | }; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/NamingHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace GraphQlClientGenerator; 5 | 6 | internal static class NamingHelper 7 | { 8 | internal const string MetadataFieldTypeName = "__typename"; 9 | 10 | private static readonly char[] UnderscoreSeparator = ['_']; 11 | 12 | public static string LowerFirst(string value) => $"{Char.ToLowerInvariant(value[0])}{value.Substring(1)}"; 13 | 14 | private static readonly Regex RegexInvalidCharacters = new("[^_a-zA-Z0-9]"); 15 | private static readonly Regex RegexNextWhiteSpace = new(@"(?<=\s)"); 16 | private static readonly Regex RegexWhiteSpace = new(@"\s"); 17 | private static readonly Regex RegexUpperCaseFirstLetter = new("^[a-z]"); 18 | private static readonly Regex RegexFirstCharFollowedByUpperCasesOnly = new("(?<=[A-Z])[A-Z0-9]+$"); 19 | private static readonly Regex RegexLowerCaseNextToNumber = new("(?<=[0-9])[a-z]"); 20 | private static readonly Regex RegexUpperCaseInside = new("(?<=[A-Z])[A-Z]+?((?=[A-Z][a-z])|(?=[0-9]))"); 21 | 22 | /// https://stackoverflow.com/questions/18627112/how-can-i-convert-text-to-pascal-case> 23 | public static string ToPascalCase(string text) 24 | { 25 | if (text is MetadataFieldTypeName) 26 | return "TypeName"; 27 | 28 | var textWithoutWhiteSpace = RegexInvalidCharacters.Replace(RegexWhiteSpace.Replace(text, String.Empty), String.Empty); 29 | if (textWithoutWhiteSpace.All(c => c is '_')) 30 | return textWithoutWhiteSpace; 31 | 32 | var pascalCase = 33 | RegexInvalidCharacters 34 | // Replaces white spaces with underscore, then replace all invalid chars with an empty string. 35 | .Replace(RegexNextWhiteSpace.Replace(text, "_"), String.Empty) 36 | .Split(UnderscoreSeparator, StringSplitOptions.RemoveEmptyEntries) 37 | .Select(w => RegexUpperCaseFirstLetter.Replace(w, m => m.Value.ToUpper())) 38 | // Replace second and all following upper case letters to lower if there is no next lower (ABC -> Abc). 39 | .Select(w => RegexFirstCharFollowedByUpperCasesOnly.Replace(w, m => m.Value.ToLower())) 40 | // Set upper case the first lower case following a number (Ab9cd -> Ab9Cd). 41 | .Select(w => RegexLowerCaseNextToNumber.Replace(w, m => m.Value.ToUpper())) 42 | // Lower second and next upper case letters except the last if it follows by any lower (ABcDEf -> AbcDef). 43 | .Select(w => RegexUpperCaseInside.Replace(w, m => m.Value.ToLower())); 44 | 45 | return String.Concat(pascalCase); 46 | } 47 | 48 | public static string ToCSharpEnumName(string name) 49 | { 50 | var builder = new StringBuilder(); 51 | var startNewWord = true; 52 | var hasLowerLetters = false; 53 | var hasUpperLetters = false; 54 | var length = name?.Length ?? throw new ArgumentNullException(nameof(name)); 55 | 56 | for (var i = 0; i < length; i++) 57 | { 58 | var @char = name[i]; 59 | if (@char is '_') 60 | { 61 | startNewWord = true; 62 | 63 | if (i == 0 && length > 1 && Char.IsDigit(name[i + 1])) 64 | builder.Append('_'); 65 | 66 | continue; 67 | } 68 | 69 | hasLowerLetters |= Char.IsLower(@char); 70 | hasUpperLetters |= Char.IsUpper(@char); 71 | 72 | builder.Append(startNewWord ? Char.ToUpper(@char) : Char.ToLower(@char)); 73 | 74 | startNewWord = Char.IsDigit(@char); 75 | } 76 | 77 | return hasLowerLetters && hasUpperLetters ? name : builder.ToString(); 78 | } 79 | } -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/OutputType.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQlClientGenerator; 2 | 3 | public enum OutputType 4 | { 5 | SingleFile, 6 | OneClassPerFile 7 | } -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | [assembly: InternalsVisibleTo("GraphQlClientGenerator.Test")] 3 | -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/RegexScalarFieldTypeMappingProvider.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace GraphQlClientGenerator; 5 | 6 | public class RegexScalarFieldTypeMappingProvider(IReadOnlyCollection rules) : IScalarFieldTypeMappingProvider 7 | { 8 | private readonly IReadOnlyCollection _rules = rules ?? throw new ArgumentNullException(nameof(rules)); 9 | 10 | public static IReadOnlyCollection ParseRulesFromJson(string json) => 11 | JsonConvert.DeserializeObject>(json) ?? []; 12 | 13 | public ScalarFieldTypeDescription GetCustomScalarFieldType(ScalarFieldTypeProviderContext context) 14 | { 15 | var valueType = context.FieldType.UnwrapIfNonNull(); 16 | 17 | foreach (var rule in _rules) 18 | if (Regex.IsMatch(context.FieldName, rule.PatternValueName) && 19 | Regex.IsMatch(context.OwnerType.Name, rule.PatternBaseType) && 20 | Regex.IsMatch(valueType.Name ?? String.Empty, rule.PatternValueType)) 21 | return 22 | new ScalarFieldTypeDescription 23 | { 24 | NetTypeName = GenerationContext.GetNullableNetTypeName(context, rule.NetTypeName, rule.IsReferenceType), 25 | FormatMask = rule.FormatMask 26 | }; 27 | 28 | return DefaultScalarFieldTypeMappingProvider.GetFallbackFieldType(context); 29 | } 30 | } 31 | 32 | public class RegexScalarFieldTypeMappingRule 33 | { 34 | public string PatternBaseType { get; set; } 35 | public string PatternValueType { get; set; } 36 | public string PatternValueName { get; set; } 37 | public string NetTypeName { get; set; } 38 | public bool IsReferenceType { get; set; } 39 | public string FormatMask { get; set; } 40 | } -------------------------------------------------------------------------------- /src/GraphQlClientGenerator/SingleFileGenerationContext.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQlClientGenerator; 2 | 3 | public class SingleFileGenerationContext(GraphQlSchema schema, TextWriter writer, GeneratedObjectType objectTypes = GeneratedObjectType.All) 4 | : GenerationContext(schema, objectTypes) 5 | { 6 | private bool _isNullableReferenceScopeEnabled; 7 | private int _enums; 8 | private int _directives; 9 | private int _queryBuilders; 10 | private int _dataClasses; 11 | 12 | public override byte IndentationSize => (byte)(Configuration.FileScopedNamespaces ? 0 : 4); 13 | 14 | protected internal override TextWriter Writer { get; } = writer ?? throw new ArgumentNullException(nameof(writer)); 15 | 16 | public override void BeforeGeneration() 17 | { 18 | _enums = _directives = _queryBuilders = _dataClasses = 0; 19 | 20 | Writer.WriteLine(GraphQlGenerator.AutoGeneratedLabel); 21 | Writer.WriteLine(); 22 | Writer.WriteLine(GraphQlGenerator.RequiredNamespaces); 23 | Writer.Write("namespace "); 24 | Writer.Write(Configuration.TargetNamespace); 25 | 26 | if (Configuration.FileScopedNamespaces) 27 | { 28 | Writer.WriteLine(";"); 29 | Writer.WriteLine(); 30 | } 31 | else 32 | { 33 | Writer.WriteLine(); 34 | Writer.WriteLine("{"); 35 | } 36 | } 37 | 38 | public override void BeforeBaseClassGeneration() => WriteLine("#region base classes"); 39 | 40 | public override void AfterBaseClassGeneration() 41 | { 42 | WriteLine("#endregion"); 43 | Writer.WriteLine(); 44 | } 45 | 46 | public override void BeforeGraphQlTypeNameGeneration() => WriteLine("#region GraphQL type helpers"); 47 | 48 | public override void AfterGraphQlTypeNameGeneration() 49 | { 50 | WriteLine("#endregion"); 51 | Writer.WriteLine(); 52 | } 53 | 54 | public override void BeforeEnumsGeneration() => WriteLine("#region enums"); 55 | 56 | public override void BeforeEnumGeneration(ObjectGenerationContext context) 57 | { 58 | if (_enums > 0) 59 | Writer.WriteLine(); 60 | } 61 | 62 | public override void AfterEnumGeneration(ObjectGenerationContext context) => _enums++; 63 | 64 | public override void AfterEnumsGeneration() 65 | { 66 | WriteLine("#endregion"); 67 | Writer.WriteLine(); 68 | } 69 | 70 | public override void BeforeDirectivesGeneration() 71 | { 72 | EnterNullableReferenceScope(); 73 | WriteLine("#region directives"); 74 | } 75 | 76 | public override void BeforeDirectiveGeneration(ObjectGenerationContext context) 77 | { 78 | if (_directives > 0) 79 | Writer.WriteLine(); 80 | } 81 | 82 | public override void AfterDirectiveGeneration(ObjectGenerationContext context) => _directives++; 83 | 84 | public override void AfterDirectivesGeneration() 85 | { 86 | WriteLine("#endregion"); 87 | Writer.WriteLine(); 88 | } 89 | 90 | public override void BeforeQueryBuildersGeneration() 91 | { 92 | EnterNullableReferenceScope(); 93 | WriteLine("#region builder classes"); 94 | } 95 | 96 | public override void BeforeQueryBuilderGeneration(ObjectGenerationContext context) 97 | { 98 | if (_queryBuilders > 0) 99 | Writer.WriteLine(); 100 | } 101 | 102 | public override void AfterQueryBuilderGeneration(ObjectGenerationContext context) => _queryBuilders++; 103 | 104 | public override void AfterQueryBuildersGeneration() 105 | { 106 | WriteLine("#endregion"); 107 | Writer.WriteLine(); 108 | } 109 | 110 | public override void BeforeInputClassesGeneration() 111 | { 112 | EnterNullableReferenceScope(); 113 | WriteLine("#region input classes"); 114 | } 115 | 116 | public override void AfterInputClassesGeneration() 117 | { 118 | WriteLine("#endregion"); 119 | Writer.WriteLine(); 120 | } 121 | 122 | public override void BeforeDataClassesGeneration() 123 | { 124 | _dataClasses = 0; 125 | EnterNullableReferenceScope(); 126 | WriteLine("#region data classes"); 127 | } 128 | 129 | public override void BeforeDataClassGeneration(ObjectGenerationContext context) 130 | { 131 | if (_dataClasses > 0) 132 | Writer.WriteLine(); 133 | } 134 | 135 | public override void OnDataClassConstructorGeneration(ObjectGenerationContext context) 136 | { 137 | } 138 | 139 | public override void AfterDataClassGeneration(ObjectGenerationContext context) => _dataClasses++; 140 | 141 | public override void AfterDataClassesGeneration() => WriteLine("#endregion"); 142 | 143 | public override void BeforeDataPropertyGeneration(PropertyGenerationContext context) 144 | { 145 | } 146 | 147 | public override void AfterDataPropertyGeneration(PropertyGenerationContext context) 148 | { 149 | } 150 | 151 | public override void AfterGeneration() 152 | { 153 | ExitNullableReferenceScope(); 154 | 155 | if (!Configuration.FileScopedNamespaces) 156 | Writer.WriteLine("}"); 157 | } 158 | 159 | private void EnterNullableReferenceScope() 160 | { 161 | if (_isNullableReferenceScopeEnabled || !Configuration.EnableNullableReferences) 162 | return; 163 | 164 | WriteLine("#nullable enable"); 165 | _isNullableReferenceScopeEnabled = true; 166 | } 167 | 168 | private void ExitNullableReferenceScope() 169 | { 170 | if (!_isNullableReferenceScopeEnabled) 171 | return; 172 | 173 | WriteLine("#nullable restore"); 174 | _isNullableReferenceScopeEnabled = false; 175 | } 176 | 177 | private void WriteLine(string text) 178 | { 179 | Writer.Write(GraphQlGenerator.GetIndentation(IndentationSize)); 180 | Writer.WriteLine(text); 181 | } 182 | } -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/CompilationHelper.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Reflection; 3 | using System.Runtime.Serialization; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CSharp; 6 | 7 | namespace GraphQlClientGenerator.Test; 8 | 9 | internal static class CompilationHelper 10 | { 11 | public static CSharpCompilation CreateCompilation(string sourceCode, string assemblyName, NullableContextOptions nullableContextOptions = NullableContextOptions.Disable) 12 | { 13 | var syntaxTree = 14 | SyntaxFactory.ParseSyntaxTree( 15 | $$""" 16 | {{GraphQlGenerator.RequiredNamespaces}} 17 | 18 | namespace {{assemblyName}} 19 | { 20 | {{sourceCode}} 21 | } 22 | """, 23 | CSharpParseOptions.Default.WithLanguageVersion(Enum.GetValues(typeof(LanguageVersion)).Cast().Max())); 24 | 25 | var compilationOptions = 26 | new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, nullableContextOptions: nullableContextOptions) 27 | .WithPlatform(Platform.AnyCpu) 28 | .WithOverflowChecks(true) 29 | .WithOptimizationLevel(OptimizationLevel.Release) 30 | .WithSpecificDiagnosticOptions( 31 | new Dictionary 32 | { 33 | { "CS1701", ReportDiagnostic.Suppress } 34 | }); 35 | 36 | var systemReference = MetadataReference.CreateFromFile(typeof(DateTimeOffset).Assembly.Location); 37 | var systemObjectModelReference = MetadataReference.CreateFromFile(Assembly.Load("System.ObjectModel").Location); 38 | var systemTextRegularExpressionsReference = MetadataReference.CreateFromFile(Assembly.Load("System.Text.RegularExpressions").Location); 39 | var systemRuntimeReference = MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location); 40 | var systemCollectionsReference = MetadataReference.CreateFromFile(Assembly.Load("System.Collections").Location); 41 | var systemGlobalizationReference = MetadataReference.CreateFromFile(Assembly.Load("System.Globalization").Location); 42 | var systemRuntimeExtensionsReference = MetadataReference.CreateFromFile(Assembly.Load("System.Runtime.Extensions").Location); 43 | var netStandardReference = MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location); 44 | var linqReference = MetadataReference.CreateFromFile(Assembly.Load("System.Linq").Location); 45 | var linqExpressionsReference = MetadataReference.CreateFromFile(Assembly.Load("System.Linq.Expressions").Location); 46 | var systemDynamicRuntimeReference = MetadataReference.CreateFromFile(Assembly.Load("System.Dynamic.Runtime").Location); 47 | var systemIoReference = MetadataReference.CreateFromFile(Assembly.Load("System.IO").Location); 48 | var jsonNetReference = MetadataReference.CreateFromFile(Assembly.Load("Newtonsoft.Json").Location); 49 | var runtimeSerializationReference = MetadataReference.CreateFromFile(typeof(EnumMemberAttribute).Assembly.Location); 50 | var componentModelReference = MetadataReference.CreateFromFile(typeof(DescriptionAttribute).Assembly.Location); 51 | var componentModelTypeConverterReference = MetadataReference.CreateFromFile(Assembly.Load("System.ComponentModel.TypeConverter").Location); 52 | 53 | return 54 | CSharpCompilation.Create( 55 | assemblyName, 56 | [syntaxTree], 57 | [ 58 | systemReference, 59 | systemIoReference, 60 | systemDynamicRuntimeReference, 61 | runtimeSerializationReference, 62 | systemObjectModelReference, 63 | systemTextRegularExpressionsReference, 64 | componentModelReference, 65 | componentModelTypeConverterReference, 66 | systemRuntimeReference, 67 | systemRuntimeExtensionsReference, 68 | systemCollectionsReference, 69 | systemGlobalizationReference, 70 | jsonNetReference, 71 | linqReference, 72 | linqExpressionsReference, 73 | netStandardReference 74 | ], 75 | compilationOptions); 76 | 77 | } 78 | } -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/ExpectedMultipleFilesContext/Avatar: -------------------------------------------------------------------------------- 1 | // This file has been auto generated. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Globalization; 7 | using System.Runtime.Serialization; 8 | #if !GRAPHQL_GENERATOR_DISABLE_NEWTONSOFT_JSON 9 | using Newtonsoft.Json; 10 | #endif 11 | 12 | namespace GraphQlGeneratorTest 13 | { 14 | public enum Avatar 15 | { 16 | [EnumMember(Value = "floorhouse1")] Floorhouse1, 17 | [EnumMember(Value = "floorhouse2")] Floorhouse2, 18 | [EnumMember(Value = "floorhouse3")] Floorhouse3, 19 | [EnumMember(Value = "castle")] Castle, 20 | [EnumMember(Value = "apartment")] Apartment, 21 | [EnumMember(Value = "cottage")] Cottage, 22 | [EnumMember(Value = "rowhouse")] Rowhouse 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/ExpectedMultipleFilesContext/Avatar.FileScoped: -------------------------------------------------------------------------------- 1 | // This file has been auto generated. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Globalization; 7 | using System.Runtime.Serialization; 8 | #if !GRAPHQL_GENERATOR_DISABLE_NEWTONSOFT_JSON 9 | using Newtonsoft.Json; 10 | #endif 11 | 12 | namespace GraphQlGeneratorTest; 13 | 14 | public enum Avatar 15 | { 16 | [EnumMember(Value = "floorhouse1")] Floorhouse1, 17 | [EnumMember(Value = "floorhouse2")] Floorhouse2, 18 | [EnumMember(Value = "floorhouse3")] Floorhouse3, 19 | [EnumMember(Value = "castle")] Castle, 20 | [EnumMember(Value = "apartment")] Apartment, 21 | [EnumMember(Value = "cottage")] Cottage, 22 | [EnumMember(Value = "rowhouse")] Rowhouse 23 | } 24 | 25 | -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/ExpectedMultipleFilesContext/Home: -------------------------------------------------------------------------------- 1 | // This file has been auto generated. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Globalization; 7 | using System.Runtime.Serialization; 8 | #if !GRAPHQL_GENERATOR_DISABLE_NEWTONSOFT_JSON 9 | using Newtonsoft.Json; 10 | #endif 11 | 12 | namespace GraphQlGeneratorTest 13 | { 14 | public partial class Home 15 | { 16 | public Guid? Id { get; set; } 17 | public Avatar? Avatar { get; set; } 18 | public string TimeZone { get; set; } 19 | public string Title { get; set; } 20 | public string Type { get; set; } 21 | public bool? HasEnergyDeal { get; set; } 22 | public Address Address { get; set; } 23 | public Subscription Subscription { get; set; } 24 | public ICollection ConsumptionMonths { get; set; } 25 | public Consumption Consumption { get; set; } 26 | public PreLiveComparison PreLiveComparison { get; set; } 27 | public ICollection Comparisons { get; set; } 28 | public Comparison ComparisonCurrentMonth { get; set; } 29 | public ICollection ProfileQuestions { get; set; } 30 | public ICollection Temperatures { get; set; } 31 | public SignupStatus SignupStatus { get; set; } 32 | public ICollection Disaggregation { get; set; } 33 | public Weather Weather { get; set; } 34 | public AwayMode AwayMode { get; set; } 35 | public DayNightSchedule DayNightSchedule { get; set; } 36 | public ReportRoot Report { get; set; } 37 | public ICollection Thermostats { get; set; } 38 | public ICollection Sensors { get; set; } 39 | public ICollection SensorsHistory { get; set; } 40 | public ICollection PairableDevices { get; set; } 41 | public ICollection ProductionMonths { get; set; } 42 | public Production Production { get; set; } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/ExpectedMultipleFilesContext/Home.FileScoped: -------------------------------------------------------------------------------- 1 | // This file has been auto generated. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Globalization; 7 | using System.Runtime.Serialization; 8 | #if !GRAPHQL_GENERATOR_DISABLE_NEWTONSOFT_JSON 9 | using Newtonsoft.Json; 10 | #endif 11 | 12 | namespace GraphQlGeneratorTest; 13 | 14 | public partial class Home 15 | { 16 | public Guid? Id { get; set; } 17 | public Avatar? Avatar { get; set; } 18 | public string TimeZone { get; set; } 19 | public string Title { get; set; } 20 | public string Type { get; set; } 21 | public bool? HasEnergyDeal { get; set; } 22 | public Address Address { get; set; } 23 | public Subscription Subscription { get; set; } 24 | public ICollection ConsumptionMonths { get; set; } 25 | public Consumption Consumption { get; set; } 26 | public PreLiveComparison PreLiveComparison { get; set; } 27 | public ICollection Comparisons { get; set; } 28 | public Comparison ComparisonCurrentMonth { get; set; } 29 | public ICollection ProfileQuestions { get; set; } 30 | public ICollection Temperatures { get; set; } 31 | public SignupStatus SignupStatus { get; set; } 32 | public ICollection Disaggregation { get; set; } 33 | public Weather Weather { get; set; } 34 | public AwayMode AwayMode { get; set; } 35 | public DayNightSchedule DayNightSchedule { get; set; } 36 | public ReportRoot Report { get; set; } 37 | public ICollection Thermostats { get; set; } 38 | public ICollection Sensors { get; set; } 39 | public ICollection SensorsHistory { get; set; } 40 | public ICollection PairableDevices { get; set; } 41 | public ICollection ProductionMonths { get; set; } 42 | public Production Production { get; set; } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/ExpectedMultipleFilesContext/IncludeDirective: -------------------------------------------------------------------------------- 1 | // This file has been auto generated. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Globalization; 7 | using System.Runtime.Serialization; 8 | #if !GRAPHQL_GENERATOR_DISABLE_NEWTONSOFT_JSON 9 | using Newtonsoft.Json; 10 | #endif 11 | 12 | namespace GraphQlGeneratorTest 13 | { 14 | /// 15 | /// Directs the executor to include this field or fragment only when the `if` argument is true. 16 | /// 17 | [Description(@"Directs the executor to include this field or fragment only when the `if` argument is true.")] 18 | public class IncludeDirective : GraphQlDirective 19 | { 20 | /// Included when true. 21 | public IncludeDirective(QueryBuilderParameter @if) : base("include") 22 | { 23 | AddArgument("if", @if); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/ExpectedMultipleFilesContext/IncludeDirective.FileScoped: -------------------------------------------------------------------------------- 1 | // This file has been auto generated. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Globalization; 7 | using System.Runtime.Serialization; 8 | #if !GRAPHQL_GENERATOR_DISABLE_NEWTONSOFT_JSON 9 | using Newtonsoft.Json; 10 | #endif 11 | 12 | namespace GraphQlGeneratorTest; 13 | 14 | /// 15 | /// Directs the executor to include this field or fragment only when the `if` argument is true. 16 | /// 17 | [Description(@"Directs the executor to include this field or fragment only when the `if` argument is true.")] 18 | public class IncludeDirective : GraphQlDirective 19 | { 20 | /// Included when true. 21 | public IncludeDirective(QueryBuilderParameter @if) : base("include") 22 | { 23 | AddArgument("if", @if); 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/ExpectedMultipleFilesContext/MutationQueryBuilder: -------------------------------------------------------------------------------- 1 | // This file has been auto generated. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Globalization; 7 | using System.Runtime.Serialization; 8 | #if !GRAPHQL_GENERATOR_DISABLE_NEWTONSOFT_JSON 9 | using Newtonsoft.Json; 10 | #endif 11 | 12 | namespace GraphQlGeneratorTest 13 | { 14 | public partial class MutationQueryBuilder : GraphQlQueryBuilder 15 | { 16 | private static readonly GraphQlFieldMetadata[] AllFieldMetadata = 17 | { 18 | new GraphQlFieldMetadata { Name = "me", IsComplex = true, QueryBuilderType = typeof(MeMutationQueryBuilder) } 19 | }; 20 | 21 | protected override string TypeName { get; } = "Mutation"; 22 | 23 | public override IReadOnlyList AllFields { get; } = AllFieldMetadata; 24 | 25 | public MutationQueryBuilder(string operationName = null) : base("mutation", operationName) 26 | { 27 | } 28 | 29 | public MutationQueryBuilder WithParameter(GraphQlQueryParameter parameter) => WithParameterInternal(parameter); 30 | 31 | public MutationQueryBuilder WithMe(MeMutationQueryBuilder meMutationQueryBuilder, string alias = null, IncludeDirective include = null, SkipDirective skip = null) => WithObjectField("me", alias, meMutationQueryBuilder, new GraphQlDirective[] { include, skip }); 32 | 33 | public MutationQueryBuilder ExceptMe() => ExceptField("me"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/ExpectedMultipleFilesContext/MutationQueryBuilder.FileScoped: -------------------------------------------------------------------------------- 1 | // This file has been auto generated. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Globalization; 7 | using System.Runtime.Serialization; 8 | #if !GRAPHQL_GENERATOR_DISABLE_NEWTONSOFT_JSON 9 | using Newtonsoft.Json; 10 | #endif 11 | 12 | namespace GraphQlGeneratorTest; 13 | 14 | public partial class MutationQueryBuilder : GraphQlQueryBuilder 15 | { 16 | private static readonly GraphQlFieldMetadata[] AllFieldMetadata = 17 | { 18 | new GraphQlFieldMetadata { Name = "me", IsComplex = true, QueryBuilderType = typeof(MeMutationQueryBuilder) } 19 | }; 20 | 21 | protected override string TypeName { get; } = "Mutation"; 22 | 23 | public override IReadOnlyList AllFields { get; } = AllFieldMetadata; 24 | 25 | public MutationQueryBuilder(string operationName = null) : base("mutation", operationName) 26 | { 27 | } 28 | 29 | public MutationQueryBuilder WithParameter(GraphQlQueryParameter parameter) => WithParameterInternal(parameter); 30 | 31 | public MutationQueryBuilder WithMe(MeMutationQueryBuilder meMutationQueryBuilder, string alias = null, IncludeDirective include = null, SkipDirective skip = null) => WithObjectField("me", alias, meMutationQueryBuilder, new GraphQlDirective[] { include, skip }); 32 | 33 | public MutationQueryBuilder ExceptMe() => ExceptField("me"); 34 | } 35 | 36 | -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/GlobalFixture.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace GraphQlClientGenerator.Test; 4 | 5 | public static class GlobalFixture 6 | { 7 | [ModuleInitializer] 8 | public static void Initialize() 9 | { 10 | UseProjectRelativeDirectory("VerifierExpectations"); 11 | } 12 | } -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/GraphQlClientGenerator.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | latest 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/GraphQlClientSourceGeneratorTest.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.CodeAnalysis.Diagnostics; 5 | using Microsoft.CodeAnalysis.Text; 6 | 7 | namespace GraphQlClientGenerator.Test; 8 | 9 | public class GraphQlClientSourceGeneratorTest : IDisposable 10 | { 11 | private const string FileNameTestSchema = "GraphQlClientSourceGeneratorTest"; 12 | 13 | private readonly AdditionalFile _fileGraphQlSchema = 14 | CreateAdditionalFile("GraphQlClientGenerator.Test.TestSchemas.TestSchema3", $"{FileNameTestSchema}.GQL.Schema.Json"); 15 | 16 | private readonly AdditionalFile _fileMappingRules = 17 | CreateAdditionalFile("GraphQlClientGenerator.Test.RegexCustomScalarFieldTypeMappingRules", "RegexScalarFieldTypeMappingProvider.gql.config.JSON"); 18 | 19 | private static AdditionalFile CreateAdditionalFile(string resourceName, string fileName) 20 | { 21 | var resourceStream = 22 | typeof(GraphQlGeneratorTest).Assembly.GetManifestResourceStream(resourceName) 23 | ?? throw new InvalidOperationException($"resource \"{resourceName}\" not found"); 24 | 25 | var fullFileName = Path.Combine(Path.GetTempPath(), fileName); 26 | using var fileStream = File.Create(fullFileName); 27 | resourceStream.CopyTo(fileStream); 28 | return new AdditionalFile(fullFileName); 29 | } 30 | 31 | public void Dispose() 32 | { 33 | if (File.Exists(_fileGraphQlSchema.Path)) 34 | File.Delete(_fileGraphQlSchema.Path); 35 | 36 | if (File.Exists(_fileMappingRules.Path)) 37 | File.Delete(_fileMappingRules.Path); 38 | } 39 | 40 | [Theory] 41 | [InlineData("GraphQlClientGenerator.DefaultScalarFieldTypeMappingProvider, GraphQlClientGenerator", false)] 42 | [InlineData(null, true)] 43 | public Task SourceGeneration(string scalarFieldTypeMappingProviderTypeName, bool useFileScopedNamespace) 44 | { 45 | var generatedSource = GenerateSource(SetupGeneratorOptions(OutputType.SingleFile, useFileScopedNamespace, scalarFieldTypeMappingProviderTypeName), null); 46 | 47 | generatedSource.Encoding.ShouldBe(Encoding.UTF8); 48 | var sourceCode = generatedSource.ToString(); 49 | return Verify(sourceCode).UseParameters(useFileScopedNamespace); 50 | } 51 | 52 | [Fact] 53 | public Task SourceGenerationWithRegexCustomScalarFieldTypeMappingProvider() 54 | { 55 | var options = SetupGeneratorOptions(OutputType.SingleFile, false, null); 56 | options.Add("build_property.GraphQlClientGenerator_DataClassMemberNullability", nameof(DataClassMemberNullability.DefinedBySchema)); 57 | options.Add("build_property.GraphQlClientGenerator_GenerationOrder", nameof(GenerationOrder.Alphabetical)); 58 | options.Add("build_property.GraphQlClientGenerator_EnableNullableReferences", "true"); 59 | options.Add("build_property.GraphQlClientGenerator_InputObjectMode", "POCO"); 60 | 61 | var generatedSource = GenerateSource(options, _fileMappingRules, NullableContextOptions.Enable); 62 | var sourceCode = generatedSource.ToString(); 63 | return Verify(sourceCode); 64 | } 65 | 66 | [Fact] 67 | public void SourceGenerationWithOneClassPerFile() 68 | { 69 | var result = RunGenerator(SetupGeneratorOptions(OutputType.OneClassPerFile, true, null), null, NullableContextOptions.Disable); 70 | result.GeneratedSources.Length.ShouldBe(70); 71 | var fileSizes = result.GeneratedSources.Where(s => s.HintName != "BaseClasses.cs").Select(s => s.SourceText.ToString().ReplaceLineEndings().Length).ToArray(); 72 | fileSizes.ShouldBe( 73 | [3581, 745, 579, 792, 1405, 486, 621, 927, 696, 714, 1446, 2852, 10599, 3375, 4497, 1378, 3800, 3519, 3620, 2620, 2506, 5561, 4038, 1597, 2476, 4563, 4342, 1627, 2455, 4140, 3537, 1616, 1155, 4009, 1792, 1579, 1839, 8725, 2071, 6394, 2207, 709, 1699, 5619, 2092, 3257, 983, 2955, 2361, 1660, 2155, 1139, 3571, 704, 969, 1134, 4025, 2771, 833, 1130, 3716, 2281, 831, 770, 1458, 1007, 877, 780, 8460]); 74 | } 75 | 76 | private static Dictionary SetupGeneratorOptions(OutputType outputType, bool useFileScopedNamespaces, string scalarFieldTypeMappingProviderTypeName) 77 | { 78 | var configurationOptions = 79 | new Dictionary 80 | { 81 | { "build_property.GraphQlClientGenerator_ClassPrefix", "SourceGenerated" }, 82 | { "build_property.GraphQlClientGenerator_ClassSuffix", "V2" }, 83 | { "build_property.GraphQlClientGenerator_IncludeDeprecatedFields", "true" }, 84 | { "build_property.GraphQlClientGenerator_CodeDocumentationType", nameof(CodeDocumentationType.XmlSummary) }, 85 | { "build_property.GraphQlClientGenerator_FloatTypeMapping", nameof(FloatTypeMapping.Double) }, 86 | { "build_property.GraphQlClientGenerator_BooleanTypeMapping", nameof(BooleanTypeMapping.Boolean) }, 87 | { "build_property.GraphQlClientGenerator_IdTypeMapping", nameof(IdTypeMapping.String) }, 88 | { "build_property.GraphQlClientGenerator_JsonPropertyGeneration", nameof(JsonPropertyGenerationOption.Always) }, 89 | { "build_property.GraphQlClientGenerator_CustomClassMapping", "Query:Tibber|RootMutation:TibberMutation Consumption:ConsumptionEntry;Production:ProductionEntry" }, 90 | { "build_property.GraphQlClientGenerator_Headers", "Authorization:Basic XXX|X-REQUEST-ID:123456789" }, 91 | { "build_property.GraphQlClientGenerator_HttpMethod", "GET" }, 92 | { "build_property.GraphQlClientGenerator_EnumValueNaming", nameof(EnumValueNamingOption.CSharp) }, 93 | { "build_property.GraphQlClientGenerator_OutputType", outputType.ToString() } 94 | }; 95 | 96 | if (scalarFieldTypeMappingProviderTypeName is not null) 97 | configurationOptions.Add("build_property.GraphQlClientGenerator_ScalarFieldTypeMappingProvider", scalarFieldTypeMappingProviderTypeName); 98 | 99 | if (useFileScopedNamespaces) 100 | configurationOptions.Add("build_property.GraphQlClientGenerator_FileScopedNamespaces", "true"); 101 | 102 | return configurationOptions; 103 | } 104 | 105 | private SourceText GenerateSource(Dictionary options, AdditionalText additionalFile, NullableContextOptions nullableContextOptions = NullableContextOptions.Disable) 106 | { 107 | var result = RunGenerator(options, additionalFile, nullableContextOptions); 108 | result.GeneratedSources.Length.ShouldBe(1); 109 | return result.GeneratedSources[0].SourceText; 110 | } 111 | 112 | private GeneratorRunResult RunGenerator(Dictionary options, AdditionalText additionalFile, NullableContextOptions nullableContextOptions) 113 | { 114 | var compilerAnalyzerConfigOptionsProvider = new CompilerAnalyzerConfigOptionsProvider(new CompilerAnalyzerConfigOptions(options)); 115 | 116 | var compilation = CompilationHelper.CreateCompilation(null, "SourceGeneratorTestAssembly", nullableContextOptions); 117 | 118 | var additionalFiles = new List { _fileGraphQlSchema }; 119 | 120 | if (additionalFile is not null) 121 | additionalFiles.Add(additionalFile); 122 | 123 | var sourceGenerator = new GraphQlClientSourceGenerator(); 124 | var driver = CSharpGeneratorDriver.Create([sourceGenerator], additionalFiles, optionsProvider: compilerAnalyzerConfigOptionsProvider); 125 | var csharpDriver = driver.RunGenerators(compilation); 126 | var runResult = csharpDriver.GetRunResult(); 127 | runResult.Results.Length.ShouldBe(1); 128 | return runResult.Results[0]; 129 | } 130 | 131 | private class AdditionalFile(string path) : AdditionalText 132 | { 133 | public override SourceText GetText(CancellationToken cancellationToken = default) => 134 | SourceText.From(File.ReadAllText(Path), Encoding.UTF8); 135 | 136 | public override string Path { get; } = path; 137 | } 138 | 139 | private class CompilerAnalyzerConfigOptionsProvider(AnalyzerConfigOptions globalOptions) : AnalyzerConfigOptionsProvider 140 | { 141 | private static readonly CompilerAnalyzerConfigOptions DummyOptions = new([]); 142 | 143 | public override AnalyzerConfigOptions GlobalOptions { get; } = globalOptions; 144 | 145 | public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => DummyOptions; 146 | 147 | public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => DummyOptions; 148 | } 149 | 150 | private class CompilerAnalyzerConfigOptions(Dictionary options) : AnalyzerConfigOptions 151 | { 152 | public override bool TryGetValue(string key, out string value) => options.TryGetValue(key, out value); 153 | } 154 | } -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/GraphQlIntrospectionSchemaTest.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQlClientGenerator.Test; 2 | 3 | public class GraphQlIntrospectionSchemaTest 4 | { 5 | public class GraphQlFieldTypeTest 6 | { 7 | [Theory] 8 | [MemberData(nameof(EqualsTestData))] 9 | public void Equality(GraphQlFieldType o1, GraphQlFieldType o2, bool expectedResult) 10 | { 11 | o1.Equals(o2).ShouldBe(expectedResult); 12 | } 13 | 14 | public static IEnumerable EqualsTestData => 15 | [ 16 | [new GraphQlFieldType(), new GraphQlFieldType(), true], 17 | [new GraphQlFieldType { Name = "" }, new GraphQlFieldType(), false], 18 | [new GraphQlFieldType(), new GraphQlFieldType { Kind = GraphQlTypeKind.Object }, false], 19 | [new GraphQlFieldType { Name = "TestType", Kind = GraphQlTypeKind.Interface }, new GraphQlFieldType { Name = "TestType", Kind = GraphQlTypeKind.Interface }, true], 20 | [new GraphQlFieldType(), new GraphQlFieldType { OfType = new GraphQlFieldType() }, false], 21 | [new GraphQlFieldType { OfType = new GraphQlFieldType() }, new GraphQlFieldType { OfType = new GraphQlFieldType() }, true] 22 | ]; 23 | } 24 | } -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/NamingHelperTest.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQlClientGenerator.Test; 2 | 3 | public class NamingHelperTest 4 | { 5 | [Theory] 6 | [InlineData("WARD_VS_VITAL_SIGNS", "WardVsVitalSigns")] 7 | [InlineData("Who am I?", "WhoAmI")] 8 | [InlineData("Hello|Who|Am|I?", "HelloWhoAmI")] 9 | [InlineData("Lorem ipsum dolor...", "LoremIpsumDolor")] 10 | [InlineData("CoolSP", "CoolSp")] 11 | [InlineData("AB9CD", "Ab9Cd")] 12 | [InlineData("CCCTrigger", "CccTrigger")] 13 | [InlineData("CIRC", "Circ")] 14 | [InlineData("ID_SOME", "IdSome")] 15 | [InlineData("ID_SomeOther", "IdSomeOther")] 16 | [InlineData("ID_SOMEOther", "IdSomeOther")] 17 | [InlineData("CCC_SOME_2Phases", "CccSome2Phases")] 18 | [InlineData("AlreadyGoodPascalCase", "AlreadyGoodPascalCase")] 19 | [InlineData("999 999 99 9 ", "999999999")] 20 | [InlineData(" 1 2 3 ", "123")] 21 | [InlineData("1 AB cd EFDDD 8", "1AbCdEfddd8")] 22 | [InlineData("INVALID VALUE AND _2THINGS", "InvalidValueAnd2Things")] 23 | [InlineData("_", "_")] 24 | [InlineData(" _ _ ? ", "__")] 25 | [InlineData(" _ _ ? x ", "X")] 26 | [InlineData(NamingHelper.MetadataFieldTypeName, "TypeName")] 27 | public void ToPascalCase(string text, string expectedText) => NamingHelper.ToPascalCase(text).ShouldBe(expectedText); 28 | 29 | [Fact] 30 | public void LowerFirst() => NamingHelper.LowerFirst("PropertyName").ShouldBe("propertyName"); 31 | 32 | [Theory] 33 | [InlineData("APPLICATION_OCTET_STREAM", "ApplicationOctetStream")] 34 | [InlineData("_2048X2048", "_2048X2048")] 35 | public void ToCSharpEnumName(string text, string expectedText) => NamingHelper.ToCSharpEnumName(text).ShouldBe(expectedText); 36 | } -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/RegexCustomScalarFieldTypeMappingRules: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "patternBaseType": ".+", 4 | "patternValueType": ".+", 5 | "patternValueName": "^(timestamp|.*(f|F)rom|.*(t|T)o)$", 6 | "netTypeName": "DateTime", 7 | "formatMask": "O" 8 | } 9 | ] -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/VerifierExpectations/GraphQlGeneratorTest.DeprecatedAttributes.verified.txt: -------------------------------------------------------------------------------- 1 | #region GraphQL type helpers 2 | public static class GraphQlTypes 3 | { 4 | public const string Boolean = "Boolean"; 5 | public const string DynamicType = "DynamicType"; 6 | public const string Float = "Float"; 7 | public const string Id = "ID"; 8 | public const string Int = "Int"; 9 | public const string String = "String"; 10 | 11 | public const string Avatar = "Avatar"; 12 | public const string PriceLevel = "PriceLevel"; 13 | public const string Resolution = "Resolution"; 14 | 15 | public const string AwayMode = "AwayMode"; 16 | public const string AwayModeSettings = "AwayModeSettings"; 17 | public const string Comparison = "Comparison"; 18 | public const string ComparisonData = "ComparisonData"; 19 | public const string Consumption = "Consumption"; 20 | public const string ConsumptionMonth = "ConsumptionMonth"; 21 | public const string Device = "Device"; 22 | public const string Disaggregation = "Disaggregation"; 23 | public const string Feed = "Feed"; 24 | public const string FeedItem = "FeedItem"; 25 | public const string Home = "Home"; 26 | public const string Me = "Me"; 27 | public const string PreLiveComparison = "PreLiveComparison"; 28 | public const string PriceRating = "PriceRating"; 29 | public const string PriceRatingEntry = "PriceRatingEntry"; 30 | public const string ProcessStep = "ProcessStep"; 31 | public const string PushNotification = "PushNotification"; 32 | public const string Query = "Query"; 33 | public const string SignupStatus = "SignupStatus"; 34 | public const string Subscription = "Subscription"; 35 | public const string Weather = "Weather"; 36 | 37 | public static readonly IReadOnlyDictionary ReverseMapping = 38 | new Dictionary 39 | { 40 | { typeof(bool), "Boolean" }, 41 | { typeof(DateTimeOffset), "String" }, 42 | { typeof(int), "Int" }, 43 | { typeof(string), "String" }, 44 | { typeof(decimal), "Float" }, 45 | { typeof(Guid), "ID" } 46 | }; 47 | } 48 | #endregion 49 | 50 | #region enums 51 | /// 52 | /// Home 53 | /// aavatar 54 | /// types 55 | /// 56 | [Description(@"Home 57 | aavatar 58 | types")] 59 | public enum Avatar 60 | { 61 | [EnumMember(Value = "floorhouse1")] Floorhouse1, 62 | [EnumMember(Value = "floorhouse2")] Floorhouse2, 63 | [EnumMember(Value = "floorhouse3")] Floorhouse3, 64 | [EnumMember(Value = "castle")] Castle, 65 | [EnumMember(Value = "apartment")] Apartment, 66 | [EnumMember(Value = "cottage")] Cottage, 67 | [Obsolete(@"use 'rowhouse' instead")] 68 | ROWHOUSE, 69 | rowhouse 70 | } 71 | 72 | public enum PriceLevel 73 | { 74 | [EnumMember(Value = "low")] Low, 75 | [EnumMember(Value = "normal")] Normal, 76 | [EnumMember(Value = "high")] High 77 | } 78 | 79 | public enum Resolution 80 | { 81 | [EnumMember(Value = "annual")] Annual, 82 | [EnumMember(Value = "monthly")] Monthly 83 | } 84 | #endregion 85 | 86 | #region data classes 87 | public class Query 88 | { 89 | /// 90 | /// Information 91 | /// about 92 | /// user 93 | /// 94 | [Description(@"Information 95 | about 96 | user")] 97 | public Me Me { get; set; } 98 | } 99 | 100 | /// 101 | /// Information 102 | /// about 103 | /// user 104 | /// 105 | [Description(@"Information 106 | about 107 | user")] 108 | public class Me 109 | { 110 | public Guid? Id { get; set; } 111 | /// 112 | /// customer's first name 113 | /// 114 | [Description(@"customer's first name")] 115 | public string FirstName { get; set; } 116 | /// 117 | /// customer's last name 118 | /// 119 | [Description(@"customer's last name")] 120 | public string LastName { get; set; } 121 | /// 122 | /// customer's full name 123 | /// 124 | [Description(@"customer's full name")] 125 | [Obsolete(@"use 'firstName' and 'lastName' fields; escaping test: ""\{}")] 126 | public string FullName { get; set; } 127 | public string Ssn { get; set; } 128 | public string Email { get; set; } 129 | public string Language { get; set; } 130 | [Obsolete] 131 | public string Tone { get; set; } 132 | public Home Home { get; set; } 133 | public ICollection Homes { get; set; } 134 | public Feed Feed { get; set; } 135 | public ICollection EnergyStatements { get; set; } 136 | [Obsolete(@"invalid circular reference")] 137 | public Me NestedMe { get; set; } 138 | } 139 | 140 | public class Home 141 | { 142 | public Guid? Id { get; set; } 143 | public Avatar? Avatar { get; set; } 144 | public string TimeZone { get; set; } 145 | public Subscription Subscription { get; set; } 146 | public ICollection ConsumptionMonths { get; set; } 147 | public Consumption Consumption { get; set; } 148 | public PreLiveComparison PreLiveComparison { get; set; } 149 | public ICollection Comparisons { get; set; } 150 | public Comparison ComparisonCurrentMonth { get; set; } 151 | public object Profile { get; set; } 152 | public object ProfileQuestions { get; set; } 153 | public object Thermostat { get; set; } 154 | public ICollection Temperatures { get; set; } 155 | public SignupStatus SignupStatus { get; set; } 156 | public ICollection Disaggregation { get; set; } 157 | public ICollection Devices { get; set; } 158 | public Weather Weather { get; set; } 159 | public AwayMode AwayMode { get; set; } 160 | } 161 | 162 | public class Subscription 163 | { 164 | public Guid? Id { get; set; } 165 | public DateTimeOffset? ValidFrom { get; set; } 166 | public DateTimeOffset? ValidTo { get; set; } 167 | public string Status { get; set; } 168 | public int? BillingRegionId { get; set; } 169 | public ICollection EnergyStatements { get; set; } 170 | public PriceRating PriceRating { get; set; } 171 | } 172 | 173 | public class PriceRating 174 | { 175 | public decimal? MinPrice { get; set; } 176 | public decimal? MaxPrice { get; set; } 177 | public ICollection Entries { get; set; } 178 | } 179 | 180 | public class PriceRatingEntry 181 | { 182 | public string Time { get; set; } 183 | public decimal? Price { get; set; } 184 | public PriceLevel? Level { get; set; } 185 | public decimal? Difference { get; set; } 186 | } 187 | 188 | public class ConsumptionMonth 189 | { 190 | public int? Year { get; set; } 191 | public int? Month { get; set; } 192 | public decimal? Kwh { get; set; } 193 | public decimal? Cost { get; set; } 194 | public bool? IsComplete { get; set; } 195 | public decimal? KwhEstimate { get; set; } 196 | public decimal? CostEstimate { get; set; } 197 | } 198 | 199 | public class Consumption 200 | { 201 | public object AnnualValues { get; set; } 202 | public object MonthlyValues { get; set; } 203 | public object WeeklyValues { get; set; } 204 | public object DailyValues { get; set; } 205 | public object HourlyValues { get; set; } 206 | public decimal? TotalConsumption { get; set; } 207 | public decimal? EnergyCost { get; set; } 208 | public decimal? TotalCost { get; set; } 209 | public string Currency { get; set; } 210 | public DateTimeOffset? LatestTransactionTimestamp { get; set; } 211 | public string TimeZone { get; set; } 212 | } 213 | 214 | public class PreLiveComparison 215 | { 216 | public string HomeId { get; set; } 217 | public bool? BasedOnActuals { get; set; } 218 | public Comparison PreviousYear { get; set; } 219 | public ICollection PreviousYearMonths { get; set; } 220 | } 221 | 222 | public class Comparison 223 | { 224 | public int? Year { get; set; } 225 | public int? Month { get; set; } 226 | public string Resolution { get; set; } 227 | public string HomeEfficency { get; set; } 228 | public string HomeEfficencyDescription { get; set; } 229 | public ComparisonData Home { get; set; } 230 | public ComparisonData Average { get; set; } 231 | public ComparisonData Efficient { get; set; } 232 | } 233 | 234 | public class ComparisonData 235 | { 236 | public decimal? Cost { get; set; } 237 | public decimal? Consumption { get; set; } 238 | } 239 | 240 | public class SignupStatus 241 | { 242 | public ProcessStep FeedStep { get; set; } 243 | public ProcessStep AvatarStep { get; set; } 244 | public ICollection Steps { get; set; } 245 | } 246 | 247 | public class ProcessStep 248 | { 249 | public DateTimeOffset? Timestamp { get; set; } 250 | public bool? IsComplete { get; set; } 251 | public string Title { get; set; } 252 | public string Description { get; set; } 253 | } 254 | 255 | public class Disaggregation 256 | { 257 | public int? Year { get; set; } 258 | public int? Month { get; set; } 259 | public decimal? FixedConsumptionKwh { get; set; } 260 | public int? FixedConsumptionKwhPercent { get; set; } 261 | public decimal? FixedConsumptionCost { get; set; } 262 | public decimal? HeatingConsumptionKwh { get; set; } 263 | public int? HeatingConsumptionKwhPercent { get; set; } 264 | public decimal? HeatingConsumptionCost { get; set; } 265 | public decimal? BehaviorConsumptionKwh { get; set; } 266 | public int? BehaviorConsumptionKwhPercent { get; set; } 267 | public decimal? BehaviorConsumptionCost { get; set; } 268 | public string Currency { get; set; } 269 | public bool? IsValid { get; set; } 270 | } 271 | 272 | public class Device 273 | { 274 | public string DeviceId { get; set; } 275 | public string Type { get; set; } 276 | public bool? IsControllable { get; set; } 277 | public string ExternalId { get; set; } 278 | public string Name { get; set; } 279 | public bool? IsBatteryLow { get; set; } 280 | public bool? IsSignalLow { get; set; } 281 | public bool? IsAlive { get; set; } 282 | public ICollection Capabilities { get; set; } 283 | public object Properties { get; set; } 284 | } 285 | 286 | public class Weather 287 | { 288 | public decimal? Temperature { get; set; } 289 | public DateTimeOffset? Timestamp { get; set; } 290 | public string Summary { get; set; } 291 | public string Type { get; set; } 292 | } 293 | 294 | public class AwayMode 295 | { 296 | public bool? IsSupported { get; set; } 297 | public AwayModeSettings Settings { get; set; } 298 | } 299 | 300 | public class AwayModeSettings 301 | { 302 | public DateTimeOffset? From { get; set; } 303 | public DateTimeOffset? To { get; set; } 304 | } 305 | 306 | public class Feed 307 | { 308 | public int? NumberOfItems { get; set; } 309 | public ICollection Items { get; set; } 310 | public ICollection PushNotifications { get; set; } 311 | } 312 | 313 | public class FeedItem 314 | { 315 | public int? Id { get; set; } 316 | public string MessageTypeId { get; set; } 317 | public string Link { get; set; } 318 | public string Text { get; set; } 319 | public DateTimeOffset? CreatedAt { get; set; } 320 | public DateTimeOffset? ModifiedAt { get; set; } 321 | } 322 | 323 | public class PushNotification 324 | { 325 | public int? Id { get; set; } 326 | public string Link { get; set; } 327 | public string Text { get; set; } 328 | } 329 | #endregion 330 | -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/VerifierExpectations/GraphQlGeneratorTest.GenerateDataClasses.verified.txt: -------------------------------------------------------------------------------- 1 | #region GraphQL type helpers 2 | public static class GraphQlTypes 3 | { 4 | public const string Boolean = "Boolean"; 5 | public const string DynamicType = "DynamicType"; 6 | public const string Float = "Float"; 7 | public const string Id = "ID"; 8 | public const string Int = "Int"; 9 | public const string String = "String"; 10 | 11 | public const string Avatar = "Avatar"; 12 | public const string PriceLevel = "PriceLevel"; 13 | public const string Resolution = "Resolution"; 14 | 15 | public const string AwayMode = "AwayMode"; 16 | public const string AwayModeSettings = "AwayModeSettings"; 17 | public const string Comparison = "Comparison"; 18 | public const string ComparisonData = "ComparisonData"; 19 | public const string Consumption = "Consumption"; 20 | public const string ConsumptionMonth = "ConsumptionMonth"; 21 | public const string Device = "Device"; 22 | public const string Disaggregation = "Disaggregation"; 23 | public const string Feed = "Feed"; 24 | public const string FeedItem = "FeedItem"; 25 | public const string Home = "Home"; 26 | public const string Me = "Me"; 27 | public const string PreLiveComparison = "PreLiveComparison"; 28 | public const string PriceRating = "PriceRating"; 29 | public const string PriceRatingEntry = "PriceRatingEntry"; 30 | public const string ProcessStep = "ProcessStep"; 31 | public const string PushNotification = "PushNotification"; 32 | public const string Query = "Query"; 33 | public const string SignupStatus = "SignupStatus"; 34 | public const string Subscription = "Subscription"; 35 | public const string Weather = "Weather"; 36 | 37 | public static readonly IReadOnlyDictionary ReverseMapping = 38 | new Dictionary 39 | { 40 | { typeof(bool), "Boolean" }, 41 | { typeof(DateTimeOffset), "String" }, 42 | { typeof(int), "Int" }, 43 | { typeof(string), "String" }, 44 | { typeof(decimal), "Float" }, 45 | { typeof(Guid), "ID" } 46 | }; 47 | } 48 | #endregion 49 | 50 | #region enums 51 | public enum Avatar 52 | { 53 | [EnumMember(Value = "floorhouse1")] Floorhouse1, 54 | [EnumMember(Value = "floorhouse2")] Floorhouse2, 55 | [EnumMember(Value = "floorhouse3")] Floorhouse3, 56 | [EnumMember(Value = "castle")] Castle, 57 | [EnumMember(Value = "apartment")] Apartment, 58 | [EnumMember(Value = "cottage")] Cottage, 59 | [EnumMember(Value = "rowhouse")] Rowhouse 60 | } 61 | 62 | public enum PriceLevel 63 | { 64 | [EnumMember(Value = "low")] Low, 65 | [EnumMember(Value = "normal")] Normal, 66 | [EnumMember(Value = "high")] High 67 | } 68 | 69 | public enum Resolution 70 | { 71 | [EnumMember(Value = "annual")] Annual, 72 | [EnumMember(Value = "monthly")] Monthly 73 | } 74 | #endregion 75 | 76 | #region data classes 77 | public partial class Query 78 | { 79 | public Me Me { get; set; } 80 | } 81 | 82 | public partial class Me 83 | { 84 | public Guid? Id { get; set; } 85 | public string FirstName { get; set; } 86 | public string LastName { get; set; } 87 | public string FullName { get; set; } 88 | public string Ssn { get; set; } 89 | public string Email { get; set; } 90 | public string Language { get; set; } 91 | public string Tone { get; set; } 92 | public ICollection Avatars { get; set; } 93 | public Home Home { get; set; } 94 | public ICollection Homes { get; set; } 95 | public Feed Feed { get; set; } 96 | public ICollection EnergyStatements { get; set; } 97 | } 98 | 99 | public partial class Home 100 | { 101 | public Guid? Id { get; set; } 102 | public Avatar? Avatar { get; set; } 103 | public string TimeZone { get; set; } 104 | public Subscription Subscription { get; set; } 105 | public ICollection ConsumptionMonths { get; set; } 106 | public Consumption Consumption { get; set; } 107 | public PreLiveComparison PreLiveComparison { get; set; } 108 | public ICollection Comparisons { get; set; } 109 | #if !GRAPHQL_GENERATOR_DISABLE_NEWTONSOFT_JSON 110 | [JsonProperty("comparison_current_month")] 111 | #endif 112 | public Comparison ComparisonCurrentMonth { get; set; } 113 | public object Profile { get; set; } 114 | public object ProfileQuestions { get; set; } 115 | public object Thermostat { get; set; } 116 | public ICollection Temperatures { get; set; } 117 | public SignupStatus SignupStatus { get; set; } 118 | public ICollection Disaggregation { get; set; } 119 | public ICollection Devices { get; set; } 120 | public Weather Weather { get; set; } 121 | public VacationMode AwayMode { get; set; } 122 | } 123 | 124 | public partial class Subscription 125 | { 126 | public Guid? Id { get; set; } 127 | public DateTimeOffset? ValidFrom { get; set; } 128 | public DateTimeOffset? ValidTo { get; set; } 129 | public string Status { get; set; } 130 | public int? BillingRegionId { get; set; } 131 | public ICollection EnergyStatements { get; set; } 132 | public PriceRating PriceRating { get; set; } 133 | } 134 | 135 | public partial class PriceRating 136 | { 137 | public decimal? MinPrice { get; set; } 138 | public decimal? MaxPrice { get; set; } 139 | public ICollection Entries { get; set; } 140 | } 141 | 142 | public partial class PriceRatingEntry 143 | { 144 | public string Time { get; set; } 145 | public decimal? Price { get; set; } 146 | public PriceLevel? Level { get; set; } 147 | public decimal? Difference { get; set; } 148 | } 149 | 150 | public partial class ConsumptionMonth 151 | { 152 | public int? Year { get; set; } 153 | public int? Month { get; set; } 154 | public decimal? Kwh { get; set; } 155 | public decimal? Cost { get; set; } 156 | public bool? IsComplete { get; set; } 157 | public decimal? KwhEstimate { get; set; } 158 | public decimal? CostEstimate { get; set; } 159 | } 160 | 161 | public partial class Consumption 162 | { 163 | public object AnnualValues { get; set; } 164 | public object MonthlyValues { get; set; } 165 | public object WeeklyValues { get; set; } 166 | public object DailyValues { get; set; } 167 | public object HourlyValues { get; set; } 168 | public decimal? TotalConsumption { get; set; } 169 | public decimal? EnergyCost { get; set; } 170 | public decimal? TotalCost { get; set; } 171 | public string Currency { get; set; } 172 | public DateTimeOffset? LatestTransactionTimestamp { get; set; } 173 | public string TimeZone { get; set; } 174 | } 175 | 176 | public partial class PreLiveComparison 177 | { 178 | public string HomeId { get; set; } 179 | public bool? BasedOnActuals { get; set; } 180 | public Comparison PreviousYear { get; set; } 181 | public ICollection PreviousYearMonths { get; set; } 182 | } 183 | 184 | public partial class Comparison 185 | { 186 | public int? Year { get; set; } 187 | public int? Month { get; set; } 188 | public string Resolution { get; set; } 189 | public string HomeEfficency { get; set; } 190 | public string HomeEfficencyDescription { get; set; } 191 | public ComparisonData Home { get; set; } 192 | public ComparisonData Average { get; set; } 193 | public ComparisonData Efficient { get; set; } 194 | } 195 | 196 | public partial class ComparisonData 197 | { 198 | public decimal? Cost { get; set; } 199 | public decimal? Consumption { get; set; } 200 | } 201 | 202 | public partial class SignupStatus 203 | { 204 | public ProcessStep FeedStep { get; set; } 205 | public ProcessStep AvatarStep { get; set; } 206 | public ICollection Steps { get; set; } 207 | } 208 | 209 | public partial class ProcessStep 210 | { 211 | public DateTimeOffset? Timestamp { get; set; } 212 | public bool? IsComplete { get; set; } 213 | public string Title { get; set; } 214 | public string Description { get; set; } 215 | } 216 | 217 | public partial class Disaggregation 218 | { 219 | public int? Year { get; set; } 220 | public int? Month { get; set; } 221 | public decimal? FixedConsumptionKwh { get; set; } 222 | public int? FixedConsumptionKwhPercent { get; set; } 223 | public decimal? FixedConsumptionCost { get; set; } 224 | public decimal? HeatingConsumptionKwh { get; set; } 225 | public int? HeatingConsumptionKwhPercent { get; set; } 226 | public decimal? HeatingConsumptionCost { get; set; } 227 | public decimal? BehaviorConsumptionKwh { get; set; } 228 | public int? BehaviorConsumptionKwhPercent { get; set; } 229 | public decimal? BehaviorConsumptionCost { get; set; } 230 | public string Currency { get; set; } 231 | public bool? IsValid { get; set; } 232 | } 233 | 234 | public partial class Device 235 | { 236 | public string DeviceId { get; set; } 237 | public string Type { get; set; } 238 | public bool? IsControllable { get; set; } 239 | public string ExternalId { get; set; } 240 | public string Name { get; set; } 241 | public bool? IsBatteryLow { get; set; } 242 | public bool? IsSignalLow { get; set; } 243 | public bool? IsAlive { get; set; } 244 | public ICollection Capabilities { get; set; } 245 | public object Properties { get; set; } 246 | } 247 | 248 | public partial class Weather 249 | { 250 | public decimal? Temperature { get; set; } 251 | public DateTimeOffset? Timestamp { get; set; } 252 | public string Summary { get; set; } 253 | public string Type { get; set; } 254 | } 255 | 256 | public partial class VacationMode 257 | { 258 | public bool? IsSupported { get; set; } 259 | public AwayModeSettings Settings { get; set; } 260 | } 261 | 262 | public partial class AwayModeSettings 263 | { 264 | public DateTimeOffset? From { get; set; } 265 | public DateTimeOffset? To { get; set; } 266 | } 267 | 268 | public partial class Feed 269 | { 270 | public int? NumberOfItems { get; set; } 271 | public ICollection Items { get; set; } 272 | public ICollection PushNotifications { get; set; } 273 | } 274 | 275 | public partial class FeedItem 276 | { 277 | public int? Id { get; set; } 278 | public string MessageTypeId { get; set; } 279 | public string Link { get; set; } 280 | public string Text { get; set; } 281 | public DateTimeOffset? CreatedAt { get; set; } 282 | public DateTimeOffset? ModifiedAt { get; set; } 283 | } 284 | 285 | public partial class PushNotification 286 | { 287 | public int? Id { get; set; } 288 | public string Link { get; set; } 289 | public string Text { get; set; } 290 | } 291 | #endregion 292 | -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/VerifierExpectations/GraphQlGeneratorTest.GenerateDataClassesWithTypeConfiguration_csharpVersion=CSharp12.verified.txt: -------------------------------------------------------------------------------- 1 | #region GraphQL type helpers 2 | public static class GraphQlTypes 3 | { 4 | public const string Boolean = "Boolean"; 5 | public const string DynamicType = "DynamicType"; 6 | public const string Float = "Float"; 7 | public const string Id = "ID"; 8 | public const string Int = "Int"; 9 | public const string String = "String"; 10 | 11 | public const string Avatar = "Avatar"; 12 | public const string PriceLevel = "PriceLevel"; 13 | public const string Resolution = "Resolution"; 14 | 15 | public const string AwayMode = "AwayMode"; 16 | public const string AwayModeSettings = "AwayModeSettings"; 17 | public const string Comparison = "Comparison"; 18 | public const string ComparisonData = "ComparisonData"; 19 | public const string Consumption = "Consumption"; 20 | public const string ConsumptionMonth = "ConsumptionMonth"; 21 | public const string Device = "Device"; 22 | public const string Disaggregation = "Disaggregation"; 23 | public const string Feed = "Feed"; 24 | public const string FeedItem = "FeedItem"; 25 | public const string Home = "Home"; 26 | public const string Me = "Me"; 27 | public const string PreLiveComparison = "PreLiveComparison"; 28 | public const string PriceRating = "PriceRating"; 29 | public const string PriceRatingEntry = "PriceRatingEntry"; 30 | public const string ProcessStep = "ProcessStep"; 31 | public const string PushNotification = "PushNotification"; 32 | public const string Query = "Query"; 33 | public const string SignupStatus = "SignupStatus"; 34 | public const string Subscription = "Subscription"; 35 | public const string Weather = "Weather"; 36 | 37 | public static readonly IReadOnlyDictionary ReverseMapping = 38 | new Dictionary 39 | { 40 | { typeof(bool), "Boolean" }, 41 | { typeof(DateTimeOffset), "String" }, 42 | { typeof(long), "Int" }, 43 | { typeof(string), "String" }, 44 | { typeof(double), "Float" } 45 | }; 46 | } 47 | #endregion 48 | 49 | #region enums 50 | public enum Avatar 51 | { 52 | [EnumMember(Value = "floorhouse1")] Floorhouse1, 53 | [EnumMember(Value = "floorhouse2")] Floorhouse2, 54 | [EnumMember(Value = "floorhouse3")] Floorhouse3, 55 | [EnumMember(Value = "castle")] Castle, 56 | [EnumMember(Value = "apartment")] Apartment, 57 | [EnumMember(Value = "cottage")] Cottage, 58 | [EnumMember(Value = "rowhouse")] Rowhouse 59 | } 60 | 61 | public enum PriceLevel 62 | { 63 | [EnumMember(Value = "low")] Low, 64 | [EnumMember(Value = "normal")] Normal, 65 | [EnumMember(Value = "high")] High 66 | } 67 | 68 | public enum Resolution 69 | { 70 | [EnumMember(Value = "annual")] Annual, 71 | [EnumMember(Value = "monthly")] Monthly 72 | } 73 | #endregion 74 | 75 | #region data classes 76 | public class Query 77 | { 78 | public Me Me { get => field; set => field = value; } 79 | } 80 | 81 | public class Me 82 | { 83 | public string Id { get => field; set => field = value; } 84 | public string FirstName { get => field; set => field = value; } 85 | public string LastName { get => field; set => field = value; } 86 | public string FullName { get => field; set => field = value; } 87 | public string Ssn { get => field; set => field = value; } 88 | public string Email { get => field; set => field = value; } 89 | public string Language { get => field; set => field = value; } 90 | public string Tone { get => field; set => field = value; } 91 | public ICollection Avatars { get => field; set => field = value; } 92 | public Home Home { get => field; set => field = value; } 93 | public ICollection Homes { get => field; set => field = value; } 94 | public Feed Feed { get => field; set => field = value; } 95 | public ICollection EnergyStatements { get => field; set => field = value; } 96 | } 97 | 98 | public class Home 99 | { 100 | public string Id { get => field; set => field = value; } 101 | public Avatar? Avatar { get => field; set => field = value; } 102 | public string TimeZone { get => field; set => field = value; } 103 | public Subscription Subscription { get => field; set => field = value; } 104 | public ICollection ConsumptionMonths { get => field; set => field = value; } 105 | public Consumption Consumption { get => field; set => field = value; } 106 | public PreLiveComparison PreLiveComparison { get => field; set => field = value; } 107 | public ICollection Comparisons { get => field; set => field = value; } 108 | #if !GRAPHQL_GENERATOR_DISABLE_NEWTONSOFT_JSON 109 | [JsonProperty("comparison_current_month")] 110 | #endif 111 | [System.Text.Json.Serialization.JsonPropertyName("comparison_current_month")] 112 | public Comparison ComparisonCurrentMonth { get => field; set => field = value; } 113 | public object Profile { get => field; set => field = value; } 114 | public object ProfileQuestions { get => field; set => field = value; } 115 | public object Thermostat { get => field; set => field = value; } 116 | public ICollection Temperatures { get => field; set => field = value; } 117 | public SignupStatus SignupStatus { get => field; set => field = value; } 118 | public ICollection Disaggregation { get => field; set => field = value; } 119 | public ICollection Devices { get => field; set => field = value; } 120 | public Weather Weather { get => field; set => field = value; } 121 | public AwayMode AwayMode { get => field; set => field = value; } 122 | } 123 | 124 | public class Subscription 125 | { 126 | public string Id { get => field; set => field = value; } 127 | public DateTimeOffset? ValidFrom { get => field; set => field = value; } 128 | public DateTimeOffset? ValidTo { get => field; set => field = value; } 129 | public string Status { get => field; set => field = value; } 130 | public long? BillingRegionId { get => field; set => field = value; } 131 | public ICollection EnergyStatements { get => field; set => field = value; } 132 | public PriceRating PriceRating { get => field; set => field = value; } 133 | } 134 | 135 | public class PriceRating 136 | { 137 | public double? MinPrice { get => field; set => field = value; } 138 | public double? MaxPrice { get => field; set => field = value; } 139 | public ICollection Entries { get => field; set => field = value; } 140 | } 141 | 142 | public class PriceRatingEntry 143 | { 144 | public string Time { get => field; set => field = value; } 145 | public double? Price { get => field; set => field = value; } 146 | public PriceLevel? Level { get => field; set => field = value; } 147 | public double? Difference { get => field; set => field = value; } 148 | } 149 | 150 | public class ConsumptionMonth 151 | { 152 | public long? Year { get => field; set => field = value; } 153 | public long? Month { get => field; set => field = value; } 154 | public double? Kwh { get => field; set => field = value; } 155 | public double? Cost { get => field; set => field = value; } 156 | public bool IsComplete { get => field; set => field = value; } 157 | public double? KwhEstimate { get => field; set => field = value; } 158 | public double? CostEstimate { get => field; set => field = value; } 159 | } 160 | 161 | public class Consumption 162 | { 163 | public object AnnualValues { get => field; set => field = value; } 164 | public object MonthlyValues { get => field; set => field = value; } 165 | public object WeeklyValues { get => field; set => field = value; } 166 | public object DailyValues { get => field; set => field = value; } 167 | public object HourlyValues { get => field; set => field = value; } 168 | public double? TotalConsumption { get => field; set => field = value; } 169 | public double? EnergyCost { get => field; set => field = value; } 170 | public double? TotalCost { get => field; set => field = value; } 171 | public string Currency { get => field; set => field = value; } 172 | public DateTimeOffset? LatestTransactionTimestamp { get => field; set => field = value; } 173 | public string TimeZone { get => field; set => field = value; } 174 | } 175 | 176 | public class PreLiveComparison 177 | { 178 | public string HomeId { get => field; set => field = value; } 179 | public bool BasedOnActuals { get => field; set => field = value; } 180 | public Comparison PreviousYear { get => field; set => field = value; } 181 | public ICollection PreviousYearMonths { get => field; set => field = value; } 182 | } 183 | 184 | public class Comparison 185 | { 186 | public long? Year { get => field; set => field = value; } 187 | public long? Month { get => field; set => field = value; } 188 | public string Resolution { get => field; set => field = value; } 189 | public string HomeEfficency { get => field; set => field = value; } 190 | public string HomeEfficencyDescription { get => field; set => field = value; } 191 | public ComparisonData Home { get => field; set => field = value; } 192 | public ComparisonData Average { get => field; set => field = value; } 193 | public ComparisonData Efficient { get => field; set => field = value; } 194 | } 195 | 196 | public class ComparisonData 197 | { 198 | public double? Cost { get => field; set => field = value; } 199 | public double? Consumption { get => field; set => field = value; } 200 | } 201 | 202 | public class SignupStatus 203 | { 204 | public ProcessStep FeedStep { get => field; set => field = value; } 205 | public ProcessStep AvatarStep { get => field; set => field = value; } 206 | public ICollection Steps { get => field; set => field = value; } 207 | } 208 | 209 | public class ProcessStep 210 | { 211 | public DateTimeOffset? Timestamp { get => field; set => field = value; } 212 | public bool IsComplete { get => field; set => field = value; } 213 | public string Title { get => field; set => field = value; } 214 | public string Description { get => field; set => field = value; } 215 | } 216 | 217 | public class Disaggregation 218 | { 219 | public long? Year { get => field; set => field = value; } 220 | public long? Month { get => field; set => field = value; } 221 | public double? FixedConsumptionKwh { get => field; set => field = value; } 222 | public long? FixedConsumptionKwhPercent { get => field; set => field = value; } 223 | public double? FixedConsumptionCost { get => field; set => field = value; } 224 | public double? HeatingConsumptionKwh { get => field; set => field = value; } 225 | public long? HeatingConsumptionKwhPercent { get => field; set => field = value; } 226 | public double? HeatingConsumptionCost { get => field; set => field = value; } 227 | public double? BehaviorConsumptionKwh { get => field; set => field = value; } 228 | public long? BehaviorConsumptionKwhPercent { get => field; set => field = value; } 229 | public double? BehaviorConsumptionCost { get => field; set => field = value; } 230 | public string Currency { get => field; set => field = value; } 231 | public bool IsValid { get => field; set => field = value; } 232 | } 233 | 234 | public class Device 235 | { 236 | public string DeviceId { get => field; set => field = value; } 237 | public string Type { get => field; set => field = value; } 238 | public bool IsControllable { get => field; set => field = value; } 239 | public string ExternalId { get => field; set => field = value; } 240 | public string Name { get => field; set => field = value; } 241 | public bool IsBatteryLow { get => field; set => field = value; } 242 | public bool IsSignalLow { get => field; set => field = value; } 243 | public bool IsAlive { get => field; set => field = value; } 244 | public ICollection Capabilities { get => field; set => field = value; } 245 | public object Properties { get => field; set => field = value; } 246 | } 247 | 248 | public class Weather 249 | { 250 | public double? Temperature { get => field; set => field = value; } 251 | public DateTimeOffset? Timestamp { get => field; set => field = value; } 252 | public string Summary { get => field; set => field = value; } 253 | public string Type { get => field; set => field = value; } 254 | } 255 | 256 | public class AwayMode 257 | { 258 | public bool IsSupported { get => field; set => field = value; } 259 | public AwayModeSettings Settings { get => field; set => field = value; } 260 | } 261 | 262 | public class AwayModeSettings 263 | { 264 | public DateTimeOffset? From { get => field; set => field = value; } 265 | public DateTimeOffset? To { get => field; set => field = value; } 266 | } 267 | 268 | public class Feed 269 | { 270 | public long? NumberOfItems { get => field; set => field = value; } 271 | public ICollection Items { get => field; set => field = value; } 272 | public ICollection PushNotifications { get => field; set => field = value; } 273 | } 274 | 275 | public class FeedItem 276 | { 277 | public long? Id { get => field; set => field = value; } 278 | public string MessageTypeId { get => field; set => field = value; } 279 | public string Link { get => field; set => field = value; } 280 | public string Text { get => field; set => field = value; } 281 | public DateTimeOffset? CreatedAt { get => field; set => field = value; } 282 | public DateTimeOffset? ModifiedAt { get => field; set => field = value; } 283 | } 284 | 285 | public class PushNotification 286 | { 287 | public long? Id { get => field; set => field = value; } 288 | public string Link { get => field; set => field = value; } 289 | public string Text { get => field; set => field = value; } 290 | } 291 | #endregion 292 | -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/VerifierExpectations/GraphQlGeneratorTest.GenerateDataClassesWithTypeConfiguration_csharpVersion=Compatible.verified.txt: -------------------------------------------------------------------------------- 1 | #region GraphQL type helpers 2 | public static class GraphQlTypes 3 | { 4 | public const string Boolean = "Boolean"; 5 | public const string DynamicType = "DynamicType"; 6 | public const string Float = "Float"; 7 | public const string Id = "ID"; 8 | public const string Int = "Int"; 9 | public const string String = "String"; 10 | 11 | public const string Avatar = "Avatar"; 12 | public const string PriceLevel = "PriceLevel"; 13 | public const string Resolution = "Resolution"; 14 | 15 | public const string AwayMode = "AwayMode"; 16 | public const string AwayModeSettings = "AwayModeSettings"; 17 | public const string Comparison = "Comparison"; 18 | public const string ComparisonData = "ComparisonData"; 19 | public const string Consumption = "Consumption"; 20 | public const string ConsumptionMonth = "ConsumptionMonth"; 21 | public const string Device = "Device"; 22 | public const string Disaggregation = "Disaggregation"; 23 | public const string Feed = "Feed"; 24 | public const string FeedItem = "FeedItem"; 25 | public const string Home = "Home"; 26 | public const string Me = "Me"; 27 | public const string PreLiveComparison = "PreLiveComparison"; 28 | public const string PriceRating = "PriceRating"; 29 | public const string PriceRatingEntry = "PriceRatingEntry"; 30 | public const string ProcessStep = "ProcessStep"; 31 | public const string PushNotification = "PushNotification"; 32 | public const string Query = "Query"; 33 | public const string SignupStatus = "SignupStatus"; 34 | public const string Subscription = "Subscription"; 35 | public const string Weather = "Weather"; 36 | 37 | public static readonly IReadOnlyDictionary ReverseMapping = 38 | new Dictionary 39 | { 40 | { typeof(bool), "Boolean" }, 41 | { typeof(DateTimeOffset), "String" }, 42 | { typeof(long), "Int" }, 43 | { typeof(string), "String" }, 44 | { typeof(double), "Float" } 45 | }; 46 | } 47 | #endregion 48 | 49 | #region enums 50 | public enum Avatar 51 | { 52 | [EnumMember(Value = "floorhouse1")] Floorhouse1, 53 | [EnumMember(Value = "floorhouse2")] Floorhouse2, 54 | [EnumMember(Value = "floorhouse3")] Floorhouse3, 55 | [EnumMember(Value = "castle")] Castle, 56 | [EnumMember(Value = "apartment")] Apartment, 57 | [EnumMember(Value = "cottage")] Cottage, 58 | [EnumMember(Value = "rowhouse")] Rowhouse 59 | } 60 | 61 | public enum PriceLevel 62 | { 63 | [EnumMember(Value = "low")] Low, 64 | [EnumMember(Value = "normal")] Normal, 65 | [EnumMember(Value = "high")] High 66 | } 67 | 68 | public enum Resolution 69 | { 70 | [EnumMember(Value = "annual")] Annual, 71 | [EnumMember(Value = "monthly")] Monthly 72 | } 73 | #endregion 74 | 75 | #region data classes 76 | public class Query 77 | { 78 | private Me _me; 79 | 80 | public Me Me { get { return _me; } set { _me = value; } } 81 | } 82 | 83 | public class Me 84 | { 85 | private string _id; 86 | private string _firstName; 87 | private string _lastName; 88 | private string _fullName; 89 | private string _ssn; 90 | private string _email; 91 | private string _language; 92 | private string _tone; 93 | private ICollection _avatars; 94 | private Home _home; 95 | private ICollection _homes; 96 | private Feed _feed; 97 | private ICollection _energyStatements; 98 | 99 | public string Id { get { return _id; } set { _id = value; } } 100 | public string FirstName { get { return _firstName; } set { _firstName = value; } } 101 | public string LastName { get { return _lastName; } set { _lastName = value; } } 102 | public string FullName { get { return _fullName; } set { _fullName = value; } } 103 | public string Ssn { get { return _ssn; } set { _ssn = value; } } 104 | public string Email { get { return _email; } set { _email = value; } } 105 | public string Language { get { return _language; } set { _language = value; } } 106 | public string Tone { get { return _tone; } set { _tone = value; } } 107 | public ICollection Avatars { get { return _avatars; } set { _avatars = value; } } 108 | public Home Home { get { return _home; } set { _home = value; } } 109 | public ICollection Homes { get { return _homes; } set { _homes = value; } } 110 | public Feed Feed { get { return _feed; } set { _feed = value; } } 111 | public ICollection EnergyStatements { get { return _energyStatements; } set { _energyStatements = value; } } 112 | } 113 | 114 | public class Home 115 | { 116 | private string _id; 117 | private Avatar? _avatar; 118 | private string _timeZone; 119 | private Subscription _subscription; 120 | private ICollection _consumptionMonths; 121 | private Consumption _consumption; 122 | private PreLiveComparison _preLiveComparison; 123 | private ICollection _comparisons; 124 | private Comparison _comparisonCurrentMonth; 125 | private object _profile; 126 | private object _profileQuestions; 127 | private object _thermostat; 128 | private ICollection _temperatures; 129 | private SignupStatus _signupStatus; 130 | private ICollection _disaggregation; 131 | private ICollection _devices; 132 | private Weather _weather; 133 | private AwayMode _awayMode; 134 | 135 | public string Id { get { return _id; } set { _id = value; } } 136 | public Avatar? Avatar { get { return _avatar; } set { _avatar = value; } } 137 | public string TimeZone { get { return _timeZone; } set { _timeZone = value; } } 138 | public Subscription Subscription { get { return _subscription; } set { _subscription = value; } } 139 | public ICollection ConsumptionMonths { get { return _consumptionMonths; } set { _consumptionMonths = value; } } 140 | public Consumption Consumption { get { return _consumption; } set { _consumption = value; } } 141 | public PreLiveComparison PreLiveComparison { get { return _preLiveComparison; } set { _preLiveComparison = value; } } 142 | public ICollection Comparisons { get { return _comparisons; } set { _comparisons = value; } } 143 | #if !GRAPHQL_GENERATOR_DISABLE_NEWTONSOFT_JSON 144 | [JsonProperty("comparison_current_month")] 145 | #endif 146 | public Comparison ComparisonCurrentMonth { get { return _comparisonCurrentMonth; } set { _comparisonCurrentMonth = value; } } 147 | public object Profile { get { return _profile; } set { _profile = value; } } 148 | public object ProfileQuestions { get { return _profileQuestions; } set { _profileQuestions = value; } } 149 | public object Thermostat { get { return _thermostat; } set { _thermostat = value; } } 150 | public ICollection Temperatures { get { return _temperatures; } set { _temperatures = value; } } 151 | public SignupStatus SignupStatus { get { return _signupStatus; } set { _signupStatus = value; } } 152 | public ICollection Disaggregation { get { return _disaggregation; } set { _disaggregation = value; } } 153 | public ICollection Devices { get { return _devices; } set { _devices = value; } } 154 | public Weather Weather { get { return _weather; } set { _weather = value; } } 155 | public AwayMode AwayMode { get { return _awayMode; } set { _awayMode = value; } } 156 | } 157 | 158 | public class Subscription 159 | { 160 | private string _id; 161 | private DateTimeOffset? _validFrom; 162 | private DateTimeOffset? _validTo; 163 | private string _status; 164 | private long? _billingRegionId; 165 | private ICollection _energyStatements; 166 | private PriceRating _priceRating; 167 | 168 | public string Id { get { return _id; } set { _id = value; } } 169 | public DateTimeOffset? ValidFrom { get { return _validFrom; } set { _validFrom = value; } } 170 | public DateTimeOffset? ValidTo { get { return _validTo; } set { _validTo = value; } } 171 | public string Status { get { return _status; } set { _status = value; } } 172 | public long? BillingRegionId { get { return _billingRegionId; } set { _billingRegionId = value; } } 173 | public ICollection EnergyStatements { get { return _energyStatements; } set { _energyStatements = value; } } 174 | public PriceRating PriceRating { get { return _priceRating; } set { _priceRating = value; } } 175 | } 176 | 177 | public class PriceRating 178 | { 179 | private double? _minPrice; 180 | private double? _maxPrice; 181 | private ICollection _entries; 182 | 183 | public double? MinPrice { get { return _minPrice; } set { _minPrice = value; } } 184 | public double? MaxPrice { get { return _maxPrice; } set { _maxPrice = value; } } 185 | public ICollection Entries { get { return _entries; } set { _entries = value; } } 186 | } 187 | 188 | public class PriceRatingEntry 189 | { 190 | private string _time; 191 | private double? _price; 192 | private PriceLevel? _level; 193 | private double? _difference; 194 | 195 | public string Time { get { return _time; } set { _time = value; } } 196 | public double? Price { get { return _price; } set { _price = value; } } 197 | public PriceLevel? Level { get { return _level; } set { _level = value; } } 198 | public double? Difference { get { return _difference; } set { _difference = value; } } 199 | } 200 | 201 | public class ConsumptionMonth 202 | { 203 | private long? _year; 204 | private long? _month; 205 | private double? _kwh; 206 | private double? _cost; 207 | private bool _isComplete; 208 | private double? _kwhEstimate; 209 | private double? _costEstimate; 210 | 211 | public long? Year { get { return _year; } set { _year = value; } } 212 | public long? Month { get { return _month; } set { _month = value; } } 213 | public double? Kwh { get { return _kwh; } set { _kwh = value; } } 214 | public double? Cost { get { return _cost; } set { _cost = value; } } 215 | public bool IsComplete { get { return _isComplete; } set { _isComplete = value; } } 216 | public double? KwhEstimate { get { return _kwhEstimate; } set { _kwhEstimate = value; } } 217 | public double? CostEstimate { get { return _costEstimate; } set { _costEstimate = value; } } 218 | } 219 | 220 | public class Consumption 221 | { 222 | private object _annualValues; 223 | private object _monthlyValues; 224 | private object _weeklyValues; 225 | private object _dailyValues; 226 | private object _hourlyValues; 227 | private double? _totalConsumption; 228 | private double? _energyCost; 229 | private double? _totalCost; 230 | private string _currency; 231 | private DateTimeOffset? _latestTransactionTimestamp; 232 | private string _timeZone; 233 | 234 | public object AnnualValues { get { return _annualValues; } set { _annualValues = value; } } 235 | public object MonthlyValues { get { return _monthlyValues; } set { _monthlyValues = value; } } 236 | public object WeeklyValues { get { return _weeklyValues; } set { _weeklyValues = value; } } 237 | public object DailyValues { get { return _dailyValues; } set { _dailyValues = value; } } 238 | public object HourlyValues { get { return _hourlyValues; } set { _hourlyValues = value; } } 239 | public double? TotalConsumption { get { return _totalConsumption; } set { _totalConsumption = value; } } 240 | public double? EnergyCost { get { return _energyCost; } set { _energyCost = value; } } 241 | public double? TotalCost { get { return _totalCost; } set { _totalCost = value; } } 242 | public string Currency { get { return _currency; } set { _currency = value; } } 243 | public DateTimeOffset? LatestTransactionTimestamp { get { return _latestTransactionTimestamp; } set { _latestTransactionTimestamp = value; } } 244 | public string TimeZone { get { return _timeZone; } set { _timeZone = value; } } 245 | } 246 | 247 | public class PreLiveComparison 248 | { 249 | private string _homeId; 250 | private bool _basedOnActuals; 251 | private Comparison _previousYear; 252 | private ICollection _previousYearMonths; 253 | 254 | public string HomeId { get { return _homeId; } set { _homeId = value; } } 255 | public bool BasedOnActuals { get { return _basedOnActuals; } set { _basedOnActuals = value; } } 256 | public Comparison PreviousYear { get { return _previousYear; } set { _previousYear = value; } } 257 | public ICollection PreviousYearMonths { get { return _previousYearMonths; } set { _previousYearMonths = value; } } 258 | } 259 | 260 | public class Comparison 261 | { 262 | private long? _year; 263 | private long? _month; 264 | private string _resolution; 265 | private string _homeEfficency; 266 | private string _homeEfficencyDescription; 267 | private ComparisonData _home; 268 | private ComparisonData _average; 269 | private ComparisonData _efficient; 270 | 271 | public long? Year { get { return _year; } set { _year = value; } } 272 | public long? Month { get { return _month; } set { _month = value; } } 273 | public string Resolution { get { return _resolution; } set { _resolution = value; } } 274 | public string HomeEfficency { get { return _homeEfficency; } set { _homeEfficency = value; } } 275 | public string HomeEfficencyDescription { get { return _homeEfficencyDescription; } set { _homeEfficencyDescription = value; } } 276 | public ComparisonData Home { get { return _home; } set { _home = value; } } 277 | public ComparisonData Average { get { return _average; } set { _average = value; } } 278 | public ComparisonData Efficient { get { return _efficient; } set { _efficient = value; } } 279 | } 280 | 281 | public class ComparisonData 282 | { 283 | private double? _cost; 284 | private double? _consumption; 285 | 286 | public double? Cost { get { return _cost; } set { _cost = value; } } 287 | public double? Consumption { get { return _consumption; } set { _consumption = value; } } 288 | } 289 | 290 | public class SignupStatus 291 | { 292 | private ProcessStep _feedStep; 293 | private ProcessStep _avatarStep; 294 | private ICollection _steps; 295 | 296 | public ProcessStep FeedStep { get { return _feedStep; } set { _feedStep = value; } } 297 | public ProcessStep AvatarStep { get { return _avatarStep; } set { _avatarStep = value; } } 298 | public ICollection Steps { get { return _steps; } set { _steps = value; } } 299 | } 300 | 301 | public class ProcessStep 302 | { 303 | private DateTimeOffset? _timestamp; 304 | private bool _isComplete; 305 | private string _title; 306 | private string _description; 307 | 308 | public DateTimeOffset? Timestamp { get { return _timestamp; } set { _timestamp = value; } } 309 | public bool IsComplete { get { return _isComplete; } set { _isComplete = value; } } 310 | public string Title { get { return _title; } set { _title = value; } } 311 | public string Description { get { return _description; } set { _description = value; } } 312 | } 313 | 314 | public class Disaggregation 315 | { 316 | private long? _year; 317 | private long? _month; 318 | private double? _fixedConsumptionKwh; 319 | private long? _fixedConsumptionKwhPercent; 320 | private double? _fixedConsumptionCost; 321 | private double? _heatingConsumptionKwh; 322 | private long? _heatingConsumptionKwhPercent; 323 | private double? _heatingConsumptionCost; 324 | private double? _behaviorConsumptionKwh; 325 | private long? _behaviorConsumptionKwhPercent; 326 | private double? _behaviorConsumptionCost; 327 | private string _currency; 328 | private bool _isValid; 329 | 330 | public long? Year { get { return _year; } set { _year = value; } } 331 | public long? Month { get { return _month; } set { _month = value; } } 332 | public double? FixedConsumptionKwh { get { return _fixedConsumptionKwh; } set { _fixedConsumptionKwh = value; } } 333 | public long? FixedConsumptionKwhPercent { get { return _fixedConsumptionKwhPercent; } set { _fixedConsumptionKwhPercent = value; } } 334 | public double? FixedConsumptionCost { get { return _fixedConsumptionCost; } set { _fixedConsumptionCost = value; } } 335 | public double? HeatingConsumptionKwh { get { return _heatingConsumptionKwh; } set { _heatingConsumptionKwh = value; } } 336 | public long? HeatingConsumptionKwhPercent { get { return _heatingConsumptionKwhPercent; } set { _heatingConsumptionKwhPercent = value; } } 337 | public double? HeatingConsumptionCost { get { return _heatingConsumptionCost; } set { _heatingConsumptionCost = value; } } 338 | public double? BehaviorConsumptionKwh { get { return _behaviorConsumptionKwh; } set { _behaviorConsumptionKwh = value; } } 339 | public long? BehaviorConsumptionKwhPercent { get { return _behaviorConsumptionKwhPercent; } set { _behaviorConsumptionKwhPercent = value; } } 340 | public double? BehaviorConsumptionCost { get { return _behaviorConsumptionCost; } set { _behaviorConsumptionCost = value; } } 341 | public string Currency { get { return _currency; } set { _currency = value; } } 342 | public bool IsValid { get { return _isValid; } set { _isValid = value; } } 343 | } 344 | 345 | public class Device 346 | { 347 | private string _deviceId; 348 | private string _type; 349 | private bool _isControllable; 350 | private string _externalId; 351 | private string _name; 352 | private bool _isBatteryLow; 353 | private bool _isSignalLow; 354 | private bool _isAlive; 355 | private ICollection _capabilities; 356 | private object _properties; 357 | 358 | public string DeviceId { get { return _deviceId; } set { _deviceId = value; } } 359 | public string Type { get { return _type; } set { _type = value; } } 360 | public bool IsControllable { get { return _isControllable; } set { _isControllable = value; } } 361 | public string ExternalId { get { return _externalId; } set { _externalId = value; } } 362 | public string Name { get { return _name; } set { _name = value; } } 363 | public bool IsBatteryLow { get { return _isBatteryLow; } set { _isBatteryLow = value; } } 364 | public bool IsSignalLow { get { return _isSignalLow; } set { _isSignalLow = value; } } 365 | public bool IsAlive { get { return _isAlive; } set { _isAlive = value; } } 366 | public ICollection Capabilities { get { return _capabilities; } set { _capabilities = value; } } 367 | public object Properties { get { return _properties; } set { _properties = value; } } 368 | } 369 | 370 | public class Weather 371 | { 372 | private double? _temperature; 373 | private DateTimeOffset? _timestamp; 374 | private string _summary; 375 | private string _type; 376 | 377 | public double? Temperature { get { return _temperature; } set { _temperature = value; } } 378 | public DateTimeOffset? Timestamp { get { return _timestamp; } set { _timestamp = value; } } 379 | public string Summary { get { return _summary; } set { _summary = value; } } 380 | public string Type { get { return _type; } set { _type = value; } } 381 | } 382 | 383 | public class AwayMode 384 | { 385 | private bool _isSupported; 386 | private AwayModeSettings _settings; 387 | 388 | public bool IsSupported { get { return _isSupported; } set { _isSupported = value; } } 389 | public AwayModeSettings Settings { get { return _settings; } set { _settings = value; } } 390 | } 391 | 392 | public class AwayModeSettings 393 | { 394 | private DateTimeOffset? _from; 395 | private DateTimeOffset? _to; 396 | 397 | public DateTimeOffset? From { get { return _from; } set { _from = value; } } 398 | public DateTimeOffset? To { get { return _to; } set { _to = value; } } 399 | } 400 | 401 | public class Feed 402 | { 403 | private long? _numberOfItems; 404 | private ICollection _items; 405 | private ICollection _pushNotifications; 406 | 407 | public long? NumberOfItems { get { return _numberOfItems; } set { _numberOfItems = value; } } 408 | public ICollection Items { get { return _items; } set { _items = value; } } 409 | public ICollection PushNotifications { get { return _pushNotifications; } set { _pushNotifications = value; } } 410 | } 411 | 412 | public class FeedItem 413 | { 414 | private long? _id; 415 | private string _messageTypeId; 416 | private string _link; 417 | private string _text; 418 | private DateTimeOffset? _createdAt; 419 | private DateTimeOffset? _modifiedAt; 420 | 421 | public long? Id { get { return _id; } set { _id = value; } } 422 | public string MessageTypeId { get { return _messageTypeId; } set { _messageTypeId = value; } } 423 | public string Link { get { return _link; } set { _link = value; } } 424 | public string Text { get { return _text; } set { _text = value; } } 425 | public DateTimeOffset? CreatedAt { get { return _createdAt; } set { _createdAt = value; } } 426 | public DateTimeOffset? ModifiedAt { get { return _modifiedAt; } set { _modifiedAt = value; } } 427 | } 428 | 429 | public class PushNotification 430 | { 431 | private long? _id; 432 | private string _link; 433 | private string _text; 434 | 435 | public long? Id { get { return _id; } set { _id = value; } } 436 | public string Link { get { return _link; } set { _link = value; } } 437 | public string Text { get { return _text; } set { _text = value; } } 438 | } 439 | #endregion 440 | -------------------------------------------------------------------------------- /test/GraphQlClientGenerator.Test/VerifierExpectations/GraphQlGeneratorTest.WithNestedListsOfComplexObjects.verified.txt: -------------------------------------------------------------------------------- 1 | #region GraphQL type helpers 2 | public static class GraphQlTypes 3 | { 4 | public const string Boolean = "Boolean"; 5 | public const string String = "String"; 6 | 7 | public const string AnotherComplex = "AnotherComplex"; 8 | public const string ObjectWithNestedListsField = "ObjectWithNestedListsField"; 9 | public const string Query = "Query"; 10 | 11 | public static readonly IReadOnlyDictionary ReverseMapping = 12 | new Dictionary 13 | { 14 | { typeof(string), "String" } 15 | }; 16 | } 17 | #endregion 18 | 19 | #region directives 20 | public class IncludeDirective : GraphQlDirective 21 | { 22 | public IncludeDirective(QueryBuilderParameter @if) : base("include") 23 | { 24 | AddArgument("if", @if); 25 | } 26 | } 27 | 28 | public class SkipDirective : GraphQlDirective 29 | { 30 | public SkipDirective(QueryBuilderParameter @if) : base("skip") 31 | { 32 | AddArgument("if", @if); 33 | } 34 | } 35 | #endregion 36 | 37 | #region builder classes 38 | public partial class QueryQueryBuilder : GraphQlQueryBuilder 39 | { 40 | private static readonly GraphQlFieldMetadata[] AllFieldMetadata = 41 | new [] 42 | { 43 | new GraphQlFieldMetadata { Name = "objectWithNestedListsField", IsComplex = true, QueryBuilderType = typeof(ObjectWithNestedListsFieldQueryBuilder) } 44 | }; 45 | 46 | protected override string TypeName { get { return "Query"; } } 47 | 48 | public override IReadOnlyList AllFields { get { return AllFieldMetadata; } } 49 | 50 | public QueryQueryBuilder(string operationName = null) : base("query", operationName) 51 | { 52 | } 53 | 54 | public QueryQueryBuilder WithParameter(GraphQlQueryParameter parameter) 55 | { 56 | return WithParameterInternal(parameter); 57 | } 58 | 59 | public QueryQueryBuilder WithObjectWithNestedListsField(ObjectWithNestedListsFieldQueryBuilder objectWithNestedListsFieldQueryBuilder, string alias = null, IncludeDirective include = null, SkipDirective skip = null) 60 | { 61 | return WithObjectField("objectWithNestedListsField", alias, objectWithNestedListsFieldQueryBuilder, new GraphQlDirective[] { include, skip }); 62 | } 63 | 64 | public QueryQueryBuilder ExceptObjectWithNestedListsField() 65 | { 66 | return ExceptField("objectWithNestedListsField"); 67 | } 68 | } 69 | 70 | public partial class ObjectWithNestedListsFieldQueryBuilder : GraphQlQueryBuilder 71 | { 72 | private static readonly GraphQlFieldMetadata[] AllFieldMetadata = 73 | new [] 74 | { 75 | new GraphQlFieldMetadata { Name = "nestedListOfComplexObjects", IsComplex = true, QueryBuilderType = typeof(AnotherComplexQueryBuilder) } 76 | }; 77 | 78 | protected override string TypeName { get { return "ObjectWithNestedListsField"; } } 79 | 80 | public override IReadOnlyList AllFields { get { return AllFieldMetadata; } } 81 | 82 | public ObjectWithNestedListsFieldQueryBuilder WithNestedListOfComplexObjects(AnotherComplexQueryBuilder anotherComplexQueryBuilder, string alias = null, IncludeDirective include = null, SkipDirective skip = null) 83 | { 84 | return WithObjectField("nestedListOfComplexObjects", alias, anotherComplexQueryBuilder, new GraphQlDirective[] { include, skip }); 85 | } 86 | 87 | public ObjectWithNestedListsFieldQueryBuilder ExceptNestedListOfComplexObjects() 88 | { 89 | return ExceptField("nestedListOfComplexObjects"); 90 | } 91 | } 92 | 93 | public partial class AnotherComplexQueryBuilder : GraphQlQueryBuilder 94 | { 95 | private static readonly GraphQlFieldMetadata[] AllFieldMetadata = 96 | new [] 97 | { 98 | new GraphQlFieldMetadata { Name = "justAString" } 99 | }; 100 | 101 | protected override string TypeName { get { return "AnotherComplex"; } } 102 | 103 | public override IReadOnlyList AllFields { get { return AllFieldMetadata; } } 104 | 105 | public AnotherComplexQueryBuilder WithJustAString(string alias = null, IncludeDirective include = null, SkipDirective skip = null) 106 | { 107 | return WithScalarField("justAString", alias, new GraphQlDirective[] { include, skip }); 108 | } 109 | 110 | public AnotherComplexQueryBuilder ExceptJustAString() 111 | { 112 | return ExceptField("justAString"); 113 | } 114 | } 115 | #endregion 116 | 117 | #region data classes 118 | public partial class Query 119 | { 120 | public ObjectWithNestedListsField ObjectWithNestedListsField { get; set; } 121 | } 122 | 123 | public partial class ObjectWithNestedListsField 124 | { 125 | public ICollection> NestedListOfComplexObjects { get; set; } 126 | } 127 | 128 | public partial class AnotherComplex 129 | { 130 | public string JustAString { get; set; } 131 | } 132 | #endregion --------------------------------------------------------------------------------