Skip to content

Commit 285b7a2

Browse files
committed
added support for named fragments which now produces interfaces in c#
1 parent 754d5f0 commit 285b7a2

12 files changed

Lines changed: 265 additions & 29 deletions

File tree

Tocsoft.GraphQLCodeGen.Cli/Models/ViewModel.cs

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public class ViewModel
1919
private List<EnumViewModel> enumCollection = new List<EnumViewModel>();
2020
private List<OperationViewModel> operationCollection;
2121

22+
private Dictionary<string, string> fragmentQueries = new Dictionary<string, string>();
23+
2224
public IEnumerable<TypeViewModel> Types => this.typeCollection;
2325
public IEnumerable<EnumViewModel> Enums => this.enumCollection;
2426
public IEnumerable<OperationViewModel> Operations => this.operationCollection;
@@ -53,7 +55,7 @@ internal ViewModel(GraphQLDocument query, CodeGeneratorSettings settings)
5355
{
5456
Name = opName,
5557
Arguments = argCollection,
56-
Query = op.Query,
58+
QueryFragment = op.Query,
5759
NamedQuery = op.Name ?? string.Empty,
5860
ResultType = new TypeReferenceModel
5961
{
@@ -63,13 +65,44 @@ internal ViewModel(GraphQLDocument query, CodeGeneratorSettings settings)
6365
TypeName = GenerateType(op.Selection, opName)
6466
}
6567
};
66-
68+
// collect all fragments reference from this query and grab thier nodes query strings too
69+
opVM.Query = FullQuery(opVM);
6770
this.operationCollection.Add(opVM);
6871
}
72+
}
73+
private string FullQuery(OperationViewModel opVM)
74+
{
75+
StringBuilder sb = new StringBuilder();
76+
sb.AppendLine(opVM.QueryFragment);
77+
var allTypes = ReferencedTypeList(opVM.ResultType.TypeName).Distinct();
78+
foreach (var t in allTypes)
79+
{
80+
if (this.fragmentQueries.ContainsKey(t))
81+
{
82+
sb.AppendLine(this.fragmentQueries[t]);
83+
}
84+
}
85+
return sb.ToString();
6986

70-
// lets convert each to a type with a back track to lookup a type based on unique reference
71-
72-
// I need type definitions for Operation Result and Argument types graphs
87+
IEnumerable<string> ReferencedTypeList(string typeName)
88+
{
89+
var types = this.typeCollection.Where(x => x.Name == typeName);
90+
foreach (var t in types)
91+
{
92+
foreach (var f in t.Interfaces)
93+
{
94+
yield return f;
95+
}
96+
foreach (var f in t.Fields)
97+
{
98+
var childTypes = ReferencedTypeList(f.Type.TypeName);
99+
foreach (var ct in childTypes)
100+
{
101+
yield return ct;
102+
}
103+
}
104+
}
105+
}
73106
}
74107

75108
private TypeReferenceModel GenerateTypeReference(FieldSelection field)
@@ -97,19 +130,28 @@ private string GenerateType(SetSelection selection, string operationName = null)
97130
return this.typeLookup[$"{operationName}_{selection.UniqueIdentifier}"]?.Name;
98131
}
99132

100-
string name = FindBestName(operationName ?? selection.RootType.Name, "Result");
133+
string name = FindBestName((operationName ?? selection.RootType.Name), "Result");
134+
var lookupName = $"{operationName}_{selection.UniqueIdentifier}";
101135

136+
TypeViewModel type = BuildTypeViewModel(selection, name, lookupName);
137+
138+
return type.Name;
139+
}
140+
141+
private TypeViewModel BuildTypeViewModel(SetSelection selection, string name, string lookupName)
142+
{
102143
TypeViewModel type = new TypeViewModel(name);
103144

104-
this.typeLookup.Add($"{operationName}_{selection.UniqueIdentifier}", type);
145+
this.typeLookup.Add(lookupName, type);
105146
this.typeCollection.Add(type);
106147
type.Fields = selection.Fields.Select(x => new NamedTypeViewModel()
107148
{
108149
Name = x.Name,
109150
Type = GenerateTypeReference(x)
110151
}).ToList();
111152

112-
return type.Name;
153+
type.Interfaces = selection.Fragments.Select(x => GenerateType(x)).ToList();
154+
return type;
113155
}
114156

