Skip to content

Commit e2755ec

Browse files
committed
Merge branch 'stringfied-enums-serilizer'
2 parents fd50233 + faaa903 commit e2755ec

File tree

8 files changed

+419
-38
lines changed

8 files changed

+419
-38
lines changed

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

+30-2
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ namespace {{Namespace}}
8383
{{ > jsonConverters }}
8484
}
8585
};
86-
86+
8787
private string SerializeBody(GraphQLRequest request)
8888
{
8989
return JsonConvert.SerializeObject(request, jsonSettings);
@@ -153,7 +153,7 @@ namespace {{Namespace}}
153153
public sealed class {{pascalCase Name}} {{#each~ Interfaces}}{{~#if @first}}: {{else}}, {{/if~}}I{{pascalCase .}}{{~/each}}
154154
{
155155
{{#each Fields}}
156-
[JsonProperty("{{Name}}")]
156+
[JsonProperty("{{Name}}")]
157157
public {{> TypeReference Type}} {{pascalCase Name}} { get; set; }
158158
{{/each}}
159159
}
@@ -170,6 +170,7 @@ namespace {{Namespace}}
170170
{{!# Enum}}
171171

172172
{{ifTemplate 'StringifyEnums' 'true'}}
173+
[JsonConverter(typeof({{pascalCase Name}}.CustomJsonStringifiedEnumConverter))]
173174
public class {{pascalCase Name}}
174175
{
175176
static Dictionary<string, {{pascalCase Name}}> lookup = new Dictionary<string, {{pascalCase Name}}>();
@@ -210,6 +211,33 @@ namespace {{Namespace}}
210211
{{#each Values}}
211212
public static readonly {{pascalCase ../Name}} {{pascalCase .}} = Create("{{.}}", true);
212213
{{/each}}
214+
215+
internal class CustomJsonStringifiedEnumConverter : JsonConverter
216+
{
217+
public override bool CanConvert(Type objectType)
218+
{
219+
return typeof({{pascalCase Name}}).IsAssignableFrom(objectType);
220+
}
221+
222+
public override bool CanRead => false;
223+
224+
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
225+
{
226+
throw new NotImplementedException();
227+
}
228+
229+
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
230+
{
231+
if (value is {{pascalCase Name}} v)
232+
{
233+
writer.WriteValue(v.Value);
234+
}
235+
else
236+
{
237+
writer.WriteNull();
238+
}
239+
}
240+
}
213241
}
214242
{{else}}
215243
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Tocsoft.GraphQLCodeGen.ObjectModel;
7+
using Xunit;
8+
9+
namespace Tocsoft.GraphQLCodeGen.Tests
10+
{
11+
public class CodeGeneratorTester
12+
{
13+
private readonly FakeLogger logger;
14+
private readonly CodeGeneratorSettingsLoader settingsLoader;
15+
private List<string> queries = new List<string>();
16+
private Action<CodeGeneratorSettings> configAction;
17+
private Func<GraphQlQuery, object> httpIntercepter;
18+
private FakeHttpClient httpClient;
19+
20+
public CodeGeneratorTester()
21+
{
22+
23+
this.logger = new FakeLogger();
24+
this.settingsLoader = new CodeGeneratorSettingsLoader(logger);
25+
}
26+
27+
public string GeneratedCode { get; private set; }
28+
internal IEnumerable<GraphQLError> Errors { get; private set; }
29+
30+
public void AddQuery(string queryPath)
31+
{
32+
queries.Add(queryPath);
33+
}
34+
public void Configure(Action<CodeGeneratorSettings> configAction)
35+
{
36+
this.configAction = configAction;
37+
}
38+
public void ConfigureResponse(Func<GraphQlQuery, object> httpIntercepter)
39+
{
40+
this.httpIntercepter = httpIntercepter;
41+
}
42+
43+
private bool generated = false;
44+
45+
public async Task<string> Generate()
46+
{
47+
if (generated) return this.GeneratedCode;
48+
49+
generated = true;
50+
51+
var settings = settingsLoader.GenerateSettings(new CodeGeneratorSettingsLoaderDefaults(), queries).Single();
52+
53+
configAction?.Invoke(settings);
54+
55+
CodeGenerator generator = new CodeGenerator(logger, settings);
56+
57+
await generator.LoadSource();
58+
59+
generator.Parse();
60+
if (!generator.HasParsingErrors)
61+
{
62+
generator.Render();
63+
}
64+
65+
this.GeneratedCode = generator.GeneratedCode;
66+
67+
this.Errors = generator.Document.Errors;
68+
69+
return this.GeneratedCode;
70+
}
71+
72+
public async Task Verify()
73+
{
74+
await Generate();
75+
76+
Assert.Empty(Errors);
77+
78+
if (this.httpClient != null)
79+
{
80+
this.httpClient.VerifyAll();
81+
}
82+
}
83+
public async Task<GraphQlQuery> ExecuteClient(string clientName, string code)
84+
{
85+
var generatedCode = await Generate();
86+
code = code.Trim();
87+
88+
var finalCode = $@"
89+
using static {clientName};
90+
91+
public class _testClass{{
92+
public static async System.Threading.Tasks.Task Execute({clientName} client){{
93+
await client.{code};
94+
}}
95+
}}
96+
";
97+
this.httpClient = FakeHttpClient.Create();
98+
99+
GraphQlQuery query = null;
100+
this.httpClient.SetupGraphqlRequest((q) =>
101+
{
102+
query = q;
103+
return this.httpIntercepter?.Invoke(q) ?? new { };
104+
});
105+
106+
var assembly = new Compiler().Compile(generatedCode, finalCode);
107+
108+
var clientType = assembly.GetType(clientName);
109+
var client = Activator.CreateInstance(clientType, this.httpClient);
110+
var executeMethod = assembly.GetType("_testClass").GetMethod("Execute");
111+
112+
await (executeMethod.Invoke(null, new[] { client }) as Task);
113+
114+
this.httpClient.VerifyAll();
115+
116+
return query;
117+
}
118+
119+
}
120+
}
+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Runtime.CompilerServices;
7+
using System.Runtime.Loader;
8+
using System.Text;
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
using Microsoft.CodeAnalysis.Text;
12+
using Xunit;
13+
14+
namespace Tocsoft.GraphQLCodeGen.Tests
15+
{
16+
internal class Compiler
17+
{
18+
public Assembly Compile(params string[] sourceCode)
19+
{
20+
var ms = CompileBytes(sourceCode);
21+
22+
if (ms == null)
23+
{
24+
throw new Exception("Failed to compile");
25+
}
26+
27+
return AssemblyLoadContext.Default.LoadFromStream(ms);
28+
}
29+
30+
private MemoryStream CompileBytes(params string[] sourceCode)
31+
{
32+
var peStream = new MemoryStream();
33+
var result = GenerateCode(sourceCode).Emit(peStream);
34+
35+
if (!result.Success)
36+
{
37+
var failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error);
38+
39+
foreach (var diagnostic in failures)
40+
{
41+
Assert.True(false, diagnostic.ToString());
42+
}
43+
44+
return null;
45+
}
46+
47+
peStream.Seek(0, SeekOrigin.Begin);
48+
49+
return peStream;
50+
}
51+
52+
private static CSharpCompilation GenerateCode(string[] sourceCode)
53+
{
54+
var codeStrings = sourceCode.Select(x => SourceText.From(x));
55+
var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest);
56+
57+
var parsedSyntaxTree = codeStrings.Select(x => SyntaxFactory.ParseSyntaxTree(x, options));
58+
var netstandard = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName("netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51"));
59+
var references = new MetadataReference[]
60+
{
61+
MetadataReference.CreateFromFile(netstandard.Location),
62+
MetadataReference.CreateFromFile(netstandard.Location),
63+
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
64+
MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location),
65+
MetadataReference.CreateFromFile(typeof(Uri).Assembly.Location),
66+
MetadataReference.CreateFromFile(typeof(System.Net.Http.HttpClient).Assembly.Location),
67+
MetadataReference.CreateFromFile(typeof(Newtonsoft.Json.JsonWriter).Assembly.Location),
68+
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
69+
MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location),
70+
MetadataReference.CreateFromFile(typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly.Location),
71+
};
72+
73+
return CSharpCompilation.Create($"Hello{Guid.NewGuid()}.dll",
74+
parsedSyntaxTree,
75+
references: references,
76+
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,
77+
optimizationLevel: OptimizationLevel.Release,
78+
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default));
79+
}
80+
}
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using Newtonsoft.Json.Linq;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Net.Http;
5+
using System.Text;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Xunit;
9+
10+
namespace Tocsoft.GraphQLCodeGen.Tests
11+
{
12+
public class GraphQlQuery
13+
{
14+
public string Query { get; set; } = "";
15+
public Dictionary<string, JToken> Variables { get; set; } = new Dictionary<string, JToken>();
16+
}
17+
18+
public static class FakeHttpClientGraphQlExtensions
19+
{
20+
public static void SetupGraphqlRequest<TResult>(this FakeHttpClient @this, Func<GraphQlQuery, TResult> intercepter)
21+
=> @this.Intercept(HttpMethod.Post, null, (request) =>
22+
{
23+
var json = request.Content.ReadAsStringAsync().Result;
24+
var query = Newtonsoft.Json.JsonConvert.DeserializeObject<GraphQlQuery>(json);
25+
var result = intercepter(query);
26+
var responseJson = Newtonsoft.Json.JsonConvert.SerializeObject(result);
27+
var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
28+
response.Content = new StringContent(responseJson, Encoding.UTF8, "application/json");
29+
return response;
30+
});
31+
}
32+
33+
public class FakeHttpClient : System.Net.Http.HttpClient
34+
{
35+
private readonly FakeHttpMessageHandler handler;
36+
37+
public static FakeHttpClient Create()
38+
{
39+
var handler = new FakeHttpMessageHandler();
40+
return new FakeHttpClient(handler);
41+
}
42+
43+
private FakeHttpClient(FakeHttpMessageHandler handler)
44+
: base(handler)
45+
{
46+
this.handler = handler;
47+
this.BaseAddress = new Uri("http://localhost.test/endpoint");
48+
}
49+
50+
public void Post(System.Net.Http.HttpMethod method, Uri uri, Func<System.Net.Http.HttpRequestMessage, System.Net.Http.HttpResponseMessage> intercepter)
51+
=> this.Intercept(HttpMethod.Post, uri, intercepter);
52+
53+
public void Intercept(System.Net.Http.HttpMethod method, Uri uri, Func<System.Net.Http.HttpRequestMessage, System.Net.Http.HttpResponseMessage> intercepter)
54+
{
55+
if (uri == null)
56+
{
57+
uri = this.BaseAddress;
58+
}
59+
60+
if (!uri.IsAbsoluteUri)
61+
{
62+
uri = new Uri(this.BaseAddress, uri);
63+
}
64+
65+
this.handler.Intercept(method, uri, intercepter);
66+
}
67+
internal void Verify(System.Net.Http.HttpMethod method, Uri uri)
68+
=> this.handler.Verify(method, uri);
69+
70+
internal void VerifyAll()
71+
=> this.handler.VerifyAll();
72+
73+
private class FakeHttpMessageHandler : System.Net.Http.HttpMessageHandler
74+
{
75+
private Dictionary<(System.Net.Http.HttpMethod method, Uri uri), Func<System.Net.Http.HttpRequestMessage, System.Net.Http.HttpResponseMessage>> intercepters
76+
= new Dictionary<(System.Net.Http.HttpMethod method, Uri uri), Func<System.Net.Http.HttpRequestMessage, System.Net.Http.HttpResponseMessage>>();
77+
78+
private List<(System.Net.Http.HttpMethod method, Uri uri)> calledIntercepters = new List<(HttpMethod method, Uri uri)>();
79+
80+
internal FakeHttpMessageHandler()
81+
{
82+
}
83+
84+
internal void Intercept(System.Net.Http.HttpMethod method, Uri uri, Func<System.Net.Http.HttpRequestMessage, System.Net.Http.HttpResponseMessage> intercepter)
85+
{
86+
this.intercepters[(method, uri)] = intercepter;
87+
}
88+
89+
internal void Verify(System.Net.Http.HttpMethod method, Uri uri)
90+
{
91+
Assert.Contains((method, uri), calledIntercepters);
92+
}
93+
internal void VerifyAll()
94+
{
95+
Assert.All(intercepters.Keys, a =>
96+
{
97+
Verify(a.method, a.uri);
98+
});
99+
}
100+
101+
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
102+
{
103+
if (this.intercepters.TryGetValue((request.Method, request.RequestUri), out var func))
104+
{
105+
calledIntercepters.Add((request.Method, request.RequestUri));
106+
var resp = func(request);
107+
return Task.FromResult(resp);
108+
}
109+
110+
return Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.NotImplemented));
111+
}
112+
}
113+
}
114+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
mutation q($emp:Episode){
2+
test(id: $emp)
3+
{
4+
nullable,
5+
nonnullable
6+
}
7+
}

Tocsoft.GraphQLCodeGen.Tests/Files/StringifiedEnums/schema.gql

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ type Query {
88
test(id: string!): Droid
99
}
1010

11+
type Mutation {
12+
test(id: Episode!): Droid
13+
}
14+
1115
type Droid {
1216
nullable: Episode
1317
nonnullable: Episode!

0 commit comments

Comments
 (0)