├── .config └── dotnet-tools.json ├── .editorconfig ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── GitVersion.yml ├── GraphQL.Query.Builder.sln ├── LICENSE ├── README.md ├── azure-pipelines.yml ├── docs ├── README.md ├── _config.yml └── api │ ├── graphql.query.builder.camelcasepropertynameformatter.md │ ├── graphql.query.builder.iquery-1.md │ ├── graphql.query.builder.iquery.md │ ├── graphql.query.builder.iquerystringbuilder.md │ ├── graphql.query.builder.query-1.md │ ├── graphql.query.builder.queryoptions.md │ ├── graphql.query.builder.querystringbuilder.md │ └── index.md ├── generate-documentation.bat ├── generate-documentation.sh ├── logo.pdn ├── logo.png ├── sample ├── Models │ ├── Attack.cs │ ├── Models.csproj │ ├── Pokemon.cs │ ├── PokemonAttack.cs │ └── PokemonDimension.cs └── Pokedex │ ├── Pokedex.csproj │ ├── PokemonResponse.cs │ ├── PokemonService.cs │ ├── PokemonsResponse.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ └── README.md ├── src └── GraphQL.Query.Builder │ ├── CamelCasePropertyNameFormatter.cs │ ├── GraphQL.Query.Builder.csproj │ ├── IQuery.cs │ ├── IQueryOf{T}.cs │ ├── IQueryStringBuilder.cs │ ├── QueryOf{T}.cs │ ├── QueryOptions.cs │ ├── QueryStringBuilder.cs │ └── RequiredArgument.cs └── tests └── GraphQL.Query.Builder.UnitTests ├── CamelCasePropertyNameFormatterTests.cs ├── CustomQueryStringBuilderTests.cs ├── GraphQL.Query.Builder.UnitTests.csproj ├── Models ├── Car.cs ├── Color.cs ├── Customer.cs ├── Order.cs ├── Truck.cs └── Vehicule.cs ├── QueryOf{T}Tests.cs ├── QueryStringBuilderTests.cs └── RequiredArgumentTests.cs /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "xmldoc2markdown": { 6 | "version": "5.0.0", 7 | "commands": [ 8 | "xmldoc2md" 9 | ] 10 | }, 11 | "dotnet-reportgenerator-globaltool": { 12 | "version": "5.3.8", 13 | "commands": [ 14 | "reportgenerator" 15 | ] 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Core EditorConfig Options # 3 | ############################### 4 | root = true 5 | # All files 6 | [*] 7 | indent_style = space 8 | 9 | # XML project files 10 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 11 | indent_size = 2 12 | 13 | # XML config files 14 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] 15 | indent_size = 2 16 | 17 | # Code files 18 | [*.{cs,csx,vb,vbx}] 19 | indent_size = 4 20 | insert_final_newline = true 21 | charset = utf-8 22 | ############################### 23 | # .NET Coding Conventions # 24 | ############################### 25 | [*.{cs,vb}] 26 | # Organize usings 27 | dotnet_sort_system_directives_first = true 28 | # this. preferences 29 | dotnet_style_qualification_for_field = true:warning 30 | dotnet_style_qualification_for_property = true:warning 31 | dotnet_style_qualification_for_method = true:warning 32 | dotnet_style_qualification_for_event = true:warning 33 | # Language keywords vs BCL types preferences 34 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent 35 | dotnet_style_predefined_type_for_member_access = true:silent 36 | # Parentheses preferences 37 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 38 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 39 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 40 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 41 | # Modifier preferences 42 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 43 | dotnet_style_readonly_field = true:suggestion 44 | # Expression-level preferences 45 | dotnet_style_object_initializer = true:suggestion 46 | dotnet_style_collection_initializer = true:suggestion 47 | dotnet_style_explicit_tuple_names = true:suggestion 48 | dotnet_style_null_propagation = true:suggestion 49 | dotnet_style_coalesce_expression = true:suggestion 50 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent 51 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 52 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 53 | dotnet_style_prefer_auto_properties = true:silent 54 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 55 | dotnet_style_prefer_conditional_expression_over_return = true:silent 56 | ############################### 57 | # Naming Conventions # 58 | ############################### 59 | # Style Definitions 60 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 61 | # Use PascalCase for constant fields 62 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion 63 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 64 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style 65 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 66 | dotnet_naming_symbols.constant_fields.applicable_accessibilities = * 67 | dotnet_naming_symbols.constant_fields.required_modifiers = const 68 | ############################### 69 | # C# Coding Conventions # 70 | ############################### 71 | [*.cs] 72 | # var preferences 73 | csharp_style_var_for_built_in_types = false:warning 74 | csharp_style_var_when_type_is_apparent = false:warning 75 | csharp_style_var_elsewhere = false:warning 76 | # Expression-bodied members 77 | csharp_style_expression_bodied_methods = false:silent 78 | csharp_style_expression_bodied_constructors = false:silent 79 | csharp_style_expression_bodied_operators = false:silent 80 | csharp_style_expression_bodied_properties = true:silent 81 | csharp_style_expression_bodied_indexers = true:silent 82 | csharp_style_expression_bodied_accessors = true:silent 83 | # Pattern matching preferences 84 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 85 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 86 | # Null-checking preferences 87 | csharp_style_throw_expression = true:suggestion 88 | csharp_style_conditional_delegate_call = true:suggestion 89 | # Modifier preferences 90 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion 91 | # Expression-level preferences 92 | csharp_prefer_braces = true:silent 93 | csharp_style_deconstructed_variable_declaration = true:suggestion 94 | csharp_prefer_simple_default_expression = true:suggestion 95 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 96 | csharp_style_inlined_variable_declaration = true:suggestion 97 | ############################### 98 | # C# Formatting Rules # 99 | ############################### 100 | # New line preferences 101 | csharp_new_line_before_open_brace = all 102 | csharp_new_line_before_else = true 103 | csharp_new_line_before_catch = true 104 | csharp_new_line_before_finally = true 105 | csharp_new_line_before_members_in_object_initializers = true 106 | csharp_new_line_before_members_in_anonymous_types = true 107 | csharp_new_line_between_query_expression_clauses = true 108 | # Indentation preferences 109 | csharp_indent_case_contents = true 110 | csharp_indent_switch_labels = true 111 | csharp_indent_labels = flush_left 112 | # Space preferences 113 | csharp_space_after_cast = false 114 | csharp_space_after_keywords_in_control_flow_statements = true 115 | csharp_space_between_method_call_parameter_list_parentheses = false 116 | csharp_space_between_method_declaration_parameter_list_parentheses = false 117 | csharp_space_between_parentheses = false 118 | csharp_space_before_colon_in_inheritance_clause = true 119 | csharp_space_after_colon_in_inheritance_clause = true 120 | csharp_space_around_binary_operators = before_and_after 121 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 122 | csharp_space_between_method_call_name_and_opening_parenthesis = false 123 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 124 | # Wrapping preferences 125 | csharp_preserve_single_line_statements = true 126 | csharp_preserve_single_line_blocks = true 127 | ############################### 128 | # VB Coding Conventions # 129 | ############################### 130 | [*.vb] 131 | # Modifier preferences 132 | visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion 133 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Autosave files 2 | *~ 3 | 4 | # build 5 | [Oo]bj/ 6 | [Bb]in/ 7 | packages/ 8 | out/ 9 | 10 | # globs 11 | Makefile.in 12 | *.DS_Store 13 | *.sln.cache 14 | *.suo 15 | *.cache 16 | *.pidb 17 | *.userprefs 18 | *.usertasks 19 | config.log 20 | config.make 21 | config.status 22 | aclocal.m4 23 | install-sh 24 | autom4te.cache/ 25 | *.user 26 | *.tar.gz 27 | tarballs/ 28 | test-results/ 29 | Thumbs.db 30 | 31 | # Mac bundle stuff 32 | *.dmg 33 | *.app 34 | 35 | # resharper 36 | *_Resharper.* 37 | *.Resharper 38 | 39 | # dotCover 40 | *.dotCover 41 | 42 | # IDE 43 | .idea 44 | /.vs/Getit/v15/Server/sqlite3/db.lock 45 | /.vs/Getit/v15/Server/sqlite3/storage.ide 46 | /.vs/Getit/v15/Server/sqlite3/storage.ide-shm 47 | /.vs/Getit/v15/Server/sqlite3/storage.ide-wal 48 | /.vs/slnx.sqlite 49 | /.vs/ProjectSettings.json 50 | /.vs 51 | 52 | # test 53 | TestResults/ 54 | coverage/ 55 | *cobertura.xml 56 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "editorconfig.editorconfig", 4 | "ms-dotnettools.csdevkit", 5 | "redhat.vscode-yaml" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "omnisharp.organizeImportsOnFormat": true, 3 | "yaml.format.singleQuote": true, 4 | "sonarlint.connectedMode.project": { 5 | "connectionId": "charlesdevandiere", 6 | "projectKey": "charlesdevandiere_graphql-query-builder-dotnet" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: Mainline 2 | branches: {} 3 | ignore: 4 | sha: [] 5 | -------------------------------------------------------------------------------- /GraphQL.Query.Builder.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.1.32407.343 4 | MinimumVisualStudioVersion = 15.0.26124.0 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{202101BE-DF6E-4893-99DA-F9B8910254F1}" 6 | EndProject 7 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Query.Builder", "src\GraphQL.Query.Builder\GraphQL.Query.Builder.csproj", "{DA0E8765-8C2D-4E72-A0DE-C3F4DA9BBCED}" 8 | EndProject 9 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{6426820E-52EF-437A-B528-18FE939AEB84}" 10 | EndProject 11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Query.Builder.UnitTests", "tests\GraphQL.Query.Builder.UnitTests\GraphQL.Query.Builder.UnitTests.csproj", "{608A2F7E-24F4-474E-8757-2BABEA8AE005}" 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{C48AF4F4-A750-446E-A4C0-91DEDF0E507F}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Models", "sample\Models\Models.csproj", "{816F7AF3-EE23-4DE1-BBEB-50510B3898E1}" 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pokedex", "sample\Pokedex\Pokedex.csproj", "{3240CA38-D18E-43F4-AA06-5C7FC35E4AA0}" 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{619BB24C-CA4A-4117-86AB-DD1FB2E66C77}" 20 | ProjectSection(SolutionItems) = preProject 21 | .editorconfig = .editorconfig 22 | .gitignore = .gitignore 23 | azure-pipelines.yml = azure-pipelines.yml 24 | generate-documentation.bat = generate-documentation.bat 25 | GitVersion.yml = GitVersion.yml 26 | LICENSE = LICENSE 27 | logo.pdn = logo.pdn 28 | logo.png = logo.png 29 | README.md = README.md 30 | generate-documentation.sh = generate-documentation.sh 31 | EndProjectSection 32 | EndProject 33 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{E5BDA139-5C76-4260-8FA8-60D306924304}" 34 | ProjectSection(SolutionItems) = preProject 35 | docs\_config.yml = docs\_config.yml 36 | docs\README.md = docs\README.md 37 | EndProjectSection 38 | EndProject 39 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "api", "api", "{AF5F52DB-31E5-4A57-ACBF-1392F9AF3C55}" 40 | ProjectSection(SolutionItems) = preProject 41 | docs\api\graphql.query.builder.iquery-1.md = docs\api\graphql.query.builder.iquery-1.md 42 | docs\api\graphql.query.builder.iquery.md = docs\api\graphql.query.builder.iquery.md 43 | docs\api\graphql.query.builder.iquerystringbuilder.md = docs\api\graphql.query.builder.iquerystringbuilder.md 44 | docs\api\graphql.query.builder.query-1.md = docs\api\graphql.query.builder.query-1.md 45 | docs\api\graphql.query.builder.queryformatters.md = docs\api\graphql.query.builder.queryformatters.md 46 | docs\api\graphql.query.builder.queryoptions.md = docs\api\graphql.query.builder.queryoptions.md 47 | docs\api\graphql.query.builder.querystringbuilder.md = docs\api\graphql.query.builder.querystringbuilder.md 48 | docs\api\index.md = docs\api\index.md 49 | EndProjectSection 50 | EndProject 51 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".config", ".config", "{38907AFF-FAA0-43D0-835C-275BB3E0261A}" 52 | ProjectSection(SolutionItems) = preProject 53 | .config\dotnet-tools.json = .config\dotnet-tools.json 54 | EndProjectSection 55 | EndProject 56 | Global 57 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 58 | Debug|Any CPU = Debug|Any CPU 59 | Debug|x64 = Debug|x64 60 | Debug|x86 = Debug|x86 61 | Release|Any CPU = Release|Any CPU 62 | Release|x64 = Release|x64 63 | Release|x86 = Release|x86 64 | EndGlobalSection 65 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 66 | {DA0E8765-8C2D-4E72-A0DE-C3F4DA9BBCED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {DA0E8765-8C2D-4E72-A0DE-C3F4DA9BBCED}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {DA0E8765-8C2D-4E72-A0DE-C3F4DA9BBCED}.Debug|x64.ActiveCfg = Debug|Any CPU 69 | {DA0E8765-8C2D-4E72-A0DE-C3F4DA9BBCED}.Debug|x64.Build.0 = Debug|Any CPU 70 | {DA0E8765-8C2D-4E72-A0DE-C3F4DA9BBCED}.Debug|x86.ActiveCfg = Debug|Any CPU 71 | {DA0E8765-8C2D-4E72-A0DE-C3F4DA9BBCED}.Debug|x86.Build.0 = Debug|Any CPU 72 | {DA0E8765-8C2D-4E72-A0DE-C3F4DA9BBCED}.Release|Any CPU.ActiveCfg = Release|Any CPU 73 | {DA0E8765-8C2D-4E72-A0DE-C3F4DA9BBCED}.Release|Any CPU.Build.0 = Release|Any CPU 74 | {DA0E8765-8C2D-4E72-A0DE-C3F4DA9BBCED}.Release|x64.ActiveCfg = Release|Any CPU 75 | {DA0E8765-8C2D-4E72-A0DE-C3F4DA9BBCED}.Release|x64.Build.0 = Release|Any CPU 76 | {DA0E8765-8C2D-4E72-A0DE-C3F4DA9BBCED}.Release|x86.ActiveCfg = Release|Any CPU 77 | {DA0E8765-8C2D-4E72-A0DE-C3F4DA9BBCED}.Release|x86.Build.0 = Release|Any CPU 78 | {608A2F7E-24F4-474E-8757-2BABEA8AE005}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 79 | {608A2F7E-24F4-474E-8757-2BABEA8AE005}.Debug|Any CPU.Build.0 = Debug|Any CPU 80 | {608A2F7E-24F4-474E-8757-2BABEA8AE005}.Debug|x64.ActiveCfg = Debug|Any CPU 81 | {608A2F7E-24F4-474E-8757-2BABEA8AE005}.Debug|x64.Build.0 = Debug|Any CPU 82 | {608A2F7E-24F4-474E-8757-2BABEA8AE005}.Debug|x86.ActiveCfg = Debug|Any CPU 83 | {608A2F7E-24F4-474E-8757-2BABEA8AE005}.Debug|x86.Build.0 = Debug|Any CPU 84 | {608A2F7E-24F4-474E-8757-2BABEA8AE005}.Release|Any CPU.ActiveCfg = Release|Any CPU 85 | {608A2F7E-24F4-474E-8757-2BABEA8AE005}.Release|Any CPU.Build.0 = Release|Any CPU 86 | {608A2F7E-24F4-474E-8757-2BABEA8AE005}.Release|x64.ActiveCfg = Release|Any CPU 87 | {608A2F7E-24F4-474E-8757-2BABEA8AE005}.Release|x64.Build.0 = Release|Any CPU 88 | {608A2F7E-24F4-474E-8757-2BABEA8AE005}.Release|x86.ActiveCfg = Release|Any CPU 89 | {608A2F7E-24F4-474E-8757-2BABEA8AE005}.Release|x86.Build.0 = Release|Any CPU 90 | {816F7AF3-EE23-4DE1-BBEB-50510B3898E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 91 | {816F7AF3-EE23-4DE1-BBEB-50510B3898E1}.Debug|Any CPU.Build.0 = Debug|Any CPU 92 | {816F7AF3-EE23-4DE1-BBEB-50510B3898E1}.Debug|x64.ActiveCfg = Debug|Any CPU 93 | {816F7AF3-EE23-4DE1-BBEB-50510B3898E1}.Debug|x64.Build.0 = Debug|Any CPU 94 | {816F7AF3-EE23-4DE1-BBEB-50510B3898E1}.Debug|x86.ActiveCfg = Debug|Any CPU 95 | {816F7AF3-EE23-4DE1-BBEB-50510B3898E1}.Debug|x86.Build.0 = Debug|Any CPU 96 | {816F7AF3-EE23-4DE1-BBEB-50510B3898E1}.Release|Any CPU.ActiveCfg = Release|Any CPU 97 | {816F7AF3-EE23-4DE1-BBEB-50510B3898E1}.Release|Any CPU.Build.0 = Release|Any CPU 98 | {816F7AF3-EE23-4DE1-BBEB-50510B3898E1}.Release|x64.ActiveCfg = Release|Any CPU 99 | {816F7AF3-EE23-4DE1-BBEB-50510B3898E1}.Release|x64.Build.0 = Release|Any CPU 100 | {816F7AF3-EE23-4DE1-BBEB-50510B3898E1}.Release|x86.ActiveCfg = Release|Any CPU 101 | {816F7AF3-EE23-4DE1-BBEB-50510B3898E1}.Release|x86.Build.0 = Release|Any CPU 102 | {3240CA38-D18E-43F4-AA06-5C7FC35E4AA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 103 | {3240CA38-D18E-43F4-AA06-5C7FC35E4AA0}.Debug|Any CPU.Build.0 = Debug|Any CPU 104 | {3240CA38-D18E-43F4-AA06-5C7FC35E4AA0}.Debug|x64.ActiveCfg = Debug|Any CPU 105 | {3240CA38-D18E-43F4-AA06-5C7FC35E4AA0}.Debug|x64.Build.0 = Debug|Any CPU 106 | {3240CA38-D18E-43F4-AA06-5C7FC35E4AA0}.Debug|x86.ActiveCfg = Debug|Any CPU 107 | {3240CA38-D18E-43F4-AA06-5C7FC35E4AA0}.Debug|x86.Build.0 = Debug|Any CPU 108 | {3240CA38-D18E-43F4-AA06-5C7FC35E4AA0}.Release|Any CPU.ActiveCfg = Release|Any CPU 109 | {3240CA38-D18E-43F4-AA06-5C7FC35E4AA0}.Release|Any CPU.Build.0 = Release|Any CPU 110 | {3240CA38-D18E-43F4-AA06-5C7FC35E4AA0}.Release|x64.ActiveCfg = Release|Any CPU 111 | {3240CA38-D18E-43F4-AA06-5C7FC35E4AA0}.Release|x64.Build.0 = Release|Any CPU 112 | {3240CA38-D18E-43F4-AA06-5C7FC35E4AA0}.Release|x86.ActiveCfg = Release|Any CPU 113 | {3240CA38-D18E-43F4-AA06-5C7FC35E4AA0}.Release|x86.Build.0 = Release|Any CPU 114 | EndGlobalSection 115 | GlobalSection(SolutionProperties) = preSolution 116 | HideSolutionNode = FALSE 117 | EndGlobalSection 118 | GlobalSection(NestedProjects) = preSolution 119 | {DA0E8765-8C2D-4E72-A0DE-C3F4DA9BBCED} = {202101BE-DF6E-4893-99DA-F9B8910254F1} 120 | {608A2F7E-24F4-474E-8757-2BABEA8AE005} = {6426820E-52EF-437A-B528-18FE939AEB84} 121 | {816F7AF3-EE23-4DE1-BBEB-50510B3898E1} = {C48AF4F4-A750-446E-A4C0-91DEDF0E507F} 122 | {3240CA38-D18E-43F4-AA06-5C7FC35E4AA0} = {C48AF4F4-A750-446E-A4C0-91DEDF0E507F} 123 | {E5BDA139-5C76-4260-8FA8-60D306924304} = {619BB24C-CA4A-4117-86AB-DD1FB2E66C77} 124 | {AF5F52DB-31E5-4A57-ACBF-1392F9AF3C55} = {E5BDA139-5C76-4260-8FA8-60D306924304} 125 | {38907AFF-FAA0-43D0-835C-275BB3E0261A} = {619BB24C-CA4A-4117-86AB-DD1FB2E66C77} 126 | EndGlobalSection 127 | GlobalSection(ExtensibilityGlobals) = postSolution 128 | SolutionGuid = {464DBCD0-E769-4ECD-8217-FCC5ED5C4657} 129 | EndGlobalSection 130 | EndGlobal 131 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Charles de Vandière 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GraphQL Query Builder .NET 2 | 3 | ![logo](https://raw.githubusercontent.com/charlesdevandiere/graphql-query-builder-dotnet/master/logo.png) 4 | 5 | A tool to build GraphQL query from a C# model. 6 | 7 | [![Build Status](https://dev.azure.com/charlesdevandiere/charlesdevandiere/_apis/build/status/charlesdevandiere.graphql-query-builder-dotnet?branchName=master)](https://dev.azure.com/charlesdevandiere/charlesdevandiere/_build/latest?definitionId=3&branchName=master) 8 | ![Coverage](https://img.shields.io/azure-devops/coverage/charlesdevandiere/charlesdevandiere/3/master) 9 | [![Nuget](https://img.shields.io/nuget/v/GraphQL.Query.Builder.svg?color=blue&logo=nuget)](https://www.nuget.org/packages/GraphQL.Query.Builder) 10 | [![Downloads](https://img.shields.io/nuget/dt/GraphQL.Query.Builder.svg?logo=nuget)](https://www.nuget.org/packages/GraphQL.Query.Builder) 11 | 12 | See complete documentation [here](https://charlesdevandiere.github.io/graphql-query-builder-dotnet/) 13 | 14 | See sample [here](https://github.com/charlesdevandiere/graphql-query-builder-dotnet/tree/master/sample/Pokedex) 15 | 16 | ## Install 17 | 18 | ```shell 19 | dotnet add package GraphQL.Query.Builder 20 | ``` 21 | 22 | ## Usage 23 | 24 | ```csharp 25 | // Create the query 26 | IQuery query = new Query("humans") // set the name of the query 27 | .AddArguments(new { id = "uE78f5hq" }) // add query arguments 28 | .AddField(h => h.FirstName) // add firstName field 29 | .AddField(h => h.LastName) // add lastName field 30 | .AddField( // add a sub-object field 31 | h => h.HomePlanet, // set the name of the field 32 | sq => sq /// build the sub-query 33 | .AddField(p => p.Name) 34 | ) 35 | .AddField( // add a sub-list field 36 | h => h.Friends, 37 | sq => sq 38 | .AddField(f => f.FirstName) 39 | .AddField(f => f.LastName) 40 | ); 41 | // This corresponds to: 42 | // humans(id: "uE78f5hq") { 43 | // FirstName 44 | // LastName 45 | // HomePlanet { 46 | // Name 47 | // } 48 | // Friends { 49 | // FirstName 50 | // LastName 51 | // } 52 | // } 53 | 54 | Console.WriteLine("{" + query.Build() + "}"); 55 | // Output: 56 | // {humans(id:"uE78f5hq"){FirstName LastName HomePlanet{Name}Friends FirstName LastName}} 57 | ``` 58 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | name: $(GitVersion.FullSemVer) 2 | pool: 3 | vmImage: 'ubuntu-latest' 4 | 5 | variables: 6 | buildConfiguration: Release 7 | 8 | trigger: 9 | batch: 'true' 10 | branches: 11 | include: 12 | - master 13 | paths: 14 | exclude: 15 | - docs/* 16 | - README.md 17 | 18 | steps: 19 | - task: UseGitVersion@5 20 | displayName: GitVersion 21 | inputs: 22 | versionSpec: '5.x' 23 | 24 | - script: dotnet tool restore 25 | displayName: 'Restore tools' 26 | 27 | - script: dotnet build -c $(buildConfiguration) 28 | displayName: 'Build' 29 | 30 | - script: dotnet test --no-build -c $(buildConfiguration) --logger xunit -p:CollectCoverage=true -p:CoverletOutputFormat=cobertura -p:Exclude="[*]Shared.Models.*" 31 | displayName: 'Test' 32 | 33 | - script: dotnet pack ./src/GraphQL.Query.Builder/GraphQL.Query.Builder.csproj --no-build -c $(buildConfiguration) -o $(Build.ArtifactStagingDirectory)/out -p:Version=$(GitVersion.SemVer) 34 | displayName: 'Pack' 35 | 36 | - task: PublishTestResults@2 37 | displayName: 'Publish test results' 38 | inputs: 39 | testRunner: xUnit 40 | testResultsFiles: '**/TestResults.xml' 41 | 42 | - script: dotnet tool run reportgenerator -reports:**/coverage.cobertura.xml -targetdir:tests/coverage -reportTypes:Cobertura 43 | displayName: 'Generate code coverage report' 44 | 45 | - task: PublishCodeCoverageResults@1 46 | displayName: 'Publish code coverage' 47 | inputs: 48 | codeCoverageTool: cobertura 49 | summaryFileLocation: ./tests/coverage/Cobertura.xml 50 | 51 | - publish: $(Build.ArtifactStagingDirectory)/out 52 | artifact: out 53 | displayName: Publish artifact 54 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # GraphQL Query Builder .NET 2 | 3 | ![logo](https://raw.githubusercontent.com/charlesdevandiere/graphql-query-builder-dotnet/master/logo.png) 4 | 5 | A tool to build GraphQL query from a C# model. 6 | 7 | [![Build Status](https://dev.azure.com/charlesdevandiere/charlesdevandiere/_apis/build/status/charlesdevandiere.graphql-query-builder?branchName=master)](https://dev.azure.com/charlesdevandiere/charlesdevandiere/_build/latest?definitionId=3&branchName=master) 8 | ![Coverage](https://img.shields.io/azure-devops/coverage/charlesdevandiere/charlesdevandiere/3/master) 9 | [![Nuget](https://img.shields.io/nuget/v/GraphQL.Query.Builder.svg?color=blue&logo=nuget)](https://www.nuget.org/packages/GraphQL.Query.Builder) 10 | [![Downloads](https://img.shields.io/nuget/dt/GraphQL.Query.Builder.svg?logo=nuget)](https://www.nuget.org/packages/GraphQL.Query.Builder) 11 | 12 | ## Install 13 | 14 | Run this command with dotnet CLI: 15 | 16 | ```console 17 | dotnet add package GraphQL.Query.Builder 18 | ``` 19 | 20 | ## Usage 21 | 22 | ### Create a query 23 | 24 | The query building is based on the object which returns. 25 | 26 | #### Entities definition 27 | 28 | In a first time, you need to create POCOs. 29 | 30 | ```csharp 31 | class Human 32 | { 33 | public string FirstName { get; set; } 34 | public string LastName { get; set; } 35 | public Planet HomePlanet { get; set; } 36 | public IEnumerable Friends { get; set; } 37 | } 38 | 39 | class Planet 40 | { 41 | public string Name { get; set; } 42 | } 43 | ``` 44 | 45 | #### Creation of the query 46 | 47 | After that, you can write a query like this : 48 | 49 | ```csharp 50 | IQuery query = new Query("humans") // set the name of the query 51 | .AddArguments(new { id = "uE78f5hq" }) // add query arguments 52 | .AddField(h => h.FirstName) // add firstName field 53 | .AddField(h => h.LastName) // add lastName field 54 | .AddField( // add a sub-object field 55 | h => h.HomePlanet, // set the name of the field 56 | sq => sq /// build the sub-query 57 | .AddField(p => p.Name) 58 | ) 59 | .AddField( // add a sub-list field 60 | h => h.Friends, 61 | sq => sq 62 | .AddField(f => f.FirstName) 63 | .AddField(f => f.LastName) 64 | ); 65 | ``` 66 | 67 | This corresponds to : 68 | 69 | ```GraphQL 70 | humans (id: "uE78f5hq") { 71 | FirstName 72 | LastName 73 | HomePlanet { 74 | Name 75 | } 76 | Friends { 77 | FirstName 78 | LastName 79 | } 80 | } 81 | ``` 82 | 83 | By default, the `AddField()` method use the property name as field name. 84 | You can change this behavior by providing a custom formatter. 85 | 86 | ```csharp 87 | QueryOptions options = new() 88 | { 89 | Formater = // Your custom formatter 90 | }; 91 | 92 | IQuery query = new Query("human", options); 93 | ``` 94 | 95 | Formater's type is ```Func``` 96 | 97 | There are a few existing formatters : 98 | 99 | - [CamelCasePropertyNameFormatter](api/graphql.query.builder.camelcasepropertynameformatter.md) : transforms property name into camel-case. 100 | - [GraphQL.Query.Builder.Formatter.SystemTextJson](https://github.com/charlesdevandiere/graphql-query-builder-formatter-systemtextjson) : use `JsonPropertyNameAttribute` value. 101 | - [GraphQL.Query.Builder.Formatter.NewtonsoftJson](https://github.com/charlesdevandiere/graphql-query-builder-formatter-newtonsoftjson) : use `JsonPropertyAttribute` value. 102 | 103 | ### Build the query 104 | 105 | Build the query using `Build` method: 106 | 107 | ```csharp 108 | string queryString = query.Build(); 109 | ``` 110 | 111 | ## API documentation 112 | 113 | See API documentation [here](api) 114 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | -------------------------------------------------------------------------------- /docs/api/graphql.query.builder.camelcasepropertynameformatter.md: -------------------------------------------------------------------------------- 1 | [`< Back`](./) 2 | 3 | --- 4 | 5 | # CamelCasePropertyNameFormatter 6 | 7 | Namespace: GraphQL.Query.Builder 8 | 9 | The camel case property name formatter class. 10 | 11 | ```csharp 12 | public static class CamelCasePropertyNameFormatter 13 | ``` 14 | 15 | Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → [CamelCasePropertyNameFormatter](./graphql.query.builder.camelcasepropertynameformatter) 16 | 17 | ## Fields 18 | 19 | ### **Format** 20 | 21 | Formats the property name in camel case. 22 | 23 | ```csharp 24 | public static Func Format; 25 | ``` 26 | 27 | --- 28 | 29 | [`< Back`](./) 30 | -------------------------------------------------------------------------------- /docs/api/graphql.query.builder.iquery-1.md: -------------------------------------------------------------------------------- 1 | [`< Back`](./) 2 | 3 | --- 4 | 5 | # IQuery<TSource> 6 | 7 | Namespace: GraphQL.Query.Builder 8 | 9 | Query of TSource interface. 10 | 11 | ```csharp 12 | public interface IQuery : IQuery 13 | ``` 14 | 15 | #### Type Parameters 16 | 17 | `TSource`
18 | 19 | Implements [IQuery](./graphql.query.builder.iquery)
20 | Attributes [NullableContextAttribute](./system.runtime.compilerservices.nullablecontextattribute) 21 | 22 | ## Properties 23 | 24 | ### **SelectList** 25 | 26 | Gets the select list. 27 | 28 | ```csharp 29 | public abstract List SelectList { get; } 30 | ``` 31 | 32 | #### Property Value 33 | 34 | [List<Object>](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1)
35 | 36 | ### **Arguments** 37 | 38 | Gets the arguments. 39 | 40 | ```csharp 41 | public abstract Dictionary Arguments { get; } 42 | ``` 43 | 44 | #### Property Value 45 | 46 | [Dictionary<String, Object>](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2)
47 | 48 | ## Methods 49 | 50 | ### **Alias(String)** 51 | 52 | Sets the query alias name. 53 | 54 | ```csharp 55 | IQuery Alias(string alias) 56 | ``` 57 | 58 | #### Parameters 59 | 60 | `alias` [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
61 | The alias name. 62 | 63 | #### Returns 64 | 65 | [IQuery<TSource>](./graphql.query.builder.iquery-1)
66 | The query. 67 | 68 | ### **AddField<TProperty>(Expression<Func<TSource, TProperty>>)** 69 | 70 | Adds a field to the query. 71 | 72 | ```csharp 73 | IQuery AddField(Expression> selector) 74 | ``` 75 | 76 | #### Type Parameters 77 | 78 | `TProperty`
79 | The property type. 80 | 81 | #### Parameters 82 | 83 | `selector` Expression<Func<TSource, TProperty>>
84 | The field selector. 85 | 86 | #### Returns 87 | 88 | [IQuery<TSource>](./graphql.query.builder.iquery-1)
89 | The query. 90 | 91 | ### **AddField(String)** 92 | 93 | Adds a field to the query. 94 | 95 | ```csharp 96 | IQuery AddField(string field) 97 | ``` 98 | 99 | #### Parameters 100 | 101 | `field` [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
102 | The field name. 103 | 104 | #### Returns 105 | 106 | [IQuery<TSource>](./graphql.query.builder.iquery-1)
107 | The query. 108 | 109 | ### **AddField<TSubSource>(Expression<Func<TSource, TSubSource>>, Func<IQuery<TSubSource>, IQuery<TSubSource>>)** 110 | 111 | Adds a sub-object field to the query. 112 | 113 | ```csharp 114 | IQuery AddField(Expression> selector, Func, IQuery> build) 115 | ``` 116 | 117 | #### Type Parameters 118 | 119 | `TSubSource`
120 | The sub-object type. 121 | 122 | #### Parameters 123 | 124 | `selector` Expression<Func<TSource, TSubSource>>
125 | The field selector. 126 | 127 | `build` Func<IQuery<TSubSource>, IQuery<TSubSource>>
128 | The sub-object query building function. 129 | 130 | #### Returns 131 | 132 | [IQuery<TSource>](./graphql.query.builder.iquery-1)
133 | The query. 134 | 135 | ### **AddField<TSubSource>(Expression<Func<TSource, IEnumerable<TSubSource>>>, Func<IQuery<TSubSource>, IQuery<TSubSource>>)** 136 | 137 | Adds a sub-list field to the query. 138 | 139 | ```csharp 140 | IQuery AddField(Expression>> selector, Func, IQuery> build) 141 | ``` 142 | 143 | #### Type Parameters 144 | 145 | `TSubSource`
146 | The sub-list object type. 147 | 148 | #### Parameters 149 | 150 | `selector` Expression<Func<TSource, IEnumerable<TSubSource>>>
151 | The field selector. 152 | 153 | `build` Func<IQuery<TSubSource>, IQuery<TSubSource>>
154 | The sub-object query building function. 155 | 156 | #### Returns 157 | 158 | [IQuery<TSource>](./graphql.query.builder.iquery-1)
159 | The query. 160 | 161 | ### **AddField<TSubSource>(String, Func<IQuery<TSubSource>, IQuery<TSubSource>>)** 162 | 163 | Adds a sub-object field to the query. 164 | 165 | ```csharp 166 | IQuery AddField(string field, Func, IQuery> build) 167 | ``` 168 | 169 | #### Type Parameters 170 | 171 | `TSubSource`
172 | The sub-object type. 173 | 174 | #### Parameters 175 | 176 | `field` [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
177 | The field name. 178 | 179 | `build` Func<IQuery<TSubSource>, IQuery<TSubSource>>
180 | The sub-object query building function. 181 | 182 | #### Returns 183 | 184 | [IQuery<TSource>](./graphql.query.builder.iquery-1)
185 | The query. 186 | 187 | ### **AddUnion<TUnionType>(String, Func<IQuery<TUnionType>, IQuery<TUnionType>>)** 188 | 189 | Adds an union to the query. 190 | 191 | ```csharp 192 | IQuery AddUnion(string typeName, Func, IQuery> build) 193 | ``` 194 | 195 | #### Type Parameters 196 | 197 | `TUnionType`
198 | The union type. 199 | 200 | #### Parameters 201 | 202 | `typeName` [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
203 | The union type name. 204 | 205 | `build` Func<IQuery<TUnionType>, IQuery<TUnionType>>
206 | The union building function. 207 | 208 | #### Returns 209 | 210 | [IQuery<TSource>](./graphql.query.builder.iquery-1)
211 | The query. 212 | 213 | ### **AddUnion<TUnionType>(Func<IQuery<TUnionType>, IQuery<TUnionType>>)** 214 | 215 | Adds an union to the query. 216 | 217 | ```csharp 218 | IQuery AddUnion(Func, IQuery> build) 219 | ``` 220 | 221 | #### Type Parameters 222 | 223 | `TUnionType`
224 | The union type. 225 | 226 | #### Parameters 227 | 228 | `build` Func<IQuery<TUnionType>, IQuery<TUnionType>>
229 | The union building function. 230 | 231 | #### Returns 232 | 233 | [IQuery<TSource>](./graphql.query.builder.iquery-1)
234 | The query. 235 | 236 | ### **AddArgument(String, Object)** 237 | 238 | Adds a new argument to the query. 239 | 240 | ```csharp 241 | IQuery AddArgument(string key, object value) 242 | ``` 243 | 244 | #### Parameters 245 | 246 | `key` [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
247 | The argument name. 248 | 249 | `value` [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object)
250 | The value. 251 | 252 | #### Returns 253 | 254 | [IQuery<TSource>](./graphql.query.builder.iquery-1)
255 | The query. 256 | 257 | ### **AddArguments(Dictionary<String, Object>)** 258 | 259 | Adds arguments to the query. 260 | 261 | ```csharp 262 | IQuery AddArguments(Dictionary arguments) 263 | ``` 264 | 265 | #### Parameters 266 | 267 | `arguments` [Dictionary<String, Object>](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2)
268 | the dictionary argument. 269 | 270 | #### Returns 271 | 272 | [IQuery<TSource>](./graphql.query.builder.iquery-1)
273 | The query. 274 | 275 | ### **AddArguments<TArguments>(TArguments)** 276 | 277 | Adds arguments to the query. 278 | 279 | ```csharp 280 | IQuery AddArguments(TArguments arguments) 281 | ``` 282 | 283 | #### Type Parameters 284 | 285 | `TArguments`
286 | The arguments object type. 287 | 288 | #### Parameters 289 | 290 | `arguments` TArguments
291 | The arguments object. 292 | 293 | #### Returns 294 | 295 | [IQuery<TSource>](./graphql.query.builder.iquery-1)
296 | The query. 297 | 298 | --- 299 | 300 | [`< Back`](./) 301 | -------------------------------------------------------------------------------- /docs/api/graphql.query.builder.iquery.md: -------------------------------------------------------------------------------- 1 | [`< Back`](./) 2 | 3 | --- 4 | 5 | # IQuery 6 | 7 | Namespace: GraphQL.Query.Builder 8 | 9 | The query interface. 10 | 11 | ```csharp 12 | public interface IQuery 13 | ``` 14 | 15 | Attributes [NullableContextAttribute](./system.runtime.compilerservices.nullablecontextattribute) 16 | 17 | ## Properties 18 | 19 | ### **Name** 20 | 21 | Gets the query name. 22 | 23 | ```csharp 24 | public abstract string Name { get; } 25 | ``` 26 | 27 | #### Property Value 28 | 29 | [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
30 | 31 | ### **AliasName** 32 | 33 | Gets the alias name. 34 | 35 | ```csharp 36 | public abstract string AliasName { get; } 37 | ``` 38 | 39 | #### Property Value 40 | 41 | [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
42 | 43 | ## Methods 44 | 45 | ### **Build()** 46 | 47 | Builds the query. 48 | 49 | ```csharp 50 | string Build() 51 | ``` 52 | 53 | #### Returns 54 | 55 | [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
56 | The GraphQL query as string, without outer enclosing block. 57 | 58 | #### Exceptions 59 | 60 | [ArgumentException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentexception)
61 | Must have a 'Name' specified in the Query 62 | 63 | [ArgumentException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentexception)
64 | Must have a one or more 'Select' fields in the Query 65 | 66 | --- 67 | 68 | [`< Back`](./) 69 | -------------------------------------------------------------------------------- /docs/api/graphql.query.builder.iquerystringbuilder.md: -------------------------------------------------------------------------------- 1 | [`< Back`](./) 2 | 3 | --- 4 | 5 | # IQueryStringBuilder 6 | 7 | Namespace: GraphQL.Query.Builder 8 | 9 | The GraphQL query builder interface. 10 | 11 | ```csharp 12 | public interface IQueryStringBuilder 13 | ``` 14 | 15 | Attributes [NullableContextAttribute](./system.runtime.compilerservices.nullablecontextattribute) 16 | 17 | ## Methods 18 | 19 | ### **Clear()** 20 | 21 | Clears the string builder. 22 | 23 | ```csharp 24 | void Clear() 25 | ``` 26 | 27 | ### **Build<TSource>(IQuery<TSource>)** 28 | 29 | Builds the query. 30 | 31 | ```csharp 32 | string Build(IQuery query) 33 | ``` 34 | 35 | #### Type Parameters 36 | 37 | `TSource`
38 | 39 | #### Parameters 40 | 41 | `query` IQuery<TSource>
42 | The query. 43 | 44 | #### Returns 45 | 46 | [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
47 | The GraphQL query as string, without outer enclosing block. 48 | 49 | --- 50 | 51 | [`< Back`](./) 52 | -------------------------------------------------------------------------------- /docs/api/graphql.query.builder.query-1.md: -------------------------------------------------------------------------------- 1 | [`< Back`](./) 2 | 3 | --- 4 | 5 | # Query<TSource> 6 | 7 | Namespace: GraphQL.Query.Builder 8 | 9 | The query class. 10 | 11 | ```csharp 12 | public class Query : IQuery`1, IQuery 13 | ``` 14 | 15 | #### Type Parameters 16 | 17 | `TSource`
18 | 19 | Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → [Query<TSource>](./graphql.query.builder.query-1)
20 | Implements IQuery<TSource>, [IQuery](./graphql.query.builder.iquery)
21 | Attributes [NullableContextAttribute](./system.runtime.compilerservices.nullablecontextattribute), [NullableAttribute](./system.runtime.compilerservices.nullableattribute) 22 | 23 | ## Fields 24 | 25 | ### **options** 26 | 27 | The query options. 28 | 29 | ```csharp 30 | protected QueryOptions options; 31 | ``` 32 | 33 | ## Properties 34 | 35 | ### **SelectList** 36 | 37 | Gets the select list. 38 | 39 | ```csharp 40 | public List SelectList { get; } 41 | ``` 42 | 43 | #### Property Value 44 | 45 | [List<Object>](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1)
46 | 47 | ### **Arguments** 48 | 49 | Gets the arguments. 50 | 51 | ```csharp 52 | public Dictionary Arguments { get; } 53 | ``` 54 | 55 | #### Property Value 56 | 57 | [Dictionary<String, Object>](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2)
58 | 59 | ### **Name** 60 | 61 | Gets the query name. 62 | 63 | ```csharp 64 | public string Name { get; private set; } 65 | ``` 66 | 67 | #### Property Value 68 | 69 | [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
70 | 71 | ### **AliasName** 72 | 73 | Gets the alias name. 74 | 75 | ```csharp 76 | public string AliasName { get; private set; } 77 | ``` 78 | 79 | #### Property Value 80 | 81 | [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
82 | 83 | ### **QueryStringBuilder** 84 | 85 | Gets the query string builder. 86 | 87 | ```csharp 88 | protected IQueryStringBuilder QueryStringBuilder { get; } 89 | ``` 90 | 91 | #### Property Value 92 | 93 | [IQueryStringBuilder](./graphql.query.builder.iquerystringbuilder)
94 | 95 | ## Constructors 96 | 97 | ### **Query(String)** 98 | 99 | Initializes a new instance of the [Query<TSource>](./graphql.query.builder.query-1) class. 100 | 101 | ```csharp 102 | public Query(string name) 103 | ``` 104 | 105 | #### Parameters 106 | 107 | `name` [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
108 | 109 | ### **Query(String, QueryOptions)** 110 | 111 | Initializes a new instance of the [Query<TSource>](./graphql.query.builder.query-1) class. 112 | 113 | ```csharp 114 | public Query(string name, QueryOptions options) 115 | ``` 116 | 117 | #### Parameters 118 | 119 | `name` [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
120 | 121 | `options` [QueryOptions](./graphql.query.builder.queryoptions)
122 | 123 | ## Methods 124 | 125 | ### **Alias(String)** 126 | 127 | Sets the query alias name. 128 | 129 | ```csharp 130 | public IQuery Alias(string alias) 131 | ``` 132 | 133 | #### Parameters 134 | 135 | `alias` [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
136 | The alias name. 137 | 138 | #### Returns 139 | 140 | IQuery<TSource>
141 | The query. 142 | 143 | ### **AddField<TProperty>(Expression<Func<TSource, TProperty>>)** 144 | 145 | Adds a field to the query. 146 | 147 | ```csharp 148 | public IQuery AddField(Expression> selector) 149 | ``` 150 | 151 | #### Type Parameters 152 | 153 | `TProperty`
154 | The property type. 155 | 156 | #### Parameters 157 | 158 | `selector` Expression<Func<TSource, TProperty>>
159 | The field selector. 160 | 161 | #### Returns 162 | 163 | IQuery<TSource>
164 | The query. 165 | 166 | ### **AddField(String)** 167 | 168 | Adds a field to the query. 169 | 170 | ```csharp 171 | public IQuery AddField(string field) 172 | ``` 173 | 174 | #### Parameters 175 | 176 | `field` [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
177 | The field name. 178 | 179 | #### Returns 180 | 181 | IQuery<TSource>
182 | The query. 183 | 184 | ### **AddField<TSubSource>(Expression<Func<TSource, TSubSource>>, Func<IQuery<TSubSource>, IQuery<TSubSource>>)** 185 | 186 | Adds a sub-object field to the query. 187 | 188 | ```csharp 189 | public IQuery AddField(Expression> selector, Func, IQuery> build) 190 | ``` 191 | 192 | #### Type Parameters 193 | 194 | `TSubSource`
195 | The sub-object type. 196 | 197 | #### Parameters 198 | 199 | `selector` Expression<Func<TSource, TSubSource>>
200 | The field selector. 201 | 202 | `build` Func<IQuery<TSubSource>, IQuery<TSubSource>>
203 | The sub-object query building function. 204 | 205 | #### Returns 206 | 207 | IQuery<TSource>
208 | The query. 209 | 210 | ### **AddField<TSubSource>(Expression<Func<TSource, IEnumerable<TSubSource>>>, Func<IQuery<TSubSource>, IQuery<TSubSource>>)** 211 | 212 | Adds a sub-list field to the query. 213 | 214 | ```csharp 215 | public IQuery AddField(Expression>> selector, Func, IQuery> build) 216 | ``` 217 | 218 | #### Type Parameters 219 | 220 | `TSubSource`
221 | The sub-list object type. 222 | 223 | #### Parameters 224 | 225 | `selector` Expression<Func<TSource, IEnumerable<TSubSource>>>
226 | The field selector. 227 | 228 | `build` Func<IQuery<TSubSource>, IQuery<TSubSource>>
229 | The sub-object query building function. 230 | 231 | #### Returns 232 | 233 | IQuery<TSource>
234 | The query. 235 | 236 | ### **AddField<TSubSource>(String, Func<IQuery<TSubSource>, IQuery<TSubSource>>)** 237 | 238 | Adds a sub-object field to the query. 239 | 240 | ```csharp 241 | public IQuery AddField(string field, Func, IQuery> build) 242 | ``` 243 | 244 | #### Type Parameters 245 | 246 | `TSubSource`
247 | The sub-object type. 248 | 249 | #### Parameters 250 | 251 | `field` [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
252 | The field name. 253 | 254 | `build` Func<IQuery<TSubSource>, IQuery<TSubSource>>
255 | The sub-object query building function. 256 | 257 | #### Returns 258 | 259 | IQuery<TSource>
260 | The query. 261 | 262 | ### **AddUnion<TUnionType>(String, Func<IQuery<TUnionType>, IQuery<TUnionType>>)** 263 | 264 | Adds an union to the query. 265 | 266 | ```csharp 267 | public IQuery AddUnion(string typeName, Func, IQuery> build) 268 | ``` 269 | 270 | #### Type Parameters 271 | 272 | `TUnionType`
273 | The union type. 274 | 275 | #### Parameters 276 | 277 | `typeName` [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
278 | The union type name. 279 | 280 | `build` Func<IQuery<TUnionType>, IQuery<TUnionType>>
281 | The union building function. 282 | 283 | #### Returns 284 | 285 | IQuery<TSource>
286 | The query. 287 | 288 | ### **AddUnion<TUnionType>(Func<IQuery<TUnionType>, IQuery<TUnionType>>)** 289 | 290 | Adds an union to the query. 291 | 292 | ```csharp 293 | public IQuery AddUnion(Func, IQuery> build) 294 | ``` 295 | 296 | #### Type Parameters 297 | 298 | `TUnionType`
299 | The union type. 300 | 301 | #### Parameters 302 | 303 | `build` Func<IQuery<TUnionType>, IQuery<TUnionType>>
304 | The union building function. 305 | 306 | #### Returns 307 | 308 | IQuery<TSource>
309 | The query. 310 | 311 | ### **AddArgument(String, Object)** 312 | 313 | Adds a new argument to the query. 314 | 315 | ```csharp 316 | public IQuery AddArgument(string key, object value) 317 | ``` 318 | 319 | #### Parameters 320 | 321 | `key` [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
322 | The argument name. 323 | 324 | `value` [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object)
325 | The value. 326 | 327 | #### Returns 328 | 329 | IQuery<TSource>
330 | The query. 331 | 332 | ### **AddArguments(Dictionary<String, Object>)** 333 | 334 | Adds arguments to the query. 335 | 336 | ```csharp 337 | public IQuery AddArguments(Dictionary arguments) 338 | ``` 339 | 340 | #### Parameters 341 | 342 | `arguments` [Dictionary<String, Object>](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2)
343 | the dictionary argument. 344 | 345 | #### Returns 346 | 347 | IQuery<TSource>
348 | The query. 349 | 350 | ### **AddArguments<TArguments>(TArguments)** 351 | 352 | Adds arguments to the query. 353 | 354 | ```csharp 355 | public IQuery AddArguments(TArguments arguments) 356 | ``` 357 | 358 | #### Type Parameters 359 | 360 | `TArguments`
361 | The arguments object type. 362 | 363 | #### Parameters 364 | 365 | `arguments` TArguments
366 | The arguments object. 367 | 368 | #### Returns 369 | 370 | IQuery<TSource>
371 | The query. 372 | 373 | ### **Build()** 374 | 375 | Builds the query. 376 | 377 | ```csharp 378 | public string Build() 379 | ``` 380 | 381 | #### Returns 382 | 383 | [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
384 | The GraphQL query as string, without outer enclosing block. 385 | 386 | #### Exceptions 387 | 388 | [ArgumentException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentexception)
389 | Must have a 'Name' specified in the Query 390 | 391 | [ArgumentException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentexception)
392 | Must have a one or more 'Select' fields in the Query 393 | 394 | --- 395 | 396 | [`< Back`](./) 397 | -------------------------------------------------------------------------------- /docs/api/graphql.query.builder.queryoptions.md: -------------------------------------------------------------------------------- 1 | [`< Back`](./) 2 | 3 | --- 4 | 5 | # QueryOptions 6 | 7 | Namespace: GraphQL.Query.Builder 8 | 9 | The query options class. 10 | 11 | ```csharp 12 | public class QueryOptions 13 | ``` 14 | 15 | Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → [QueryOptions](./graphql.query.builder.queryoptions) 16 | 17 | ## Properties 18 | 19 | ### **Formatter** 20 | 21 | Gets or sets the property name formatter. 22 | 23 | ```csharp 24 | public Func Formatter { get; set; } 25 | ``` 26 | 27 | #### Property Value 28 | 29 | [Func<PropertyInfo, String>](https://docs.microsoft.com/en-us/dotnet/api/system.func-2)
30 | 31 | ### **QueryStringBuilderFactory** 32 | 33 | Gets or sets the query string builder factory. 34 | 35 | ```csharp 36 | public Func QueryStringBuilderFactory { get; set; } 37 | ``` 38 | 39 | #### Property Value 40 | 41 | [Func<IQueryStringBuilder>](https://docs.microsoft.com/en-us/dotnet/api/system.func-1)
42 | 43 | ## Constructors 44 | 45 | ### **QueryOptions()** 46 | 47 | ```csharp 48 | public QueryOptions() 49 | ``` 50 | 51 | --- 52 | 53 | [`< Back`](./) 54 | -------------------------------------------------------------------------------- /docs/api/graphql.query.builder.querystringbuilder.md: -------------------------------------------------------------------------------- 1 | [`< Back`](./) 2 | 3 | --- 4 | 5 | # QueryStringBuilder 6 | 7 | Namespace: GraphQL.Query.Builder 8 | 9 | The GraphQL query builder class. 10 | 11 | ```csharp 12 | public class QueryStringBuilder : IQueryStringBuilder 13 | ``` 14 | 15 | Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → [QueryStringBuilder](./graphql.query.builder.querystringbuilder)
16 | Implements [IQueryStringBuilder](./graphql.query.builder.iquerystringbuilder)
17 | Attributes [NullableContextAttribute](./system.runtime.compilerservices.nullablecontextattribute), [NullableAttribute](./system.runtime.compilerservices.nullableattribute) 18 | 19 | ## Fields 20 | 21 | ### **formatter** 22 | 23 | The property name formatter. 24 | 25 | ```csharp 26 | protected Func formatter; 27 | ``` 28 | 29 | ## Properties 30 | 31 | ### **QueryString** 32 | 33 | The query string builder. 34 | 35 | ```csharp 36 | public StringBuilder QueryString { get; } 37 | ``` 38 | 39 | #### Property Value 40 | 41 | [StringBuilder](https://docs.microsoft.com/en-us/dotnet/api/system.text.stringbuilder)
42 | 43 | ## Constructors 44 | 45 | ### **QueryStringBuilder()** 46 | 47 | Initializes a new instance of the [QueryStringBuilder](./graphql.query.builder.querystringbuilder) class. 48 | 49 | ```csharp 50 | public QueryStringBuilder() 51 | ``` 52 | 53 | ### **QueryStringBuilder(Func<PropertyInfo, String>)** 54 | 55 | Initializes a new instance of the [QueryStringBuilder](./graphql.query.builder.querystringbuilder) class. 56 | 57 | ```csharp 58 | public QueryStringBuilder(Func formatter) 59 | ``` 60 | 61 | #### Parameters 62 | 63 | `formatter` [Func<PropertyInfo, String>](https://docs.microsoft.com/en-us/dotnet/api/system.func-2)
64 | The property name formatter 65 | 66 | ## Methods 67 | 68 | ### **Build<TSource>(IQuery<TSource>)** 69 | 70 | Builds the query. 71 | 72 | ```csharp 73 | public string Build(IQuery query) 74 | ``` 75 | 76 | #### Type Parameters 77 | 78 | `TSource`
79 | 80 | #### Parameters 81 | 82 | `query` IQuery<TSource>
83 | The query. 84 | 85 | #### Returns 86 | 87 | [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
88 | The GraphQL query as string, without outer enclosing block. 89 | 90 | ### **Clear()** 91 | 92 | Clears the string builder. 93 | 94 | ```csharp 95 | public void Clear() 96 | ``` 97 | 98 | ### **FormatQueryParam(Object)** 99 | 100 | Formats query param. 101 | 102 | Returns: 103 | 104 | - **null** - `null` 105 | - **String** - `"foo"` 106 | - **Number** - `10` 107 | - **Boolean** - `true` or `false` 108 | - **Enum** - `EnumValue` 109 | - **DateTime** - `"2024-06-15T13:45:30.0000000Z"` 110 | - **Key value pair** - `foo:"bar"` or `foo:10` ... 111 | - **List** - `["foo","bar"]` or `[1,2]` ... 112 | - **Dictionary** - `{foo:"bar",b:10}` 113 | - **Object** - `{foo:"bar",b:10}` 114 | 115 | ```csharp 116 | protected internal string FormatQueryParam(object value) 117 | ``` 118 | 119 | #### Parameters 120 | 121 | `value` [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object)
122 | 123 | #### Returns 124 | 125 | [String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
126 | The formatted query param. 127 | 128 | #### Exceptions 129 | 130 | [InvalidDataException](https://docs.microsoft.com/en-us/dotnet/api/system.io.invaliddataexception)
131 | Invalid Object Type in Param List 132 | 133 | ### **AddParams<TSource>(IQuery<TSource>)** 134 | 135 | Adds query params to the query string. 136 | 137 | ```csharp 138 | protected internal void AddParams(IQuery query) 139 | ``` 140 | 141 | #### Type Parameters 142 | 143 | `TSource`
144 | 145 | #### Parameters 146 | 147 | `query` IQuery<TSource>
148 | The query. 149 | 150 | ### **AddFields<TSource>(IQuery<TSource>)** 151 | 152 | Adds fields to the query sting. 153 | 154 | ```csharp 155 | protected internal void AddFields(IQuery query) 156 | ``` 157 | 158 | #### Type Parameters 159 | 160 | `TSource`
161 | 162 | #### Parameters 163 | 164 | `query` IQuery<TSource>
165 | The query. 166 | 167 | #### Exceptions 168 | 169 | [ArgumentException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentexception)
170 | Invalid Object in Field List 171 | 172 | --- 173 | 174 | [`< Back`](./) 175 | -------------------------------------------------------------------------------- /docs/api/index.md: -------------------------------------------------------------------------------- 1 | # GraphQL.Query.Builder 2 | 3 | ## GraphQL.Query.Builder 4 | 5 | [CamelCasePropertyNameFormatter](./graphql.query.builder.camelcasepropertynameformatter) 6 | 7 | [IQuery](./graphql.query.builder.iquery) 8 | 9 | [IQuery<TSource>](./graphql.query.builder.iquery-1) 10 | 11 | [IQueryStringBuilder](./graphql.query.builder.iquerystringbuilder) 12 | 13 | [Query<TSource>](./graphql.query.builder.query-1) 14 | 15 | [QueryOptions](./graphql.query.builder.queryoptions) 16 | 17 | [QueryStringBuilder](./graphql.query.builder.querystringbuilder) 18 | -------------------------------------------------------------------------------- /generate-documentation.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | dotnet tool restore 3 | dotnet build ./src/GraphQL.Query.Builder/GraphQL.Query.Builder.csproj -c Release -o out/ 4 | dotnet xmldoc2md out/GraphQL.Query.Builder.dll --output ./docs/api --github-pages --back-button 5 | -------------------------------------------------------------------------------- /generate-documentation.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf ./out/ 4 | find ./docs/api -name "*.md" -type f -delete 5 | 6 | dotnet tool restore 7 | dotnet build ./src/GraphQL.Query.Builder/GraphQL.Query.Builder.csproj -c Release -o out/ 8 | dotnet xmldoc2md out/GraphQL.Query.Builder.dll --output ./docs/api --github-pages --back-button 9 | -------------------------------------------------------------------------------- /logo.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charlesdevandiere/graphql-query-builder-dotnet/4709ec6f7d5a8baefeb34c775aa0dd5a76bc435e/logo.pdn -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/charlesdevandiere/graphql-query-builder-dotnet/4709ec6f7d5a8baefeb34c775aa0dd5a76bc435e/logo.png -------------------------------------------------------------------------------- /sample/Models/Attack.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Shared.Models; 4 | 5 | public class Attack 6 | { 7 | [JsonPropertyName("name")] 8 | public string Name { get; set; } = string.Empty; 9 | 10 | [JsonPropertyName("type")] 11 | public string Type { get; set; } = string.Empty; 12 | 13 | [JsonPropertyName("damage")] 14 | public int Damage { get; set; } 15 | 16 | public override string ToString() 17 | { 18 | return $"{this.Name} (type: {this.Type}, damage: {this.Damage})"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sample/Models/Models.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | latest 6 | enable 7 | enable 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /sample/Models/Pokemon.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Shared.Models; 5 | 6 | public class Pokemon 7 | { 8 | [JsonPropertyName("attacks")] 9 | public PokemonAttack? Attacks { get; set; } 10 | 11 | [JsonPropertyName("height")] 12 | public PokemonDimension? Height { get; set; } 13 | 14 | [JsonPropertyName("id")] 15 | public string Id { get; set; } = string.Empty; 16 | 17 | [JsonPropertyName("name")] 18 | public string Name { get; set; } = string.Empty; 19 | 20 | [JsonPropertyName("number")] 21 | public string Number { get; set; } = string.Empty; 22 | 23 | [JsonPropertyName("types")] 24 | public string[]? Types { get; set; } 25 | 26 | [JsonPropertyName("weight")] 27 | public PokemonDimension? Weight { get; set; } 28 | 29 | public override string ToString() 30 | { 31 | StringBuilder sb = new(); 32 | sb.AppendLine($"{this.Number} {this.Name}"); 33 | if (this.Height != null) 34 | { 35 | sb.AppendLine($"Height: {this.Height}"); 36 | } 37 | if (this.Weight != null) 38 | { 39 | sb.AppendLine($"Weight: {this.Weight}"); 40 | } 41 | if (this.Types != null && this.Types.Length > 0) 42 | { 43 | sb.AppendLine($"Types: {string.Join(", ", this.Types)}"); 44 | } 45 | if (this.Attacks != null) 46 | { 47 | sb.Append(this.Attacks.ToString()); 48 | } 49 | 50 | return sb.ToString(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /sample/Models/PokemonAttack.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Shared.Models; 5 | 6 | public class PokemonAttack 7 | { 8 | [JsonPropertyName("fast")] 9 | public Attack[] Fast { get; set; } = []; 10 | 11 | [JsonPropertyName("special")] 12 | public Attack[] Special { get; set; } = []; 13 | 14 | public override string ToString() 15 | { 16 | StringBuilder sb = new(); 17 | if (this.Fast != null && this.Fast.Length > 0) 18 | { 19 | sb.AppendLine("Fast attacks:"); 20 | foreach (Attack attack in this.Fast) 21 | { 22 | sb.AppendLine($"- {attack}"); 23 | } 24 | } 25 | if (this.Special != null && this.Special.Length > 0) 26 | { 27 | sb.AppendLine("Special attacks:"); 28 | foreach (Attack attack in this.Special) 29 | { 30 | sb.AppendLine($"- {attack}"); 31 | } 32 | } 33 | 34 | return sb.ToString(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sample/Models/PokemonDimension.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Shared.Models; 4 | 5 | public class PokemonDimension 6 | { 7 | [JsonPropertyName("minimum")] 8 | public string Minimum { get; set; } = string.Empty; 9 | 10 | [JsonPropertyName("maximum")] 11 | public string Maximum { get; set; } = string.Empty; 12 | 13 | public override string ToString() 14 | { 15 | return $"{this.Minimum} - {this.Maximum}"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sample/Pokedex/Pokedex.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | latest 7 | enable 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /sample/Pokedex/PokemonResponse.cs: -------------------------------------------------------------------------------- 1 | using Shared.Models; 2 | 3 | namespace Pokedex; 4 | 5 | public class PokemonResponse 6 | { 7 | public Pokemon? Pokemon { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /sample/Pokedex/PokemonService.cs: -------------------------------------------------------------------------------- 1 | using GraphQL; 2 | using GraphQL.Client.Abstractions.Websocket; 3 | using GraphQL.Client.Http; 4 | using GraphQL.Client.Serializer.SystemTextJson; 5 | using GraphQL.Query.Builder; 6 | using GraphQL.Query.Builder.Formatter.SystemTextJson; 7 | using Shared.Models; 8 | 9 | namespace Pokedex; 10 | 11 | class PokemonService 12 | { 13 | private readonly string graphqlPokemonUrl; 14 | 15 | private readonly QueryOptions options = new() 16 | { 17 | Formatter = SystemTextJsonPropertyNameFormatter.Format 18 | }; 19 | 20 | private readonly IGraphQLWebsocketJsonSerializer serializer = new SystemTextJsonSerializer(); 21 | 22 | /// Initializes a new instance of the class. 23 | /// The pokemon graphQL API URL 24 | public PokemonService(string apiUrl) 25 | { 26 | this.graphqlPokemonUrl = apiUrl; 27 | } 28 | 29 | /// Returns a Pokemon. 30 | /// The Pokemon name. 31 | public async Task GetPokemon(string name) 32 | { 33 | IQuery query = new Query("pokemon", this.options) 34 | .AddArguments(new { name }) 35 | .AddField(p => p.Id) 36 | .AddField(p => p.Number) 37 | .AddField(p => p.Name) 38 | .AddField(p => p.Height, hq => hq 39 | .AddField(h => h!.Minimum) 40 | .AddField(h => h!.Maximum) 41 | ) 42 | .AddField(p => p.Weight, wq => wq 43 | .AddField(w => w!.Minimum) 44 | .AddField(w => w!.Maximum) 45 | ) 46 | .AddField(p => p.Types) 47 | .AddField(p => p.Attacks, aq => aq 48 | .AddField(a => a!.Fast, fq => fq 49 | .AddField(f => f.Name) 50 | .AddField(f => f.Type) 51 | .AddField(f => f.Damage) 52 | ) 53 | .AddField(a => a!.Special, sq => sq 54 | .AddField(f => f.Name) 55 | .AddField(f => f.Type) 56 | .AddField(f => f.Damage) 57 | ) 58 | ); 59 | GraphQLRequest request = new() { Query = "{" + query.Build() + "}" }; 60 | 61 | using GraphQLHttpClient client = new(this.graphqlPokemonUrl, this.serializer); 62 | GraphQLResponse response = await client.SendQueryAsync(request); 63 | 64 | return response.Data.Pokemon; 65 | } 66 | 67 | /// Returns the Pokemons. 68 | /// The Pokemons names. 69 | public async IAsyncEnumerable GetPokemons(string[] names) 70 | { 71 | foreach (string name in names) 72 | { 73 | yield return await this.GetPokemon(name); 74 | } 75 | } 76 | 77 | /// Returns all Pokemons 78 | public async Task> GetAllPokemons() 79 | { 80 | IQuery query = new Query("pokemons", this.options) 81 | .AddArguments(new { first = 100 }) 82 | .AddField(p => p.Id) 83 | .AddField(p => p.Number) 84 | .AddField(p => p.Name) 85 | .AddField(p => p.Height, hq => hq 86 | .AddField(h => h!.Minimum) 87 | .AddField(h => h!.Maximum) 88 | ) 89 | .AddField(p => p.Weight, wq => wq 90 | .AddField(w => w!.Minimum) 91 | .AddField(w => w!.Maximum) 92 | ) 93 | .AddField(p => p.Types); 94 | GraphQLRequest request = new() { Query = "{" + query.Build() + "}" }; 95 | 96 | using GraphQLHttpClient client = new(this.graphqlPokemonUrl, this.serializer); 97 | GraphQLResponse response = await client.SendQueryAsync(request); 98 | 99 | return response.Data.Pokemons ?? []; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /sample/Pokedex/PokemonsResponse.cs: -------------------------------------------------------------------------------- 1 | using Shared.Models; 2 | 3 | namespace Pokedex; 4 | 5 | public class PokemonsResponse 6 | { 7 | public Pokemon[]? Pokemons { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /sample/Pokedex/Program.cs: -------------------------------------------------------------------------------- 1 | using Pokedex; 2 | using Shared.Models; 3 | 4 | PokemonService service = new("https://graphql-pokemon2.vercel.app/"); 5 | 6 | // The official URL https://graphql-pokemon.now.sh is actualy down. 7 | 8 | if (args != null && args.Length > 0) 9 | { 10 | await foreach (Pokemon? pokemon in service.GetPokemons(args)) 11 | { 12 | Console.WriteLine(pokemon); 13 | } 14 | } 15 | else 16 | { 17 | foreach (Pokemon? pokemon in await service.GetAllPokemons()) 18 | { 19 | Console.WriteLine(pokemon); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sample/Pokedex/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Pokedex": { 4 | "commandName": "Project", 5 | "commandLineArgs": "bulbasaur pikachu" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /sample/Pokedex/README.md: -------------------------------------------------------------------------------- 1 | # GraphQL Query Builder Sample: Pokedex 2 | 3 | This sample is base on the [GraphQL Pokémon](https://github.com/lucasbento/graphql-pokemon) API 4 | 5 | ## Build 6 | 7 | ### Windows 8 | 9 | ```console 10 | > dotnet build -c Release -r win-x64 11 | ``` 12 | 13 | ### Ubuntu 14 | 15 | ```console 16 | > dotnet build -c Release -r linux-x64 17 | ``` 18 | 19 | ### macOS 20 | 21 | ```console 22 | > dotnet build -c Release -r osx-x64 23 | ``` 24 | 25 | > See all runtime identifiers [here](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog) 26 | 27 | ## Run 28 | 29 | (Example for Windows) 30 | 31 | ```console 32 | > .\bin\Release\net5\win-x64\Pokedex.exe pikachu 33 | ``` 34 | 35 | ```console 36 | 025 Pikachu 37 | Height: 0.35m - 0.45m 38 | Weight: 5.25kg - 6.75kg 39 | Types: Electric 40 | Fast attacks: 41 | - Quick Attack (type: Normal, damage: 10) 42 | - Thunder Shock (type: Electric, damage: 5) 43 | Special attacks: 44 | - Discharge (type: Electric, damage: 35) 45 | - Thunder (type: Electric, damage: 100) 46 | - Thunderbolt (type: Electric, damage: 55) 47 | ``` 48 | -------------------------------------------------------------------------------- /src/GraphQL.Query.Builder/CamelCasePropertyNameFormatter.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace GraphQL.Query.Builder; 4 | 5 | /// The camel case property name formatter class. 6 | public static class CamelCasePropertyNameFormatter 7 | { 8 | /// Formats the property name in camel case. 9 | /// The property. 10 | public static readonly Func Format = property => 11 | { 12 | RequiredArgument.NotNull(property, nameof(property)); 13 | 14 | return char.ToLowerInvariant(property.Name[0]) + property.Name.Substring(1); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/GraphQL.Query.Builder/GraphQL.Query.Builder.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | latest 5 | enable 6 | enable 7 | 0.0.0 8 | true 9 | 10 | Charles de Vandière 11 | © 2024 Charles de Vandière 12 | GraphQL.Query.Builder 13 | GraphQL.Query.Builder 14 | A tool to build GraphQL query from a C# model. 15 | A tool to build GraphQL query from a C# model. 16 | logo.png 17 | https://raw.githubusercontent.com/charlesdevandiere/graphql-query-builder-dotnet/master/logo.png 18 | MIT 19 | https://charlesdevandiere.github.io/graphql-query-builder-dotnet 20 | README.md 21 | graphql 22 | git 23 | https://github.com/charlesdevandiere/graphql-query-builder-dotnet 24 | 25 | true 26 | true 27 | true 28 | snupkg 29 | 30 | 31 | 32 | true 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/GraphQL.Query.Builder/IQuery.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Query.Builder; 2 | 3 | /// The query interface. 4 | public interface IQuery 5 | { 6 | /// Gets the query name. 7 | string Name { get; } 8 | 9 | /// Gets the alias name. 10 | string? AliasName { get; } 11 | 12 | /// Builds the query. 13 | /// The GraphQL query as string, without outer enclosing block. 14 | /// Must have a 'Name' specified in the Query 15 | /// Must have a one or more 'Select' fields in the Query 16 | string Build(); 17 | } 18 | -------------------------------------------------------------------------------- /src/GraphQL.Query.Builder/IQueryOf{T}.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace GraphQL.Query.Builder; 4 | 5 | /// Query of TSource interface. 6 | public interface IQuery : IQuery 7 | { 8 | /// Gets the select list. 9 | List SelectList { get; } 10 | 11 | /// Gets the arguments. 12 | Dictionary Arguments { get; } 13 | 14 | /// Sets the query alias name. 15 | /// The alias name. 16 | /// The query. 17 | IQuery Alias(string alias); 18 | 19 | /// Adds a field to the query. 20 | /// The property type. 21 | /// The field selector. 22 | /// The query. 23 | IQuery AddField(Expression> selector); 24 | 25 | /// Adds a field to the query. 26 | /// The field name. 27 | /// The query. 28 | IQuery AddField(string field); 29 | 30 | /// Adds a sub-object field to the query. 31 | /// The sub-object type. 32 | /// The field selector. 33 | /// The sub-object query building function. 34 | /// The query. 35 | IQuery AddField( 36 | Expression> selector, 37 | Func, IQuery> build) 38 | where TSubSource : class?; 39 | 40 | /// Adds a sub-list field to the query. 41 | /// The sub-list object type. 42 | /// The field selector. 43 | /// The sub-object query building function. 44 | /// The query. 45 | IQuery AddField( 46 | Expression>> selector, 47 | Func, IQuery> build) 48 | where TSubSource : class?; 49 | 50 | /// Adds a sub-object field to the query. 51 | /// The sub-object type. 52 | /// The field name. 53 | /// The sub-object query building function. 54 | /// The query. 55 | IQuery AddField( 56 | string field, 57 | Func, IQuery> build) 58 | where TSubSource : class?; 59 | 60 | /// Adds an union to the query. 61 | /// The union type. 62 | /// The union type name. 63 | /// The union building function. 64 | /// The query. 65 | IQuery AddUnion( 66 | string typeName, 67 | Func, IQuery> build) 68 | where TUnionType : class?, TSource; 69 | 70 | /// Adds an union to the query. 71 | /// The union type. 72 | /// The union building function. 73 | /// The query. 74 | IQuery AddUnion( 75 | Func, IQuery> build) 76 | where TUnionType : class?, TSource; 77 | 78 | /// Adds a new argument to the query. 79 | /// The argument name. 80 | /// The value. 81 | /// The query. 82 | IQuery AddArgument(string key, object? value); 83 | 84 | /// Adds arguments to the query. 85 | /// the dictionary argument. 86 | /// The query. 87 | IQuery AddArguments(Dictionary arguments); 88 | 89 | /// Adds arguments to the query. 90 | /// The arguments object type. 91 | /// The arguments object. 92 | /// The query. 93 | IQuery AddArguments(TArguments arguments) where TArguments : class; 94 | } 95 | -------------------------------------------------------------------------------- /src/GraphQL.Query.Builder/IQueryStringBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Query.Builder; 2 | 3 | /// The GraphQL query builder interface. 4 | public interface IQueryStringBuilder 5 | { 6 | /// Clears the string builder. 7 | void Clear(); 8 | 9 | /// Builds the query. 10 | /// The query. 11 | /// The GraphQL query as string, without outer enclosing block. 12 | string Build(IQuery query); 13 | } 14 | -------------------------------------------------------------------------------- /src/GraphQL.Query.Builder/QueryOf{T}.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | 5 | [assembly: InternalsVisibleTo("GraphQL.Query.Builder.UnitTests")] 6 | namespace GraphQL.Query.Builder; 7 | 8 | /// The query class. 9 | public class Query : IQuery 10 | { 11 | /// The query options. 12 | protected readonly QueryOptions? options; 13 | 14 | /// Gets the select list. 15 | public List SelectList { get; } = []; 16 | 17 | /// Gets the arguments. 18 | public Dictionary Arguments { get; } = []; 19 | 20 | /// Gets the query name. 21 | public string Name { get; private set; } 22 | 23 | /// Gets the alias name. 24 | public string? AliasName { get; private set; } 25 | 26 | /// Gets the query string builder. 27 | protected IQueryStringBuilder QueryStringBuilder { get; } = new QueryStringBuilder(); 28 | 29 | /// Initializes a new instance of the class. 30 | public Query(string name) 31 | { 32 | RequiredArgument.NotNullOrEmpty(name, nameof(name)); 33 | 34 | this.Name = name; 35 | } 36 | 37 | /// Initializes a new instance of the class. 38 | public Query(string name, QueryOptions? options) 39 | { 40 | RequiredArgument.NotNullOrEmpty(name, nameof(name)); 41 | 42 | this.Name = name; 43 | this.options = options; 44 | if (options?.QueryStringBuilderFactory != null) 45 | { 46 | this.QueryStringBuilder = options.QueryStringBuilderFactory(); 47 | } 48 | else if (options?.Formatter != null) 49 | { 50 | this.QueryStringBuilder = new QueryStringBuilder(options.Formatter); 51 | } 52 | } 53 | 54 | /// Sets the query alias name. 55 | /// The alias name. 56 | /// The query. 57 | public IQuery Alias(string alias) 58 | { 59 | RequiredArgument.NotNullOrEmpty(alias, nameof(alias)); 60 | 61 | this.AliasName = alias; 62 | 63 | return this; 64 | } 65 | 66 | /// Adds a field to the query. 67 | /// The property type. 68 | /// The field selector. 69 | /// The query. 70 | public IQuery AddField(Expression> selector) 71 | { 72 | RequiredArgument.NotNull(selector, nameof(selector)); 73 | 74 | PropertyInfo property = GetPropertyInfo(selector); 75 | string name = this.GetPropertyName(property); 76 | 77 | this.SelectList.Add(name); 78 | 79 | return this; 80 | } 81 | 82 | /// Adds a field to the query. 83 | /// The field name. 84 | /// The query. 85 | public IQuery AddField(string field) 86 | { 87 | RequiredArgument.NotNullOrEmpty(field, nameof(field)); 88 | 89 | this.SelectList.Add(field); 90 | 91 | return this; 92 | } 93 | 94 | /// Adds a sub-object field to the query. 95 | /// The sub-object type. 96 | /// The field selector. 97 | /// The sub-object query building function. 98 | /// The query. 99 | public IQuery AddField( 100 | Expression> selector, 101 | Func, IQuery> build) 102 | where TSubSource : class? 103 | { 104 | RequiredArgument.NotNull(selector, nameof(selector)); 105 | RequiredArgument.NotNull(build, nameof(build)); 106 | 107 | PropertyInfo property = GetPropertyInfo(selector); 108 | string name = this.GetPropertyName(property); 109 | 110 | return this.AddField(name, build); 111 | } 112 | 113 | /// Adds a sub-list field to the query. 114 | /// The sub-list object type. 115 | /// The field selector. 116 | /// The sub-object query building function. 117 | /// The query. 118 | public IQuery AddField( 119 | Expression>> selector, 120 | Func, IQuery> build) 121 | where TSubSource : class? 122 | { 123 | RequiredArgument.NotNull(selector, nameof(selector)); 124 | RequiredArgument.NotNull(build, nameof(build)); 125 | 126 | PropertyInfo property = GetPropertyInfo(selector); 127 | string name = this.GetPropertyName(property); 128 | 129 | return this.AddField(name, build); 130 | } 131 | 132 | /// Adds a sub-object field to the query. 133 | /// The sub-object type. 134 | /// The field name. 135 | /// The sub-object query building function. 136 | /// The query. 137 | public IQuery AddField( 138 | string field, 139 | Func, IQuery> build) 140 | where TSubSource : class? 141 | { 142 | RequiredArgument.NotNullOrEmpty(field, nameof(field)); 143 | RequiredArgument.NotNull(build, nameof(build)); 144 | 145 | Query query = new(field, this.options); 146 | IQuery subQuery = build.Invoke(query); 147 | 148 | this.SelectList.Add(subQuery); 149 | 150 | return this; 151 | } 152 | 153 | /// Adds an union to the query. 154 | /// The union type. 155 | /// The union type name. 156 | /// The union building function. 157 | /// The query. 158 | public IQuery AddUnion( 159 | string typeName, 160 | Func, IQuery> build) 161 | where TUnionType : class?, TSource 162 | { 163 | RequiredArgument.NotNullOrEmpty(typeName, nameof(typeName)); 164 | RequiredArgument.NotNull(build, nameof(build)); 165 | 166 | Query query = new($"... on {typeName}", this.options); 167 | IQuery union = build.Invoke(query); 168 | 169 | this.SelectList.Add(union); 170 | 171 | return this; 172 | } 173 | 174 | /// Adds an union to the query. 175 | /// The union type. 176 | /// The union building function. 177 | /// The query. 178 | public IQuery AddUnion( 179 | Func, IQuery> build) 180 | where TUnionType : class?, TSource 181 | { 182 | RequiredArgument.NotNull(build, nameof(build)); 183 | 184 | return this.AddUnion(typeof(TUnionType).Name, build); 185 | } 186 | 187 | /// Adds a new argument to the query. 188 | /// The argument name. 189 | /// The value. 190 | /// The query. 191 | public IQuery AddArgument(string key, object? value) 192 | { 193 | RequiredArgument.NotNullOrEmpty(key, nameof(key)); 194 | 195 | this.Arguments.Add(key, value); 196 | 197 | return this; 198 | } 199 | 200 | /// Adds arguments to the query. 201 | /// the dictionary argument. 202 | /// The query. 203 | public IQuery AddArguments(Dictionary arguments) 204 | { 205 | RequiredArgument.NotNull(arguments, nameof(arguments)); 206 | 207 | foreach (KeyValuePair argument in arguments) 208 | { 209 | this.Arguments.Add(argument.Key, argument.Value); 210 | } 211 | 212 | return this; 213 | } 214 | 215 | /// Adds arguments to the query. 216 | /// The arguments object type. 217 | /// The arguments object. 218 | /// The query. 219 | public IQuery AddArguments(TArguments arguments) where TArguments : class 220 | { 221 | RequiredArgument.NotNull(arguments, nameof(arguments)); 222 | 223 | IEnumerable properties = arguments 224 | .GetType() 225 | .GetProperties() 226 | .Where(property => property.GetValue(arguments) != null) 227 | .OrderBy(property => property.Name); 228 | foreach (PropertyInfo property in properties) 229 | { 230 | this.Arguments.Add( 231 | this.GetPropertyName(property), 232 | property.GetValue(arguments)); 233 | } 234 | 235 | return this; 236 | } 237 | 238 | /// Builds the query. 239 | /// The GraphQL query as string, without outer enclosing block. 240 | /// Must have a 'Name' specified in the Query 241 | /// Must have a one or more 'Select' fields in the Query 242 | public string Build() 243 | { 244 | this.QueryStringBuilder.Clear(); 245 | 246 | return this.QueryStringBuilder.Build(this); 247 | } 248 | 249 | /// Gets property infos from lambda. 250 | /// The lambda. 251 | /// The property. 252 | /// The property infos. 253 | private static PropertyInfo GetPropertyInfo(Expression> lambda) 254 | { 255 | RequiredArgument.NotNull(lambda, nameof(lambda)); 256 | 257 | if (lambda.Body is not MemberExpression member) 258 | { 259 | throw new ArgumentException($"Expression '{lambda}' body is not member expression."); 260 | } 261 | 262 | if (member.Member is not PropertyInfo propertyInfo) 263 | { 264 | throw new ArgumentException($"Expression '{lambda}' not refers to a property."); 265 | } 266 | 267 | if (propertyInfo.ReflectedType is null) 268 | { 269 | throw new ArgumentException($"Expression '{lambda}' not refers to a property."); 270 | } 271 | 272 | Type type = typeof(TSource); 273 | if (type != propertyInfo.ReflectedType && !propertyInfo.ReflectedType.IsAssignableFrom(type)) 274 | { 275 | throw new ArgumentException($"Expression '{lambda}' refers to a property that is not from type {type}."); 276 | } 277 | 278 | return propertyInfo; 279 | } 280 | 281 | private string GetPropertyName(PropertyInfo property) 282 | { 283 | RequiredArgument.NotNull(property, nameof(property)); 284 | 285 | return this.options?.Formatter is not null 286 | ? this.options.Formatter.Invoke(property) 287 | : property.Name; 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/GraphQL.Query.Builder/QueryOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace GraphQL.Query.Builder; 4 | 5 | /// The query options class. 6 | public class QueryOptions 7 | { 8 | /// Gets or sets the property name formatter. 9 | public Func? Formatter { get; set; } 10 | 11 | /// Gets or sets the query string builder factory. 12 | public Func? QueryStringBuilderFactory { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /src/GraphQL.Query.Builder/QueryStringBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Globalization; 3 | using System.Reflection; 4 | using System.Text; 5 | 6 | namespace GraphQL.Query.Builder; 7 | 8 | /// The GraphQL query builder class. 9 | public class QueryStringBuilder : IQueryStringBuilder 10 | { 11 | /// The property name formatter. 12 | protected readonly Func? formatter; 13 | 14 | /// The query string builder. 15 | public StringBuilder QueryString { get; } = new(); 16 | 17 | /// Initializes a new instance of the class. 18 | public QueryStringBuilder() { } 19 | 20 | /// Initializes a new instance of the class. 21 | /// The property name formatter 22 | public QueryStringBuilder(Func formatter) 23 | { 24 | this.formatter = formatter; 25 | } 26 | 27 | /// Builds the query. 28 | /// The query. 29 | /// The GraphQL query as string, without outer enclosing block. 30 | public string Build(IQuery query) 31 | { 32 | if (!string.IsNullOrWhiteSpace(query.AliasName)) 33 | { 34 | this.QueryString.Append($"{query.AliasName}:"); 35 | } 36 | 37 | this.QueryString.Append(query.Name); 38 | 39 | if (query.Arguments.Count > 0) 40 | { 41 | this.QueryString.Append("("); 42 | this.AddParams(query); 43 | this.QueryString.Append(")"); 44 | } 45 | 46 | if (query.SelectList.Count > 0) 47 | { 48 | this.QueryString.Append("{"); 49 | this.AddFields(query); 50 | this.QueryString.Append("}"); 51 | } 52 | 53 | return this.QueryString.ToString(); 54 | } 55 | 56 | /// Clears the string builder. 57 | public void Clear() 58 | { 59 | this.QueryString.Clear(); 60 | } 61 | 62 | /// 63 | /// Formats query param. 64 | /// 65 | /// Returns: 66 | /// 67 | /// 68 | /// null 69 | /// null 70 | /// 71 | /// 72 | /// String 73 | /// "foo" 74 | /// 75 | /// 76 | /// Number 77 | /// 10 78 | /// 79 | /// 80 | /// Boolean 81 | /// true or false 82 | /// 83 | /// 84 | /// Enum 85 | /// EnumValue 86 | /// 87 | /// 88 | /// DateTime 89 | /// "2024-06-15T13:45:30.0000000Z" 90 | /// 91 | /// 92 | /// Key value pair 93 | /// foo:"bar" or foo:10 ... 94 | /// 95 | /// 96 | /// List 97 | /// ["foo","bar"] or [1,2] ... 98 | /// 99 | /// 100 | /// Dictionary 101 | /// {foo:"bar",b:10} 102 | /// 103 | /// 104 | /// Object 105 | /// {foo:"bar",b:10} 106 | /// 107 | /// 108 | /// 109 | /// 110 | /// The formatted query param. 111 | /// Invalid Object Type in Param List 112 | protected internal virtual string FormatQueryParam(object? value) 113 | { 114 | switch (value) 115 | { 116 | case null: 117 | return "null"; 118 | 119 | case string strValue: 120 | string encoded = strValue.Replace("\"", "\\\""); 121 | return $"\"{encoded}\""; 122 | 123 | case char charValue: 124 | return $"\"{charValue}\""; 125 | 126 | case byte byteValue: 127 | return byteValue.ToString(); 128 | 129 | case sbyte sbyteValue: 130 | return sbyteValue.ToString(); 131 | 132 | case short shortValue: 133 | return shortValue.ToString(); 134 | 135 | case ushort ushortValue: 136 | return ushortValue.ToString(); 137 | 138 | case int intValue: 139 | return intValue.ToString(); 140 | 141 | case uint uintValue: 142 | return uintValue.ToString(); 143 | 144 | case long longValue: 145 | return longValue.ToString(); 146 | 147 | case ulong ulongValue: 148 | return ulongValue.ToString(); 149 | 150 | case float floatValue: 151 | return floatValue.ToString(CultureInfo.CreateSpecificCulture("en-us")); 152 | 153 | case double doubleValue: 154 | return doubleValue.ToString(CultureInfo.CreateSpecificCulture("en-us")); 155 | 156 | case decimal decimalValue: 157 | return decimalValue.ToString(CultureInfo.CreateSpecificCulture("en-us")); 158 | 159 | case bool booleanValue: 160 | return booleanValue ? "true" : "false"; 161 | 162 | case Enum enumValue: 163 | return enumValue.ToString(); 164 | 165 | case DateTime dateTimeValue: 166 | return this.FormatQueryParam(dateTimeValue.ToString("o")); 167 | 168 | case KeyValuePair kvValue: 169 | return $"{kvValue.Key}:{this.FormatQueryParam(kvValue.Value)}"; 170 | 171 | case IDictionary dictValue: 172 | return $"{{{string.Join(",", dictValue.Select(e => this.FormatQueryParam(e)))}}}"; 173 | 174 | case IEnumerable enumerableValue: 175 | List items = []; 176 | foreach (object item in enumerableValue) 177 | { 178 | items.Add(this.FormatQueryParam(item)); 179 | } 180 | return $"[{string.Join(",", items)}]"; 181 | 182 | case { } objectValue: 183 | Dictionary dictionay = this.ObjectToDictionary(objectValue); 184 | return this.FormatQueryParam(dictionay); 185 | 186 | default: 187 | throw new InvalidDataException($"Invalid Object Type in Param List: {value.GetType()}"); 188 | } 189 | } 190 | 191 | /// Adds query params to the query string. 192 | /// The query. 193 | protected internal void AddParams(IQuery query) 194 | { 195 | RequiredArgument.NotNull(query, nameof(query)); 196 | 197 | foreach (KeyValuePair param in query.Arguments) 198 | { 199 | this.QueryString.Append($"{param.Key}:{this.FormatQueryParam(param.Value)},"); 200 | } 201 | 202 | if (query.Arguments.Count > 0) 203 | { 204 | this.QueryString.Length--; 205 | } 206 | } 207 | 208 | /// Adds fields to the query sting. 209 | /// The query. 210 | /// Invalid Object in Field List 211 | protected internal void AddFields(IQuery query) 212 | { 213 | foreach (object? item in query.SelectList) 214 | { 215 | switch (item) 216 | { 217 | case string field: 218 | this.QueryString.Append($"{field} "); 219 | break; 220 | 221 | case IQuery subQuery: 222 | this.QueryString.Append($"{subQuery.Build()} "); 223 | break; 224 | 225 | default: 226 | throw new ArgumentException("Invalid Field Type Specified, must be `string` or `Query`"); 227 | } 228 | } 229 | 230 | if (query.SelectList.Count > 0) 231 | { 232 | this.QueryString.Length--; 233 | } 234 | } 235 | 236 | /// Convert object into dictionary. 237 | /// The object. 238 | /// The object as dictionary. 239 | private Dictionary ObjectToDictionary(object @object) => 240 | @object 241 | .GetType() 242 | .GetProperties() 243 | .Where(property => property.GetValue(@object) != null) 244 | .Select(property => 245 | new KeyValuePair( 246 | this.formatter is not null ? this.formatter.Invoke(property) : property.Name, 247 | property.GetValue(@object))) 248 | .OrderBy(property => property.Key) 249 | .ToDictionary(property => property.Key, property => property.Value); 250 | } 251 | -------------------------------------------------------------------------------- /src/GraphQL.Query.Builder/RequiredArgument.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Query.Builder; 2 | 3 | internal static class RequiredArgument 4 | { 5 | /// Verifies argument is not null. 6 | /// The parameter. 7 | /// The parameter name. 8 | /// The parameter type. 9 | internal static void NotNull(TArgument param, string paramName) 10 | { 11 | if (param is null) 12 | { 13 | throw new ArgumentNullException(paramName); 14 | } 15 | } 16 | 17 | /// Verifies argument is not null or empty. 18 | /// The parameter. 19 | /// The parameter name. 20 | internal static void NotNullOrEmpty(string param, string paramName) 21 | { 22 | RequiredArgument.NotNull(param, paramName); 23 | 24 | if (param.Length == 0) 25 | { 26 | throw new ArgumentException("Value cannot be empty.", paramName); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/GraphQL.Query.Builder.UnitTests/CamelCasePropertyNameFormatterTests.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Query.Builder.UnitTests.Models; 2 | using Xunit; 3 | 4 | namespace GraphQL.Query.Builder.UnitTests; 5 | 6 | public class CamelCasePropertyNameFormatterTests 7 | { 8 | [Fact] 9 | public void Format_ShouldTransfomeNameIntoCamelCase() 10 | { 11 | string name = CamelCasePropertyNameFormatter.Format.Invoke(typeof(Car).GetProperty(nameof(Car.Name))!); 12 | Assert.Equal("name", name); 13 | } 14 | 15 | [Fact] 16 | public void Format_ShouldThrowIfPropertyIsNull() 17 | { 18 | #nullable disable 19 | Assert.Throws(() => CamelCasePropertyNameFormatter.Format.Invoke(null)); 20 | #nullable restore 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/GraphQL.Query.Builder.UnitTests/CustomQueryStringBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Xunit; 3 | 4 | namespace GraphQL.Query.Builder.UnitTests; 5 | 6 | public class CustomQueryStringBuilderTests 7 | { 8 | [Fact] 9 | public void TestOverride() 10 | { 11 | QueryOptions options = new() 12 | { 13 | QueryStringBuilderFactory = () => new ConstantCaseEnumQueryStringBuilder() 14 | }; 15 | string query = new Query("something", options) 16 | .AddArgument("case", Cases.ConstantCase) 17 | .AddField("some") 18 | .Build(); 19 | 20 | Assert.Equal("something(case:CONSTANT_CASE){some}", query); 21 | } 22 | 23 | enum Cases 24 | { 25 | CamelCase, 26 | PascalCase, 27 | ConstantCase 28 | } 29 | 30 | class ConstantCaseEnumQueryStringBuilder : QueryStringBuilder 31 | { 32 | protected internal override string FormatQueryParam(object? value) => 33 | value switch 34 | { 35 | Enum e => ToConstantCase(e.ToString()), 36 | _ => base.FormatQueryParam(value) 37 | }; 38 | 39 | private static string ToConstantCase(string name) 40 | { 41 | StringBuilder sb = new(); 42 | bool firstUpperLetter = true; 43 | foreach (char c in name) 44 | { 45 | if (char.IsUpper(c)) 46 | { 47 | if (!firstUpperLetter) 48 | { 49 | sb.Append('_'); 50 | } 51 | firstUpperLetter = false; 52 | } 53 | 54 | sb.Append(char.ToUpperInvariant(c)); 55 | } 56 | string result = sb.ToString(); 57 | 58 | return result; 59 | } 60 | } 61 | 62 | [Fact] 63 | public void TestArgumentsWithCamelCasePropertyNameFormatter() 64 | { 65 | QueryOptions options = new() 66 | { 67 | Formatter = CamelCasePropertyNameFormatter.Format 68 | }; 69 | string query = new Query("something", options) 70 | .AddArguments(new 71 | { 72 | SomeObject = new 73 | { 74 | InnerObjectField = "camel case" 75 | } 76 | }) 77 | .AddField("some") 78 | .Build(); 79 | 80 | Assert.Equal("something(someObject:{innerObjectField:\"camel case\"}){some}", query); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/GraphQL.Query.Builder.UnitTests/GraphQL.Query.Builder.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | latest 6 | enable 7 | enable 8 | false 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | 18 | 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | all 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/GraphQL.Query.Builder.UnitTests/Models/Car.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Query.Builder.UnitTests.Models; 2 | 3 | public sealed class Car : Vehicule { } 4 | -------------------------------------------------------------------------------- /tests/GraphQL.Query.Builder.UnitTests/Models/Color.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Query.Builder.UnitTests.Models; 2 | 3 | public class Color 4 | { 5 | public byte Red { get; set; } 6 | public byte Green { get; set; } 7 | public byte Blue { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /tests/GraphQL.Query.Builder.UnitTests/Models/Customer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace GraphQL.Query.Builder.UnitTests.Models; 4 | 5 | public class Customer 6 | { 7 | public string? Name { get; set; } 8 | public List? Orders { get; set; } 9 | public int Age { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /tests/GraphQL.Query.Builder.UnitTests/Models/Order.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Query.Builder.UnitTests.Models; 2 | 3 | public class Order 4 | { 5 | public Car? Product { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /tests/GraphQL.Query.Builder.UnitTests/Models/Truck.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Query.Builder.UnitTests.Models; 2 | 3 | public sealed class Truck : Vehicule { } 4 | -------------------------------------------------------------------------------- /tests/GraphQL.Query.Builder.UnitTests/Models/Vehicule.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Query.Builder.UnitTests.Models; 2 | 3 | public abstract class Vehicule 4 | { 5 | public string? Name { get; set; } 6 | public string? Manufacturer { get; set; } 7 | public decimal Price { get; set; } 8 | public Color? Color { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /tests/GraphQL.Query.Builder.UnitTests/QueryOf{T}Tests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using GraphQL.Query.Builder.UnitTests.Models; 3 | using Xunit; 4 | 5 | namespace GraphQL.Query.Builder.UnitTests; 6 | 7 | public class QueryOfTTests 8 | { 9 | [Fact] 10 | public void Query_name() 11 | { 12 | // Arrange 13 | const string name = "user"; 14 | 15 | Query query = new(name); 16 | 17 | // Assert 18 | Assert.Equal(name, query.Name); 19 | } 20 | 21 | [Fact] 22 | public void Query_name_required() 23 | { 24 | #nullable disable 25 | Assert.Throws(() => new Query(null)); 26 | #nullable restore 27 | } 28 | 29 | [Fact] 30 | public void AddField_list() 31 | { 32 | // Arrange 33 | Query query = new("something"); 34 | 35 | List selectList = ["id", "name"]; 36 | 37 | // Act 38 | foreach (string field in selectList) 39 | { 40 | query.AddField(field); 41 | } 42 | 43 | // Assert 44 | Assert.Equal(selectList, query.SelectList); 45 | } 46 | 47 | [Fact] 48 | public void AddField_string() 49 | { 50 | // Arrange 51 | Query query = new("something"); 52 | 53 | const string select = "id"; 54 | 55 | // Act 56 | query.AddField(select); 57 | 58 | // Assert 59 | Assert.Equal(select, query.SelectList[0]); 60 | } 61 | 62 | [Fact] 63 | public void AddField_chained() 64 | { 65 | // Arrange 66 | Query query = new("something"); 67 | 68 | // Act 69 | query.AddField("some").AddField("thing").AddField("else"); 70 | 71 | // Assert 72 | List shouldEqual = 73 | [ 74 | "some", 75 | "thing", 76 | "else" 77 | ]; 78 | Assert.Equal(shouldEqual, query.SelectList); 79 | } 80 | 81 | [Fact] 82 | public void AddField_array() 83 | { 84 | // Arrange 85 | Query query = new("something"); 86 | 87 | string[] selects = 88 | { 89 | "id", 90 | "name" 91 | }; 92 | 93 | // Act 94 | foreach (string field in selects) 95 | { 96 | query.AddField(field); 97 | } 98 | 99 | // Assert 100 | List shouldEqual = 101 | [ 102 | "id", 103 | "name" 104 | ]; 105 | Assert.Equal(shouldEqual, query.SelectList); 106 | } 107 | 108 | [Fact] 109 | public void AddArgument_string_number() 110 | { 111 | // Arrange 112 | Query query = new("something"); 113 | 114 | // Act 115 | query.AddArgument("id", 1); 116 | 117 | // Assert 118 | Assert.Equal(1, query.Arguments["id"]); 119 | } 120 | 121 | [Fact] 122 | public void AddArgument_string_string() 123 | { 124 | // Arrange 125 | Query query = new("something"); 126 | 127 | // Act 128 | query.AddArgument("name", "danny"); 129 | 130 | // Assert 131 | Assert.Equal("danny", query.Arguments["name"]); 132 | } 133 | 134 | [Fact] 135 | public void AddArgument_string_dictionary() 136 | { 137 | // Arrange 138 | Query query = new("something"); 139 | 140 | Dictionary dict = new() 141 | { 142 | { "from", 1 }, 143 | { "to", 100 } 144 | }; 145 | 146 | // Act 147 | query.AddArgument("price", dict); 148 | 149 | // Assert 150 | Dictionary queryWhere = (Dictionary)query.Arguments["price"]!; 151 | Assert.Equal(1, queryWhere["from"]); 152 | Assert.Equal(100, queryWhere["to"]); 153 | Assert.Equal(dict, (ICollection)query.Arguments["price"]!); 154 | } 155 | 156 | [Fact] 157 | public void AddArguments_object() 158 | { 159 | // Arrange 160 | Query query = new("car"); 161 | 162 | Car car = new() 163 | { 164 | Name = "Bee", 165 | Price = 10000 166 | }; 167 | 168 | // Act 169 | query.AddArguments(car); 170 | 171 | // Assert 172 | Assert.Equal(2, query.Arguments.Count); 173 | Assert.Equal("Bee", query.Arguments[nameof(Car.Name)]); 174 | Assert.Equal(10000m, query.Arguments[nameof(Car.Price)]); 175 | } 176 | 177 | [Fact] 178 | public void AddArguments_anonymous() 179 | { 180 | // Arrange 181 | Query query = new("something"); 182 | 183 | var @object = new 184 | { 185 | from = 1, 186 | to = 100 187 | }; 188 | 189 | // Act 190 | query.AddArguments(@object); 191 | 192 | // Assert 193 | Assert.Equal(2, query.Arguments.Count); 194 | Assert.Equal(1, query.Arguments["from"]); 195 | Assert.Equal(100, query.Arguments["to"]); 196 | } 197 | 198 | [Fact] 199 | public void AddArguments_dictionary() 200 | { 201 | // Arrange 202 | Query query = new("something"); 203 | 204 | Dictionary dictionary = new() 205 | { 206 | { "from", 1 }, 207 | { "to", 100 } 208 | }; 209 | 210 | // Act 211 | query.AddArguments(dictionary); 212 | 213 | // Assert 214 | Assert.Equal(2, query.Arguments.Count); 215 | Assert.Equal(1, query.Arguments["from"]); 216 | Assert.Equal(100, query.Arguments["to"]); 217 | } 218 | 219 | [Fact] 220 | public void AddArgument_chained() 221 | { 222 | // Arrange 223 | Query query = new("something"); 224 | 225 | Dictionary dict = new() 226 | { 227 | { "from", 1 }, 228 | { "to", 100 } 229 | }; 230 | 231 | // Act 232 | query 233 | .AddArgument("id", 123) 234 | .AddArgument("name", "danny") 235 | .AddArgument("price", dict); 236 | 237 | // Assert 238 | Dictionary shouldPass = new() 239 | { 240 | { "id", 123 }, 241 | { "name", "danny" }, 242 | { "price", dict } 243 | }; 244 | Assert.Equal(shouldPass, query.Arguments); 245 | } 246 | 247 | [Fact] 248 | public void TestAddField() 249 | { 250 | Query query = new("car"); 251 | query.AddField(c => c.Name); 252 | 253 | Assert.Equal(new List { nameof(Car.Name) }, query.SelectList); 254 | } 255 | 256 | [Fact] 257 | public void TestAddField_subQuery() 258 | { 259 | Query query = new("car"); 260 | query.AddField(c => c.Color, sq => sq); 261 | 262 | Assert.Equal(nameof(Car.Color), (query.SelectList[0] as IQuery)?.Name); 263 | } 264 | 265 | [Fact] 266 | public void TestAddField_customFormatter() 267 | { 268 | Query query = new("car", options: new QueryOptions 269 | { 270 | Formatter = property => $"__{property.Name.ToLower()}" 271 | }); 272 | query.AddField(c => c.Name); 273 | 274 | Assert.Equal(new List { "__name" }, query.SelectList); 275 | } 276 | 277 | [Fact] 278 | public void TestAddField_subQuery_customFormatter() 279 | { 280 | Query query = new("car", options: new QueryOptions 281 | { 282 | Formatter = property => $"__{property.Name.ToLower()}" 283 | }); 284 | query.AddField(c => c.Color, sq => sq); 285 | 286 | Assert.Equal("__color", (query.SelectList[0] as IQuery)?.Name); 287 | } 288 | 289 | [Fact] 290 | public void TestQuery() 291 | { 292 | IQuery? query = new Query(nameof(Car)) 293 | .AddField(car => car.Name) 294 | .AddField(car => car.Price) 295 | .AddField( 296 | car => car.Color, 297 | sq => sq 298 | .AddField(color => color!.Red) 299 | .AddField(color => color!.Green) 300 | .AddField(color => color!.Blue)); 301 | 302 | Assert.Equal(nameof(Car), query.Name); 303 | Assert.Equal(3, query.SelectList.Count); 304 | Assert.Equal(nameof(Car.Name), query.SelectList[0]); 305 | Assert.Equal(nameof(Car.Price), query.SelectList[1]); 306 | 307 | Assert.Equal(nameof(Car.Color), (query.SelectList[2] as IQuery)?.Name); 308 | List expectedSubSelectList = 309 | [ 310 | nameof(Color.Red), 311 | nameof(Color.Green), 312 | nameof(Color.Blue) 313 | ]; 314 | Assert.Equal(expectedSubSelectList, (query.SelectList[2] as IQuery)?.SelectList); 315 | } 316 | 317 | [Fact] 318 | public void TestQuery_build() 319 | { 320 | IQuery? query = new Query("car") 321 | .AddArguments(new { id = "yk8h4vn0", km = 2100, imported = true, page = new { from = 1, to = 100 } }) 322 | .AddField(car => car.Name) 323 | .AddField(car => car.Price) 324 | .AddField( 325 | car => car.Color, 326 | sq => sq 327 | .AddField(color => color!.Red) 328 | .AddField(color => color!.Green) 329 | .AddField(color => color!.Blue)); 330 | 331 | string result = query.Build(); 332 | 333 | Assert.Equal("car(id:\"yk8h4vn0\",imported:true,km:2100,page:{from:1,to:100}){Name Price Color{Red Green Blue}}", result); 334 | } 335 | 336 | [Fact] 337 | public void TestQueryWithUnion() 338 | { 339 | IQuery? query = new Query("vehicule") 340 | .AddUnion( 341 | union => union 342 | .AddField(car => car.Name) 343 | .AddField(car => car.Price)) 344 | .AddField(vehicule => vehicule.Manufacturer) 345 | .AddUnion( 346 | "BeautifulTruck", 347 | union => union 348 | .AddField(truck => truck.Name) 349 | .AddField( 350 | truck => truck.Color, 351 | sq => sq 352 | .AddField(color => color!.Red) 353 | .AddField(color => color!.Green) 354 | .AddField(color => color!.Blue))); 355 | 356 | string result = query.Build(); 357 | 358 | Assert.Equal("vehicule{... on Car{Name Price} Manufacturer ... on BeautifulTruck{Name Color{Red Green Blue}}}", result); 359 | } 360 | 361 | [Fact] 362 | public void TestSubSelectWithList() 363 | { 364 | IQuery? query = new Query("object") 365 | .AddField(c => c.IEnumerable!, sq => sq) 366 | .AddField(c => c.List!, sq => sq) 367 | .AddField(c => c.IQueryable!, sq => sq) 368 | .AddField(c => c.Array!, sq => sq); 369 | 370 | Assert.Equal(typeof(Query), query.SelectList[0]?.GetType()); 371 | Assert.Equal(typeof(Query), query.SelectList[1]?.GetType()); 372 | Assert.Equal(typeof(Query), query.SelectList[2]?.GetType()); 373 | Assert.Equal(typeof(Query), query.SelectList[3]?.GetType()); 374 | } 375 | 376 | class ObjectWithList 377 | { 378 | public IEnumerable? IEnumerable { get; set; } 379 | public List? List { get; set; } 380 | public IQueryable? IQueryable { get; set; } 381 | public SubObject[]? Array { get; set; } 382 | } 383 | 384 | class SubObject 385 | { 386 | public byte Id { get; set; } 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /tests/GraphQL.Query.Builder.UnitTests/QueryStringBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using GraphQL.Query.Builder.UnitTests.Models; 3 | using Xunit; 4 | 5 | namespace GraphQL.Query.Builder.UnitTests; 6 | 7 | public class QueryStringBuilderTests 8 | { 9 | enum TestEnum 10 | { 11 | ENABLED, 12 | DISABLED, 13 | HAYstack 14 | } 15 | 16 | [Fact] 17 | public void TestFormatQueryParam_null() 18 | { 19 | Assert.Equal("null", new QueryStringBuilder().FormatQueryParam(null)); 20 | } 21 | 22 | [Fact] 23 | public void TestFormatQueryParam_string() 24 | { 25 | string value = "value"; 26 | Assert.Equal("\"value\"", new QueryStringBuilder().FormatQueryParam(value)); 27 | } 28 | 29 | [Fact] 30 | public void TestFormatQueryParam_string_json() 31 | { 32 | string value = "{\"foo\":\"bar\",\"array\":[1,2]}"; 33 | Assert.Equal( 34 | "\"{\\\"foo\\\":\\\"bar\\\",\\\"array\\\":[1,2]}\"", 35 | new QueryStringBuilder().FormatQueryParam(value)); 36 | } 37 | 38 | [Fact] 39 | public void TestFormatQueryParam_char() 40 | { 41 | char value = 'a'; 42 | Assert.Equal("\"a\"", new QueryStringBuilder().FormatQueryParam(value)); 43 | } 44 | 45 | [Fact] 46 | public void TestFormatQueryParam_byte() 47 | { 48 | byte value = 1; 49 | Assert.Equal("1", new QueryStringBuilder().FormatQueryParam(value)); 50 | } 51 | 52 | [Fact] 53 | public void TestFormatQueryParam_sbyte() 54 | { 55 | { 56 | sbyte value = 1; 57 | Assert.Equal("1", new QueryStringBuilder().FormatQueryParam(value)); 58 | } 59 | { 60 | sbyte value = -1; 61 | Assert.Equal("-1", new QueryStringBuilder().FormatQueryParam(value)); 62 | } 63 | } 64 | 65 | [Fact] 66 | public void TestFormatQueryParam_short() 67 | { 68 | { 69 | short value = 1; 70 | Assert.Equal("1", new QueryStringBuilder().FormatQueryParam(value)); 71 | } 72 | { 73 | short value = -1; 74 | Assert.Equal("-1", new QueryStringBuilder().FormatQueryParam(value)); 75 | } 76 | } 77 | 78 | [Fact] 79 | public void TestFormatQueryParam_ushort() 80 | { 81 | ushort value = 1; 82 | Assert.Equal("1", new QueryStringBuilder().FormatQueryParam(value)); 83 | } 84 | 85 | [Fact] 86 | public void TestFormatQueryParam_int() 87 | { 88 | { 89 | int value = 1; 90 | Assert.Equal("1", new QueryStringBuilder().FormatQueryParam(value)); 91 | } 92 | { 93 | int value = -1; 94 | Assert.Equal("-1", new QueryStringBuilder().FormatQueryParam(value)); 95 | } 96 | } 97 | 98 | [Fact] 99 | public void TestFormatQueryParam_uint() 100 | { 101 | uint value = 1; 102 | Assert.Equal("1", new QueryStringBuilder().FormatQueryParam(value)); 103 | } 104 | 105 | [Fact] 106 | public void TestFormatQueryParam_long() 107 | { 108 | { 109 | long value = 1L; 110 | Assert.Equal("1", new QueryStringBuilder().FormatQueryParam(value)); 111 | } 112 | { 113 | long value = -1L; 114 | Assert.Equal("-1", new QueryStringBuilder().FormatQueryParam(value)); 115 | } 116 | } 117 | 118 | [Fact] 119 | public void TestFormatQueryParam_ulong() 120 | { 121 | ulong value = 1L; 122 | Assert.Equal("1", new QueryStringBuilder().FormatQueryParam(value)); 123 | } 124 | 125 | [Fact] 126 | public void TestFormatQueryParam_float() 127 | { 128 | float value = 1.2F; 129 | Assert.Equal("1.2", new QueryStringBuilder().FormatQueryParam(value)); 130 | } 131 | 132 | [Fact] 133 | public void TestFormatQueryParam_double() 134 | { 135 | double value = 1.2D; 136 | Assert.Equal("1.2", new QueryStringBuilder().FormatQueryParam(value)); 137 | } 138 | 139 | [Fact] 140 | public void TestFormatQueryParam_decimal() 141 | { 142 | decimal value = 1.2M; 143 | Assert.Equal("1.2", new QueryStringBuilder().FormatQueryParam(value)); 144 | } 145 | 146 | [Fact] 147 | public void TestFormatQueryParam_boolean() 148 | { 149 | { 150 | bool value = true; 151 | Assert.Equal("true", new QueryStringBuilder().FormatQueryParam(value)); 152 | } 153 | { 154 | bool value = false; 155 | Assert.Equal("false", new QueryStringBuilder().FormatQueryParam(value)); 156 | } 157 | } 158 | 159 | [Fact] 160 | public void TestFormatQueryParam_enum() 161 | { 162 | TestEnum value = TestEnum.DISABLED; 163 | Assert.Equal("DISABLED", new QueryStringBuilder().FormatQueryParam(value)); 164 | } 165 | 166 | [Fact] 167 | public void TestFormatQueryParam_date() 168 | { 169 | DateTime value = new(2022, 3, 30, 0, 0, 0, DateTimeKind.Utc); 170 | Assert.Equal("\"2022-03-30T00:00:00.0000000Z\"", new QueryStringBuilder().FormatQueryParam(value)); 171 | } 172 | 173 | [Fact] 174 | public void TestFormatQueryParam_keyvaluepair() 175 | { 176 | KeyValuePair value = new("from", 444.45); 177 | Assert.Equal("from:444.45", new QueryStringBuilder().FormatQueryParam(value)); 178 | } 179 | 180 | [Fact] 181 | public void TestFormatQueryParam_dictionary() 182 | { 183 | Dictionary value = new() 184 | { 185 | { "from", 444.45 }, 186 | { "to", 555.45 } 187 | }; 188 | Assert.Equal("{from:444.45,to:555.45}", new QueryStringBuilder().FormatQueryParam(value)); 189 | } 190 | 191 | [Fact] 192 | public void TestFormatQueryParam_listNumber() 193 | { 194 | { 195 | List value = [43783, 43784, 43145]; 196 | Assert.Equal("[43783,43784,43145]", new QueryStringBuilder().FormatQueryParam(value)); 197 | } 198 | { 199 | int[] value = [43783, 43784, 43145]; 200 | Assert.Equal("[43783,43784,43145]", new QueryStringBuilder().FormatQueryParam(value)); 201 | } 202 | { 203 | double[] value = [43.783, 43.784, 43.145]; 204 | Assert.Equal("[43.783,43.784,43.145]", new QueryStringBuilder().FormatQueryParam(value)); 205 | } 206 | } 207 | 208 | [Fact] 209 | public void TestFormatQueryParam_listString() 210 | { 211 | { 212 | List value = ["a", "b", "c"]; 213 | Assert.Equal("[\"a\",\"b\",\"c\"]", new QueryStringBuilder().FormatQueryParam(value)); 214 | } 215 | { 216 | string[] value = ["a", "b", "c"]; 217 | Assert.Equal("[\"a\",\"b\",\"c\"]", new QueryStringBuilder().FormatQueryParam(value)); 218 | } 219 | } 220 | 221 | [Fact] 222 | public void TestFormatQueryParam_listEnum() 223 | { 224 | { 225 | List value = [TestEnum.ENABLED, TestEnum.DISABLED, TestEnum.HAYstack]; 226 | Assert.Equal("[ENABLED,DISABLED,HAYstack]", new QueryStringBuilder().FormatQueryParam(value)); 227 | } 228 | { 229 | TestEnum[] value = [TestEnum.ENABLED, TestEnum.DISABLED, TestEnum.HAYstack]; 230 | Assert.Equal("[ENABLED,DISABLED,HAYstack]", new QueryStringBuilder().FormatQueryParam(value)); 231 | } 232 | } 233 | 234 | [Fact] 235 | public void TestFormatQueryParam_Anonymous() 236 | { 237 | var anonymous = new 238 | { 239 | Name = "Test", 240 | Age = 10, 241 | Addresses = new List 242 | { 243 | new 244 | { 245 | Street = "Street", 246 | Number = 123, 247 | }, 248 | new 249 | { 250 | Street = "Street 2", 251 | Number = 123, 252 | } 253 | }, 254 | Orders = new 255 | { 256 | Product = "Product 1", 257 | Price = 123 258 | } 259 | }; 260 | 261 | Assert.Equal( 262 | "{Addresses:[{Number:123,Street:\"Street\"},{Number:123,Street:\"Street 2\"}],Age:10,Name:\"Test\",Orders:{Price:123,Product:\"Product 1\"}}", 263 | new QueryStringBuilder().FormatQueryParam(anonymous)); 264 | } 265 | 266 | [Fact] 267 | public void TestFormatQueryParam_Object() 268 | { 269 | Customer @object = new() 270 | { 271 | Name = "Test", 272 | Age = 10, 273 | Orders = 274 | [ 275 | new() 276 | { 277 | Product = new Car 278 | { 279 | Name = "Bee", 280 | Price = 10000, 281 | Color = new Color 282 | { 283 | Red = 45, 284 | Green = 12, 285 | Blue = 83 286 | } 287 | } 288 | } 289 | ] 290 | }; 291 | 292 | Assert.Equal( 293 | "{Age:10,Name:\"Test\",Orders:[{Product:{Color:{Blue:83,Green:12,Red:45},Name:\"Bee\",Price:10000}}]}", 294 | new QueryStringBuilder().FormatQueryParam(@object)); 295 | 296 | // with inner object with null property 297 | @object = new() 298 | { 299 | Name = "Test", 300 | Age = 10, 301 | Orders = 302 | [ 303 | new() 304 | { 305 | Product = new Car 306 | { 307 | Name = "Bee", 308 | Price = 10000, 309 | Color = null 310 | } 311 | } 312 | ] 313 | }; 314 | 315 | Assert.Equal( 316 | "{Age:10,Name:\"Test\",Orders:[{Product:{Name:\"Bee\",Price:10000}}]}", 317 | new QueryStringBuilder().FormatQueryParam(@object)); 318 | } 319 | 320 | [Fact] 321 | public void BuildQueryParam_NestedListType_ParseNestedList() 322 | { 323 | List objList = ["aa", "bb", "cc"]; 324 | Dictionary fromToMap = new() 325 | { 326 | { "from", 444.45 }, 327 | { "to", 555.45 }, 328 | }; 329 | Dictionary nestedListMap = new() 330 | { 331 | { "from", 123 }, 332 | { "to", 454 }, 333 | { "recurse", objList }, 334 | { "map", fromToMap }, 335 | { "name", TestEnum.HAYstack } 336 | }; 337 | 338 | Assert.Equal( 339 | "{from:123,to:454,recurse:[\"aa\",\"bb\",\"cc\"],map:{from:444.45,to:555.45},name:HAYstack}", 340 | new QueryStringBuilder().FormatQueryParam(nestedListMap)); 341 | } 342 | 343 | [Fact] 344 | public void Where_QueryString_ParseQueryString() 345 | { 346 | List objList = ["aa", "bb", "cc"]; 347 | Dictionary fromToMap = new() 348 | { 349 | { "from", 444.45 }, 350 | { "to", 555.45 }, 351 | }; 352 | Dictionary nestedListMap = new() 353 | { 354 | { "from", 123 }, 355 | { "to", 454 }, 356 | { "recurse", objList }, 357 | { "map", fromToMap }, 358 | { "name", TestEnum.HAYstack } 359 | }; 360 | IQuery query = new Query("test1") 361 | .AddField("name") 362 | .AddArguments(nestedListMap); 363 | 364 | QueryStringBuilder queryString = new(); 365 | queryString.AddParams(query); 366 | 367 | Assert.Equal( 368 | "from:123,to:454,recurse:[\"aa\",\"bb\",\"cc\"],map:{from:444.45,to:555.45},name:HAYstack", 369 | queryString.QueryString.ToString()); 370 | } 371 | 372 | [Fact] 373 | public void Where_ClearQueryString_EmptyQueryString() 374 | { 375 | List objList = ["aa", "bb", "cc"]; 376 | Dictionary fromToMap = new() 377 | { 378 | { "from", 444.45 }, 379 | { "to", 555.45 }, 380 | }; 381 | Dictionary nestedListMap = new() 382 | { 383 | { "from", 123 }, 384 | { "to", 454 }, 385 | { "recurse", objList }, 386 | { "map", fromToMap }, 387 | { "name", TestEnum.HAYstack } 388 | }; 389 | IQuery query = new Query("test1") 390 | .AddField("name") 391 | .AddArguments(nestedListMap); 392 | 393 | QueryStringBuilder queryString = new(); 394 | queryString.AddParams(query); 395 | queryString.QueryString.Clear(); 396 | 397 | Assert.True(string.IsNullOrEmpty(queryString.QueryString.ToString())); 398 | } 399 | 400 | [Fact] 401 | public void Select_QueryString_ParseQueryString() 402 | { 403 | Dictionary mySubDict = new() 404 | { 405 | { "subMake", "aston martin" }, 406 | { "subState", "ca" }, 407 | { "subLimit", 1 }, 408 | { "__debug", TestEnum.DISABLED }, 409 | { "SuperQuerySpeed", TestEnum.ENABLED } 410 | }; 411 | IQuery query = new Query("test1") 412 | .AddField("more") 413 | .AddField("things") 414 | .AddField("in_a_select") 415 | .AddField("subSelect", q => q 416 | .AddField("subName") 417 | .AddField("subMake") 418 | .AddField("subModel") 419 | .AddArguments(mySubDict)); 420 | 421 | QueryStringBuilder builder = new(); 422 | builder.AddFields(query); 423 | 424 | Assert.Equal( 425 | "more things in_a_select subSelect(subMake:\"aston martin\",subState:\"ca\",subLimit:1,__debug:DISABLED,SuperQuerySpeed:ENABLED){subName subMake subModel}", 426 | builder.QueryString.ToString()); 427 | } 428 | 429 | [Fact] 430 | public void Build_AllElements_StringMatch() 431 | { 432 | Dictionary mySubDict = new() 433 | { 434 | { "subMake", "aston martin" }, 435 | { "subState", "ca" }, 436 | { "subLimit", 1 }, 437 | { "__debug", TestEnum.DISABLED }, 438 | { "SuperQuerySpeed", TestEnum.ENABLED } 439 | }; 440 | IQuery query = new Query("test1") 441 | .Alias("test1Alias") 442 | .AddField("more") 443 | .AddField("things") 444 | .AddField("in_a_select") 445 | .AddField("subSelect", q => q 446 | .AddField("subName") 447 | .AddField("subMake") 448 | .AddField("subModel") 449 | .AddArguments(mySubDict)); 450 | 451 | Assert.Equal( 452 | "test1Alias:test1{more things in_a_select subSelect(subMake:\"aston martin\",subState:\"ca\",subLimit:1,__debug:DISABLED,SuperQuerySpeed:ENABLED){subName subMake subModel}}", 453 | new QueryStringBuilder().Build(query)); 454 | } 455 | 456 | [Fact] 457 | public void QueryWithoutField() 458 | { 459 | Query query = new("test"); 460 | 461 | Assert.Equal("test", new QueryStringBuilder().Build(query)); 462 | } 463 | 464 | [Fact] 465 | public void QueryWithFormatter() 466 | { 467 | static string formatter(PropertyInfo property) => $"FIELD_{property.Name}"; 468 | 469 | QueryStringBuilder builder = new(formatter); 470 | string param = builder.FormatQueryParam(new { Id = "urv7fe53", Name = "Bob" }); 471 | 472 | Assert.Equal("{FIELD_Id:\"urv7fe53\",FIELD_Name:\"Bob\"}", param); 473 | } 474 | } 475 | -------------------------------------------------------------------------------- /tests/GraphQL.Query.Builder.UnitTests/RequiredArgumentTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace GraphQL.Query.Builder.UnitTests; 4 | 5 | public class RequiredArgumentTests 6 | 7 | { 8 | [Fact] 9 | public void NotNull_ShouldContinue() 10 | { 11 | string str = "foo"; 12 | RequiredArgument.NotNull(str, nameof(str)); 13 | Assert.True(true); 14 | } 15 | 16 | [Fact] 17 | public void NotNull_ShouldThrowArgumentNullException() 18 | { 19 | string? str = null; 20 | ArgumentNullException exception = Assert.Throws(() => RequiredArgument.NotNull(str, nameof(str))); 21 | Assert.Equal($"Value cannot be null. (Parameter '{nameof(str)}')", exception.Message); 22 | } 23 | 24 | [Fact] 25 | public void NotNull_ShouldThrowArgumentNullExceptionWithNoParamName() 26 | { 27 | string? str = null; 28 | #nullable disable 29 | ArgumentNullException exception = Assert.Throws(() => RequiredArgument.NotNull(str, null)); 30 | #nullable restore 31 | Assert.Equal("Value cannot be null.", exception.Message); 32 | } 33 | 34 | [Fact] 35 | public void NotNullOrEmpty_ShouldThrowArgumentNullException() 36 | { 37 | string? str = null; 38 | #nullable disable 39 | ArgumentNullException exception = Assert.Throws(() => RequiredArgument.NotNullOrEmpty(str, nameof(str))); 40 | #nullable restore 41 | Assert.Equal($"Value cannot be null. (Parameter '{nameof(str)}')", exception.Message); 42 | } 43 | 44 | [Fact] 45 | public void NotNullOrEmpty_ShouldThrowArgumentNullExceptionWithNoParamName() 46 | { 47 | string? str = null; 48 | #nullable disable 49 | ArgumentNullException exception = Assert.Throws(() => RequiredArgument.NotNullOrEmpty(str, null)); 50 | #nullable restore 51 | Assert.Equal("Value cannot be null.", exception.Message); 52 | } 53 | 54 | [Fact] 55 | public void NotNullOrEmpty_ShouldThrowArgumentException() 56 | { 57 | string str = ""; 58 | ArgumentException exception = Assert.Throws(() => RequiredArgument.NotNullOrEmpty(str, nameof(str))); 59 | Assert.Equal($"Value cannot be empty. (Parameter '{nameof(str)}')", exception.Message); 60 | } 61 | 62 | [Fact] 63 | public void NotNullOrEmpty_ShouldThrowArgumentExceptionWithNoParamName() 64 | { 65 | string str = ""; 66 | #nullable disable 67 | ArgumentException exception = Assert.Throws(() => RequiredArgument.NotNullOrEmpty(str, null)); 68 | #nullable restore 69 | Assert.Equal("Value cannot be empty.", exception.Message); 70 | } 71 | } 72 | --------------------------------------------------------------------------------