ruby-graphql-java-client-generator 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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