115157
private TypeReferenceModel GenerateTypeReference(ObjectModel.ValueTypeReference type)
@@ -179,6 +221,17 @@ private string GenerateType(IGraphQLType type)
179221
this.enumCollection.Add(typeVm);
180222
return typeVm.Name;
181223
}
224+
else if (type is FragmentType fragment)
225+
{
226+
227+
TypeViewModel typeVm = BuildTypeViewModel(fragment.Selection, name, $"_{fragment.Selection}_[fragment]");
228+
typeVm.IsInterface = true;
229+
230+
fragmentQueries.Add(typeVm.Name, fragment.Query);
231+
this.inputTypeLookup.Add(type, typeVm.Name);
232+
233+
return typeVm.Name;
234+
}
182235
else
183236
{
184237
throw new Exception("unkown type");
@@ -206,6 +259,7 @@ public class OperationViewModel
206259
public TypeReferenceModel ResultType { get; internal set; }
207260
public List<NamedTypeViewModel> Arguments { get; internal set; }
208261
public string Query { get; internal set; }
262+
public string QueryFragment { get; internal set; }
209263
public string NamedQuery { get; internal set; }
210264

211265
public OperationViewModel()
@@ -249,7 +303,12 @@ public TypeViewModel(string name)
249303

250304
// a type is a list of fields and and unique name
251305
public string Name { get; set; }
306+
307+
public bool IsInterface { get; set; }
308+
252309
public IEnumerable<NamedTypeViewModel> Fields { get; set; }
310+
311+
public IEnumerable<string> Interfaces { get; set; }
253312
}
254313

255314

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using GraphQLParser.AST;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Diagnostics;
5+
using System.Linq;
6+
using System.Text;
7+
using Tocsoft.GraphQLCodeGen.ObjectModel.Selections;
8+
9+
namespace Tocsoft.GraphQLCodeGen.ObjectModel
10+
{
11+
[DebuggerDisplay(@"... \{{Name}\}")]
12+
internal class FragmentType : IGraphQLType, IGraphQLInitter
13+
{
14+
private readonly GraphQLFragmentDefinition definition;
15+
private GraphQLSelectionSet selectionSet;
16+
private IGraphQLFieldCollection rootType;
17+
18+
public string Name { get; set; }
19+
20+
public SetSelection Selection { get; set; }
21+
22+
public string Query { get; private set; }
23+
24+
public string Path { get; private set; }
25+
26+
public FragmentType(GraphQLFragmentDefinition definition)
27+
{
28+
this.definition = definition;
29+
this.Name = definition.Name?.Value;
30+
this.Selection = new SetSelection(this.definition);
31+
}
32+
33+
bool resolved = false;
34+
public void Resolve(GraphQLDocument doc)
35+
{
36+
IGraphQLFieldCollection rootType = doc.ResolveType(this.definition.TypeCondition) as IGraphQLFieldCollection;
37+
this.Selection.Resolve(doc, rootType);
38+
(this.Query, this.Path) = doc.ResolveQuery(this.definition.Location);
39+
}
40+
}
41+
}

Tocsoft.GraphQLCodeGen.Cli/ObjectModel/GraphQLDocument.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ internal ValueTypeReference ResolveValueType(GraphQLType type)
144144
(LocatedNamedSource part, int offsetStart, int length) = ResolveNode(location);
145145

146146
string text = part.Body.Substring(offsetStart, length);
147+
// operations seems to include the start of the next token in the end of their location
148+
// back track to the closing brace and take upto there
149+
text = text.Substring(0, text.LastIndexOf('}')+1);
147150

