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.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +7 -0
- data/codegen/lib/graphql_java_gen/annotation.rb +15 -0
- data/codegen/lib/graphql_java_gen/reformatter.rb +53 -0
- data/codegen/lib/graphql_java_gen/scalar.rb +33 -0
- data/codegen/lib/graphql_java_gen/templates/APISchema.java.erb +384 -0
- data/codegen/lib/graphql_java_gen/templates/Enum.java.erb +70 -0
- data/codegen/lib/graphql_java_gen/templates/GQLClient.java.erb +66 -0
- data/codegen/lib/graphql_java_gen/templates/Input.java.erb +103 -0
- data/codegen/lib/graphql_java_gen/templates/Interface.java.erb +39 -0
- data/codegen/lib/graphql_java_gen/templates/Object.java.erb +113 -0
- data/codegen/lib/graphql_java_gen/templates/Operations.java.erb +36 -0
- data/codegen/lib/graphql_java_gen/templates/Query.java.erb +113 -0
- data/codegen/lib/graphql_java_gen/templates/QueryDefinition.java.erb +29 -0
- data/codegen/lib/graphql_java_gen/templates/Responses.java.erb +55 -0
- data/codegen/lib/graphql_java_gen/version.rb +3 -0
- data/codegen/lib/ruby-graphql-java-client-generator.rb +376 -0
- metadata +130 -0
@@ -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,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
|