1
- using Microsoft . CodeAnalysis ;
1
+ using System . Runtime . Serialization ;
2
+
3
+ using Microsoft . CodeAnalysis ;
2
4
using System . Text ;
3
5
using System . Text . Json ;
4
6
7
+ using Microsoft . CodeAnalysis . CSharp ;
8
+
5
9
namespace Tanka . GraphQL . Server . SourceGenerators ;
6
10
7
- public class InputTypeEmitter
11
+ public class InputTypeEmitter ( SourceProductionContext context )
8
12
{
9
- public const string ObjectTypeTemplate = """
13
+ public const string InputObjectTypeTemplate =
14
+ """
15
+ /// <auto-generated/>
16
+ #nullable enable
10
17
using System;
11
18
using System.Threading.Tasks;
12
19
using Microsoft.Extensions.Options;
@@ -31,28 +38,101 @@ public static class {{name}}InputTypeExtensions
31
38
}
32
39
}
33
40
41
+ {{parseableImplementation}}
42
+ #nullable restore
43
+ """ ;
44
+
45
+ public static string ParseMethodTemplate ( string name , string parseMethod ) =>
46
+ $$ """
47
+ public partial class {{ name }} : IParseableInputObject
48
+ {
49
+ public void Parse(IReadOnlyDictionary<string, object?> argumentValue)
50
+ {
51
+ {{ parseMethod }}
52
+ }
53
+ }
34
54
""" ;
35
55
36
- public SourceProductionContext Context { get ; }
56
+ public static string TrySetProperty ( string fieldName , string name , string type ) =>
57
+ $$ """
58
+ // {{ name }} is an scalar type
59
+ if (argumentValue.TryGetValue("{{ fieldName }} ", out var {{ fieldName }} Value))
60
+ {
61
+ if ({{ fieldName }} Value is null)
62
+ {
63
+ {{ name }} = default;
64
+ }
65
+ else
66
+ {
67
+ {{ name }} = ({{ type }} ){{ fieldName }} Value;
68
+ }
69
+ }
70
+ """ ;
37
71
38
- public InputTypeEmitter ( SourceProductionContext context )
39
- {
40
- Context = context ;
41
- }
72
+ public static string TrySetPropertyObjectValue ( string fieldName , string name , string type ) =>
73
+ $$ """
74
+ // {{ name }} is an input object type
75
+ if (argumentValue.TryGetValue("{{ fieldName }} ", out var {{ fieldName }} Value))
76
+ {
77
+ if ({{ fieldName }} Value is null)
78
+ {
79
+ {{ name }} = default;
80
+ }
81
+ else
82
+ {
83
+ if ({{ fieldName }} Value is not IReadOnlyDictionary<string, object?> dictionaryValue)
84
+ throw new InvalidOperationException($"{{ fieldName }} is not IReadOnlyDictionary<string, object?>");
85
+
86
+ {{ name }} = new {{ type }} ();
87
+
88
+ if ({{ name }} is not IParseableInputObject parseable)
89
+ throw new InvalidOperationException($"{{ name }} is not IParseableInputObject");
90
+
91
+ parseable.Parse(dictionaryValue);
92
+ }
93
+ }
94
+ """ ;
95
+
96
+ public SourceProductionContext Context { get ; } = context ;
42
97
43
98
public void Emit ( InputTypeDefinition definition )
44
99
{
45
- var typeSDL = BuildTypeSdl ( definition ) ;
46
-
100
+ var typeSdl = BuildTypeSdl ( definition ) ;
47
101
var builder = new StringBuilder ( ) ;
48
102
string ns = string . IsNullOrEmpty ( definition . Namespace ) ? "" : $ "{ definition . Namespace } ";
49
- builder . AppendLine ( ObjectTypeTemplate
103
+ builder . AppendLine ( InputObjectTypeTemplate
50
104
. Replace ( "{{namespace}}" , string . IsNullOrEmpty ( ns ) ? "" : $ "namespace { ns } ;")
51
105
. Replace ( "{{name}}" , definition . TargetType )
52
- . Replace ( "{{typeSDL}}" , typeSDL )
106
+ . Replace ( "{{typeSDL}}" , typeSdl )
107
+ . Replace ( "{{parseableImplementation}}" , BuildParseMethod ( definition ) )
53
108
) ;
54
109
55
- Context . AddSource ( $ "{ ns } { definition . TargetType } InputType.g.cs", builder . ToString ( ) ) ;
110
+ var sourceText = CSharpSyntaxTree . ParseText ( builder . ToString ( ) )
111
+ . GetRoot ( )
112
+ . NormalizeWhitespace ( )
113
+ . ToFullString ( ) ;
114
+
115
+ Context . AddSource ( $ "{ ns } { definition . TargetType } InputType.g.cs", sourceText ) ;
116
+ }
117
+
118
+ private string BuildParseMethod ( InputTypeDefinition definition )
119
+ {
120
+ var builder = new IndentedStringBuilder ( ) ;
121
+ foreach ( ObjectPropertyDefinition property in definition . Properties )
122
+ {
123
+ var typeName = property . ReturnType . Replace ( "?" , "" ) ;
124
+ var fieldName = JsonNamingPolicy . CamelCase . ConvertName ( property . Name ) ;
125
+ if ( property . ReturnTypeObject is not null )
126
+ {
127
+ builder . AppendLine ( TrySetPropertyObjectValue ( fieldName , property . Name , typeName ) ) ;
128
+ }
129
+ else
130
+ {
131
+ builder . AppendLine ( TrySetProperty ( fieldName , property . Name , typeName ) ) ;
132
+ }
133
+ }
134
+
135
+ return ParseMethodTemplate ( definition . TargetType , builder . ToString ( ) ) ;
56
136
}
57
137
58
138
private string BuildTypeSdl ( InputTypeDefinition definition )
0 commit comments