148151
return (text, part.Path);
149152
}
@@ -250,6 +253,8 @@ private object Visit(GraphQLParser.AST.ASTNode node)
250253
return new UnionType(op);
251254
case GraphQLInputObjectTypeDefinition op:
252255
return new ObjectType(op);
256+
case GraphQLFragmentDefinition op:
257+
return new FragmentType(op);
253258
default:
254259
return null;
255260
}

Tocsoft.GraphQLCodeGen.Cli/ObjectModel/ObjectType.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ internal class ObjectType : IGraphQLType, IGraphQLInitter, IGraphQLFieldCollecti
1313
{
1414
private readonly GraphQLObjectTypeDefinition definition;
1515
private readonly GraphQLInputObjectTypeDefinition definitionInput;
16-
private GraphQLSelectionSet selectionSet;
17-
private IGraphQLFieldCollection rootType;
1816

1917
public string Name { get; set; }
2018

@@ -32,8 +30,6 @@ public ObjectType(GraphQLInputObjectTypeDefinition definition)
3230
this.definitionInput = definition;
3331
this.Name = definition.Name?.Value;
3432
}
35-
36-
3733

3834
public void Resolve(GraphQLDocument doc)
3935
{

Tocsoft.GraphQLCodeGen.Cli/ObjectModel/Selections/SetSelection.cs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,30 @@ internal class SetSelection
1414
public IGraphQLType RootType { get; set; }
1515

1616
public string UniqueIdentifier { get; set; }
17-
public IEnumerable<FieldSelection> Fields { get; set; }
18-
public IEnumerable<SetSelection> Fragments { get; set; }
17+
public IEnumerable<FieldSelection> Fields => fields;
1918

19+
/// <summary>
20+
/// this makes up the list of named interfaces that we will be applying
21+
/// </summary>
22+
public IEnumerable<FragmentType> Fragments => fragmentsItems;
23+
24+
private List<FragmentType> fragmentsItems = new List<FragmentType>();
25+
private List<GraphQLName> fragmentNames;
26+
private List<SetSelection> inlineFragments = new List<SetSelection>();
27+
private List<SetSelection> fragments = new List<SetSelection>();
28+
private List<FieldSelection> fields = new List<FieldSelection>();
29+
2030
public SetSelection(GraphQLSelectionSet op)
2131
{
2232
this.op = op;
2333
List<object> nodes = op.Selections.Select(this.Visit).ToList();
24-
this.Fields = nodes.OfType<FieldSelection>().ToList();
25-
this.Fragments = nodes.OfType<SetSelection>().ToList();
34+
this.fields.AddRange(nodes.OfType<FieldSelection>());
35+
this.inlineFragments.AddRange(nodes.OfType<SetSelection>());
36+
this.fragmentNames = nodes.OfType<GraphQLName>().ToList();
2637
}
2738

2839
public SetSelection(GraphQLInlineFragment op)
29-
:this(op.SelectionSet)
40+
: this(op.SelectionSet)
3041
{
3142
this.TypeCondition = op.TypeCondition;
3243
}
@@ -39,6 +50,8 @@ private object Visit(GraphQLParser.AST.ASTNode node)
3950
return new FieldSelection(op);
4051
case GraphQLInlineFragment op:
4152
return new SetSelection(op);
53+
case GraphQLFragmentSpread op:
54+
return op.Name;
4255
default:
4356
return node;
4457
}
@@ -47,18 +60,36 @@ private object Visit(GraphQLParser.AST.ASTNode node)
4760
internal void Resolve(GraphQLDocument doc, IGraphQLType rootType)
4861
{
4962
this.RootType = rootType;
63+
5064
foreach (FieldSelection f in this.Fields)
5165
{
5266
f.Resolve(doc, rootType as IGraphQLFieldCollection);
5367
}
5468

55-
foreach (SetSelection f in this.Fragments)
69+
foreach (SetSelection f in this.inlineFragments)
5670
{
5771
IGraphQLType root = doc.ResolveType(f.TypeCondition);
5872
f.Resolve(doc, root);
73+
this.fragments.Add(f);
74+
}
75+
76+
// do these after the inline fragments as they have been pre-resolved
77+
foreach (GraphQLName name in this.fragmentNames)
78+
{
79+
var fragmentRoot = doc.ResolveType(name.Value) as FragmentType;
80+
this.fragmentsItems.Add(fragmentRoot);
81+
this.fragments.Add(fragmentRoot.Selection);
82+
}
83+
84+
// merge fields from fragments into root object
85+
foreach (SetSelection f in this.fragments)
86+
{
87+
var currentFieldRefs = fields.Select(x => x.UniqueIdentifier);
88+
var newFields = f.Fields.Where(x => !currentFieldRefs.Contains(x.UniqueIdentifier));
89+
fields.AddRange(newFields);
5990
}
6091

61-
this.UniqueIdentifier = $"{this.TypeCondition?.Name?.Value}_{rootType?.Name}_{string.Join("|", this.Fields.Select(x => x.UniqueIdentifier))}_{string.Join("|", this.Fragments.Select(x => x.UniqueIdentifier))}";
92+
this.UniqueIdentifier = $"{this.TypeCondition?.Name?.Value}_{rootType?.Name}_{string.Join("|", this.Fields.Select(x => x.UniqueIdentifier))}_{string.Join("|", this.fragments.Select(x => x.UniqueIdentifier))}";
6293
}
6394
}
6495
}

