ruby-graphql-java-client-generator 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ <%= render_license %>
2
+
3
+ package <%= package_name %>;
4
+
5
+ import com.google.gson.Gson;
6
+ import com.google.gson.GsonBuilder;
7
+ import com.google.gson.JsonElement;
8
+ import com.google.gson.JsonObject;
9
+ import poc.graphql.client.support.AbstractResponse;
10
+ import poc.graphql.client.support.Arguments;
11
+ import poc.graphql.client.support.Error;
12
+ import poc.graphql.client.support.AbstractQuery;
13
+ import poc.graphql.client.support.SchemaViolationError;
14
+ import poc.graphql.client.support.TopLevelResponse;
15
+ import poc.graphql.client.support.Input;
16
+ <% imports.each do |import| %>
17
+ import <%= import %>;
18
+ <% end %>
19
+
20
+ import java.io.Serializable;
21
+ import java.util.ArrayList;
22
+ import java.util.List;
23
+ import java.util.Map;
24
+
25
+
26
+ <% fields = type.fields(include_deprecated: include_deprecated) || [] %>
27
+ public interface <%= type.name %>QueryDefinition {
28
+ void define(<%= type.name %>Query _queryBuilder);
29
+ }
@@ -0,0 +1,55 @@
1
+ <%= render_license %>
2
+
3
+ package <%= package_name %>;
4
+
5
+ import com.google.gson.Gson;
6
+ import com.google.gson.GsonBuilder;
7
+ import com.google.gson.JsonElement;
8
+ import com.google.gson.JsonObject;
9
+ import poc.graphql.client.support.AbstractResponse;
10
+ import poc.graphql.client.support.Arguments;
11
+ import poc.graphql.client.support.Error;
12
+ import poc.graphql.client.support.AbstractQuery;
13
+ import poc.graphql.client.support.SchemaViolationError;
14
+ import poc.graphql.client.support.TopLevelResponse;
15
+ import poc.graphql.client.support.Input;
16
+ <% imports.each do |import| %>
17
+ import <%= import %>;
18
+ <% end %>
19
+
20
+ import java.io.Serializable;
21
+ import java.util.ArrayList;
22
+ import java.util.List;
23
+ import java.util.Map;
24
+
25
+ public class <%= response_type %>Response {
26
+ private TopLevelResponse response;
27
+ private <%= root_name %> data;
28
+
29
+ public <%= response_type %>Response(TopLevelResponse response) throws SchemaViolationError {
30
+ this.response = response;
31
+ this.data = response.getData() != null ? new <%= root_name %>(response.getData()) : null;
32
+ }
33
+
34
+ public <%= root_name %> getData() {
35
+ return data;
36
+ }
37
+
38
+ public List<Error> getErrors() {
39
+ return response.getErrors();
40
+ }
41
+
42
+ public String toJson() {
43
+ return new Gson().toJson(response);
44
+ }
45
+
46
+ public String prettyPrintJson() {
47
+ final Gson gson = new GsonBuilder().setPrettyPrinting().create();
48
+ return gson.toJson(response);
49
+ }
50
+
51
+ public static <%= response_type %>Response fromJson(String json) throws SchemaViolationError {
52
+ final TopLevelResponse response = new Gson().fromJson(json, TopLevelResponse.class);
53
+ return new <%= response_type %>Response(response);
54
+ }
55
+ }
@@ -0,0 +1,3 @@
1
+ class GraphQLJavaGen
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,376 @@
1
+ require 'graphql_java_gen/version'
2
+ require 'graphql_java_gen/reformatter'
3
+ require 'graphql_java_gen/scalar'
4
+ require 'graphql_java_gen/annotation'
5
+
6
+ require 'erb'
7
+ require 'set'
8
+
9
+ class GraphQLJavaGen
10
+ attr_reader :schema, :package_name, :scalars, :imports, :script_name, :schema_name, :include_deprecated
11
+
12
+ def initialize(schema,
13
+ package_name:, nest_under:, script_name: 'graphql_java_gen gem',
14
+ custom_scalars: [], custom_annotations: [], include_deprecated: false, license_header_file:
15
+ )
16
+ @schema = schema
17
+ @schema_name = nest_under
18
+ @script_name = script_name
19
+ @package_name = package_name
20
+ @scalars = (BUILTIN_SCALARS + custom_scalars).reduce({}) { |hash, scalar| hash[scalar.type_name] = scalar; hash }
21
+ @scalars.default_proc = ->(hash, key) { DEFAULT_SCALAR }
22
+ @annotations = custom_annotations
23
+ @imports = (@scalars.values.map(&:imports) + @annotations.map(&:imports)).flatten.sort.uniq
24
+ @include_deprecated = include_deprecated
25
+ @license_header_file = license_header_file
26
+ end
27
+
28
+ def save(path)
29
+ File.write(path, generate)
30
+ end
31
+
32
+ def save_granular(path)
33
+ write_static_methods(path)
34
+ write_response(path, :query, schema.query_root_name)
35
+ write_response(path, :mutation, schema.mutation_root_name)
36
+ write_entities(path)
37
+ write_gqlclient(path)
38
+ end
39
+
40
+ def generate
41
+ reformat(TEMPLATE_ERB.result(binding))
42
+ end
43
+
44
+ private
45
+
46
+ class << self
47
+ private
48
+
49
+ def erb_for(template_filename)
50
+ erb = ERB.new(File.read(template_filename), nil, '-')
51
+ erb.filename = template_filename
52
+ erb
53
+ end
54
+ end
55
+
56
+ TEMPLATE_ERB = erb_for(File.expand_path("../graphql_java_gen/templates/APISchema.java.erb", __FILE__))
57
+ private_constant :TEMPLATE_ERB
58
+
59
+ def erb_for_entity(template)
60
+ template_filename = File.expand_path("../graphql_java_gen/templates/#{template}.erb", __FILE__)
61
+ erb = ERB.new(File.read(template_filename), nil, '-')
62
+ erb.filename = template_filename
63
+ erb
64
+ end
65
+
66
+ def generate_entity(template, type)
67
+ erb_template = erb_for_entity(template)
68
+ reformat(erb_template.result(binding))
69
+ end
70
+
71
+ def write_static_methods(path)
72
+ File.write(path + "/Operations.java", reformat(erb_for_entity("Operations.java").result(binding)))
73
+ end
74
+
75
+ def write_response(path, query, root_name)
76
+ response_type = query.to_s.capitalize
77
+ response = reformat(erb_for_entity("Responses.java").result(binding))
78
+ File.write(path + "/#{response_type}Response.java", response)
79
+ end
80
+
81
+ def write_entities(path)
82
+ schema.types.reject{ |type| type.name.start_with?('__') || type.scalar? }.each do |type|
83
+ case type.kind when 'OBJECT', 'INTERFACE', 'UNION'
84
+ File.write(path + "/#{type.name}QueryDefinition.java", generate_entity("QueryDefinition.java", type))
85
+ File.write(path + "/#{type.name}Query.java", generate_entity("Query.java", type))
86
+ File.write(path + "/#{type.name}.java", generate_entity("Interface.java", type))
87
+
88
+ class_name = type.object? ? type.name : "Unknown#{type.name}"
89
+ File.write(path + "/#{class_name}.java", generate_entity("Object.java", type))
90
+ when 'INPUT_OBJECT'
91
+ File.write(path + "/#{type.name}.java", generate_entity("Input.java", type))
92
+ when 'ENUM'
93
+ File.write(path + "/#{type.name}.java", generate_entity("Enum.java", type))
94
+ else
95
+ raise NotImplementedError, "unhandled #{type.kind} type #{type.name}"
96
+ end
97
+ end
98
+ end
99
+
100
+ def write_gqlclient(path)
101
+ File.write(path + "/GQLClient.java", reformat(erb_for_entity("GQLClient.java").result(binding)))
102
+ end
103
+
104
+ DEFAULT_SCALAR = Scalar.new(
105
+ type_name: nil,
106
+ java_type: 'String',
107
+ deserialize_expr: ->(expr) { "jsonAsString(#{expr}, key)" },
108
+ )
109
+ private_constant :DEFAULT_SCALAR
110
+
111
+ BUILTIN_SCALARS = [
112
+ Scalar.new(
113
+ type_name: 'Int',
114
+ java_type: 'int',
115
+ deserialize_expr: ->(expr) { "jsonAsInteger(#{expr}, key)" },
116
+ ),
117
+ Scalar.new(
118
+ type_name: 'Float',
119
+ java_type: 'double',
120
+ deserialize_expr: ->(expr) { "jsonAsDouble(#{expr}, key)" },
121
+ ),
122
+ Scalar.new(
123
+ type_name: 'String',
124
+ java_type: 'String',
125
+ deserialize_expr: ->(expr) { "jsonAsString(#{expr}, key)" },
126
+ ),
127
+ Scalar.new(
128
+ type_name: 'Boolean',
129
+ java_type: 'boolean',
130
+ deserialize_expr: ->(expr) { "jsonAsBoolean(#{expr}, key)" },
131
+ ),
132
+ Scalar.new(
133
+ type_name: 'ID',
134
+ java_type: 'ID',
135
+ deserialize_expr: ->(expr) { "new ID(jsonAsString(#{expr}, key))" },
136
+ imports: ['poc.graphql.client.support.ID'],
137
+ ),
138
+ ]
139
+ private_constant :BUILTIN_SCALARS
140
+
141
+ # From: http://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html
142
+ RESERVED_WORDS = [
143
+ "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float",
144
+ "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super",
145
+ "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while", "null"
146
+ ]
147
+ private_constant :RESERVED_WORDS
148
+
149
+ def escape_reserved_word(word)
150
+ return word unless RESERVED_WORDS.include?(word)
151
+ "#{word}Value"
152
+ end
153
+
154
+ def reformat(code)
155
+ Reformatter.new(indent: " " * 4).reformat(code)
156
+ end
157
+
158
+ def java_input_type(type, non_null: false)
159
+ case type.kind
160
+ when "NON_NULL"
161
+ java_input_type(type.of_type, non_null: true)
162
+ when "SCALAR"
163
+ non_null ? scalars[type.name].non_nullable_type : scalars[type.name].nullable_type
164
+ when 'LIST'
165
+ "List<#{java_input_type(type.of_type.unwrap_non_null)}>"
166
+ when 'INPUT_OBJECT', 'ENUM'
167
+ type.name
168
+ else
169
+ raise NotImplementedError, "Unhandled #{type.kind} input type"
170
+ end
171
+ end
172
+
173
+ def java_output_type(type)
174
+ type = type.unwrap_non_null
175
+ case type.kind
176
+ when "SCALAR"
177
+ scalars[type.name].nullable_type
178
+ when 'LIST'
179
+ "List<#{java_output_type(type.of_type)}>"
180
+ when 'ENUM', 'OBJECT', 'INTERFACE', 'UNION'
181
+ type.name
182
+ else
183
+ raise NotImplementedError, "Unhandled #{type.kind} response type"
184
+ end
185
+ end
186
+
187
+ def generate_build_input_code(expr, type, depth: 1)
188
+ type = type.unwrap_non_null
189
+ case type.kind
190
+ when 'SCALAR'
191
+ if ['Int', 'Float', 'Boolean'].include?(type.name)
192
+ "_queryBuilder.append(#{expr});"
193
+ else
194
+ "AbstractQuery.appendQuotedString(_queryBuilder, #{expr}.toString());"
195
+ end
196
+ when 'ENUM'
197
+ "_queryBuilder.append(#{expr}.toString());"
198
+ when 'LIST'
199
+ item_type = type.of_type
200
+ <<-JAVA
201
+ _queryBuilder.append('[');
202
+ {
203
+ String listSeperator#{depth} = "";
204
+ for (#{java_input_type(item_type)} item#{depth} : #{expr}) {
205
+ _queryBuilder.append(listSeperator#{depth});
206
+ listSeperator#{depth} = ",";
207
+ #{generate_build_input_code("item#{depth}", item_type, depth: depth + 1)}
208
+ }
209
+ }
210
+ _queryBuilder.append(']');
211
+ JAVA
212
+ when 'INPUT_OBJECT'
213
+ "#{expr}.appendTo(_queryBuilder);"
214
+ else
215
+ raise NotImplementedError, "Unexpected #{type.kind} argument type"
216
+ end
217
+ end
218
+
219
+ def generate_build_output_code(expr, type, depth: 1, non_null: false, &block)
220
+ if type.non_null?
221
+ return generate_build_output_code(expr, type.of_type, depth: depth, non_null: true, &block)
222
+ end
223
+
224
+ statements = ""
225
+ unless non_null
226
+ optional_name = "optional#{depth}"
227
+ generate_build_output_code(expr, type, depth: depth, non_null: true) do |item_statements, item_expr|
228
+ statements = <<-JAVA
229
+ #{java_output_type(type)} #{optional_name} = null;
230
+ if (!#{expr}.isJsonNull()) {
231
+ #{item_statements}
232
+ #{optional_name} = #{item_expr};
233
+ }
234
+ JAVA
235
+ end
236
+ return yield statements, optional_name
237
+ end
238
+
239
+ expr = case type.kind
240
+ when 'SCALAR'
241
+ scalars[type.name].deserialize(expr)
242
+ when 'LIST'
243
+ list_name = "list#{depth}"
244
+ element_name = "element#{depth}"
245
+ generate_build_output_code(element_name, type.of_type, depth: depth + 1) do |item_statements, item_expr|
246
+ statements = <<-JAVA
247
+ #{java_output_type(type)} #{list_name} = new ArrayList<>();
248
+ for (JsonElement #{element_name} : jsonAsArray(#{expr}, key)) {
249
+ #{item_statements}
250
+ #{list_name}.add(#{item_expr});
251
+ }
252
+ JAVA
253
+ end
254
+ list_name
255
+ when 'OBJECT'
256
+ "new #{type.name}(jsonAsObject(#{expr}, key))"
257
+ when 'INTERFACE', 'UNION'
258
+ "Unknown#{type.name}.create(jsonAsObject(#{expr}, key))"
259
+ when 'ENUM'
260
+ "#{type.name}.fromGraphQl(jsonAsString(#{expr}, key))"
261
+ else
262
+ raise NotImplementedError, "Unexpected #{type.kind} argument type"
263
+ end
264
+ yield statements, expr
265
+ end
266
+
267
+ def java_arg_defs(field, skip_optional: false)
268
+ defs = []
269
+ field.required_args.each do |arg|
270
+ defs << "#{java_input_type(arg.type)} #{escape_reserved_word(arg.camelize_name)}"
271
+ end
272
+ unless field.optional_args.empty? || skip_optional
273
+ defs << "#{field.classify_name}ArgumentsDefinition argsDef"
274
+ end
275
+ if field.subfields?
276
+ defs << "#{field.type.unwrap.name}QueryDefinition queryDef"
277
+ end
278
+ defs.join(', ')
279
+ end
280
+
281
+ def java_required_arg_defs(field)
282
+ defs = []
283
+ field.required_args.each do |arg|
284
+ defs << "#{java_input_type(arg.type)} #{escape_reserved_word(arg.camelize_name)}"
285
+ end
286
+ unless field.optional_args.empty?
287
+ defs << "#{field.classify_name}ArgumentsDefinition argsDef"
288
+ end
289
+ if field.subfields?
290
+ defs << "#{field.type.unwrap.classify_name}QueryDefinition queryDef"
291
+ end
292
+ defs.join(', ')
293
+ end
294
+
295
+ def java_arg_expresions_with_empty_optional_args(field)
296
+ expressions = field.required_args.map { |arg| escape_reserved_word(arg.camelize_name) }
297
+ expressions << "args -> {}"
298
+ if field.subfields?
299
+ expressions << "queryDef"
300
+ end
301
+ expressions.join(', ')
302
+ end
303
+
304
+ def java_implements(type)
305
+ return "implements #{type.name} " unless type.object?
306
+ interfaces = abstract_types.fetch(type.name)
307
+ return "" if interfaces.empty?
308
+ "implements #{interfaces.to_a.join(', ')} "
309
+ end
310
+
311
+ def java_annotations(field, in_argument: false)
312
+ annotations = @annotations.map do |annotation|
313
+ "@#{annotation.name}" if annotation.annotate?(field)
314
+ end.compact
315
+ return "" unless annotations.any?
316
+
317
+ if in_argument
318
+ annotations.join(" ") + " "
319
+ else
320
+ annotations.join("\n")
321
+ end
322
+ end
323
+
324
+ def type_names_set
325
+ @type_names_set ||= schema.types.map(&:name).to_set
326
+ end
327
+
328
+ def abstract_types
329
+ @abstract_types ||= schema.types.each_with_object({}) do |type, result|
330
+ case type.kind
331
+ when 'OBJECT'
332
+ result[type.name] ||= Set.new
333
+ when 'INTERFACE', 'UNION'
334
+ type.possible_types.each do |possible_type|
335
+ (result[possible_type.name] ||= Set.new).add(type.name)
336
+ end
337
+ end
338
+ end
339
+ end
340
+
341
+ def java_doc(element)
342
+ doc = ''
343
+ unless element.description.nil?
344
+ description = wrap_text(element.description, 100)
345
+ description = description.chomp("\n").gsub("\n", "\n* ")
346
+ doc << '* '
347
+ doc << description
348
+ end
349
+
350
+ if element.respond_to?(:deprecated?) && element.deprecated?
351
+ unless doc.empty?
352
+ doc << "\n*"
353
+ doc << "\n*"
354
+ else
355
+ doc << '*'
356
+ end
357
+ doc << ' @deprecated '
358
+ doc << element.deprecation_reason
359
+ end
360
+
361
+ doc.empty? ? doc : "/**\n" + ERB::Util.html_escape(doc) + "\n*/"
362
+ end
363
+
364
+ def wrap_text(text, col_width=80)
365
+ text.gsub!( /(\S{#{col_width}})(?=\S)/, '\1 ' )
366
+ text.gsub!( /(.{1,#{col_width}})(?:\s+|$)/, "\\1\n" )
367
+ text
368
+ end
369
+
370
+ def render_license
371
+ content = File.read(File.expand_path(@license_header_file))
372
+ t = ERB.new(content)
373
+ t.result(binding)
374
+ end
375
+
376
+ end