Tocsoft.GraphQLCodeGen.Cli/Templates/cs/CSharp.template

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,7 @@ namespace {{Namespace}}
6262
}
6363

6464
{{#each Types}}
65-
{{> Type}}
66-
65+
{{#if IsInterface}}{{> Interface}}{{else}}{{> Type}}{{/if}}
6766

6867
{{/each}}
6968

@@ -78,13 +77,21 @@ namespace {{Namespace}}
7877
{{~/if~}}
7978

8079
{{!# Type}}
81-
public sealed class {{pascalCase Name}}
80+
public sealed class {{pascalCase Name}} {{#each~ Interfaces}}{{~#if @first}}: {{else}}, {{/if~}}I{{pascalCase .}}{{~/each}}
8281
{
8382
{{#each Fields}}
8483
public {{> TypeReference Type}} {{pascalCase Name}} { get; set; }
8584
{{/each}}
8685
}
8786

87+
{{!# Interface}}
88+
public interface I{{pascalCase Name}}
89+
{
90+
{{#each Fields}}
91+
{{> TypeReference Type}} {{pascalCase Name}} { get; set; }
92+
{{/each}}
93+
}
94+
8895
{{!# Enum}}
8996
public enum {{pascalCase Name}}
9097
{

Tocsoft.GraphQLCodeGen.Cli/Tocsoft.GraphQLCodeGen.Cli.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
<ItemGroup>
1515
<PackageReference Include="Glob" Version="0.4.0" />
1616
<PackageReference Include="GraphQL-Parser" Version="3.0.0" />
17-
<PackageReference Include="Handlebars.Net" Version="1.9.0" />
18-
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="2.0.0" />
19-
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
17+
<PackageReference Include="Handlebars.Net" Version="1.9.4" />
18+
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="2.1.0" />
19+
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
2020
<PackageReference Include="System.Net.Http" Version="4.3.3" />
2121
<PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" />
2222
</ItemGroup>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
schema
2+
{
3+
query: Query
4+
}
5+
6+
type User {
7+
id: ID!
8+
username: String
9+
first_name: String
10+
last_name: String
11+
full_name: String
12+
name: String @deprecated
13+
}
14+
15+
type Query {
16+
User(id: ID!): User
17+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#! schema:schema.gql
2+
#! output: Client.cs
3+
#! class: Sample.Client.GitHub.GitHubClient
4+
5+
query ($login:ID!){
6+
User(id: $login){
7+
...user
8+
}
9+
}
10+
11+
fragment user on User {
12+
id,
13+
username
14+
}

0 commit comments

Comments
 (0)