graphql_java_gen 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a3088c3fd787e2d815792f8bb8f6a0b3fe414829
4
+ data.tar.gz: 1058513015d8fe05d201f7e72ef5f3a007a94b8a
5
+ SHA512:
6
+ metadata.gz: a3d82e27f2ca1cfbf3a7b7c082a5a75d631607f32f1d6bc2e7fdb54840ed023814ce81c8fe4d6e65bf4576a0c28bb1eadbb988aecb8593362935fa2603648e18
7
+ data.tar.gz: 1c040d36778c54054772af31b651ac30db8aef05f77812c158e51cbcf33ab98b171d942d1aaf1837439a84de2dcbeefab0063a6e43eb2455e40b666d5ff86d79
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Shopify
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # GraphQLJavaGen
2
+
3
+ Generate code for a specific GraphQL schema that provides query
4
+ builders and response classes.
5
+
6
+ ## Installation
7
+
8
+ The code generator requires ruby version 2.1 or later.
9
+
10
+ It is recommended to use [bundler](http://bundler.io/) to install
11
+ the code generators ruby package.
12
+
13
+ Until this project is released, it is recommended to include it into
14
+ a project as a git submodule.
15
+
16
+ $ git submodule https://github.com/Shopify/graphql_java_gen.git
17
+
18
+ Add this line to your application's Gemfile:
19
+
20
+ ```ruby
21
+ gem 'graphql_java_gen', path: 'graphql_java_gen'
22
+ ```
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ The generated code depends on the com.shopify.graphql.support package
29
+ which is in the support directory of this repo. Create a symlink to
30
+ include that project in a [gradle multi-project build
31
+ ](https://docs.gradle.org/current/userguide/multi_project_builds.html)
32
+
33
+ $ ln -s graphql_java_gen/support GraphQLSupport
34
+
35
+ ## Usage
36
+
37
+ Create a script that generates the code for a GraphQL API
38
+
39
+ ```ruby
40
+ require 'graphql_java_gen'
41
+ require 'graphql_schema'
42
+ require 'json'
43
+
44
+ introspection_result = File.read("graphql_schema.json")
45
+ schema = GraphQLSchema.new(JSON.parse(introspection_result))
46
+
47
+ GraphQLJavaGen.new(schema,
48
+ package_name: "com.example.MyApp",
49
+ nest_under: 'ExampleSchema',
50
+ custom_scalars: [
51
+ GraphQLJavaGen::Scalar.new(
52
+ type_name: 'Decimal',
53
+ java_type: 'BigDecimal',
54
+ deserialize_expr: ->(expr) { "new BigDecimal(jsonAsString(#{expr}, key))" },
55
+ imports: ['java.math.BigDecimal'],
56
+ ),
57
+ ]
58
+ ).save("#{Dir.pwd}/../MyApp/src/main/java/com/example/MyApp/ExampleSchema.java")
59
+ ```
60
+
61
+ That generated code depends on the com.shopify.graphql.support package.
62
+
63
+ The generated code includes a query builder that can be used to create a GraphQL
64
+ query in a type-safe manner.
65
+
66
+ ```java
67
+ String queryString = ExampleSchema.query(query -> query
68
+ .user(user -> user
69
+ .firstName()
70
+ .lastName()
71
+ )
72
+ ).toString();
73
+ ```
74
+
75
+ The generated code also includes response classes that will deserialize the response
76
+ and provide methods for accessing the field data with it already coerced to the
77
+ correct type.
78
+
79
+ ```java
80
+ ExampleSchema.QueryResponse response = ExampleSchema.QueryResponse.fromJson(responseJson);
81
+ if (!response.getErrors().isEmpty()) {
82
+ for (Error error : response.getErrors()) {
83
+ System.err.println("GraphQL error: " + error.message());
84
+ }
85
+ }
86
+ if (data != null) {
87
+ ExampleSchema.User user = data.getUser();
88
+ System.out.println("user.firstName() + " " + user.lastName());
89
+ }
90
+ ```
91
+
92
+ ## Lambda Expressions
93
+
94
+ The java 8 lambda expressions feature is essential for having a
95
+ nice syntax for the query builder. This feature is also available
96
+ for Android apps by using the
97
+ [Jack toolchain](https://source.android.com/source/jack.html).
98
+
99
+ If an older version of java must be used, then
100
+ [retrolambda](https://github.com/orfjackal/retrolambda) can be used
101
+ to backport this feature to java 5-7.
102
+
103
+ ## Development
104
+
105
+ After checking out the repo, run `bundle` to install ruby dependencies.
106
+ Then, run `rake test` to run the tests.
107
+
108
+ To install this gem onto your local machine, run `bundle exec rake
109
+ install` or reference it from a Gemfile using the path option
110
+ (e.g. `gem 'graphql_java_gen', path: '~/src/graphql_java_gen'`).
111
+
112
+ ## Contributing
113
+
114
+ See our [contributing guidelines](CONTRIBUTING.md) for more information.
115
+
116
+ ## License
117
+
118
+ The gem is available as open source under the terms of the
119
+ [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,263 @@
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
11
+
12
+ def initialize(schema,
13
+ package_name:, nest_under:, script_name: 'graphql_java_gen gem',
14
+ custom_scalars: [], custom_annotations: []
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
+ end
25
+
26
+ def save(path)
27
+ File.write(path, generate)
28
+ end
29
+
30
+ def generate
31
+ reformat(TEMPLATE_ERB.result(binding))
32
+ end
33
+
34
+ private
35
+
36
+ class << self
37
+ private
38
+
39
+ def erb_for(template_filename)
40
+ erb = ERB.new(File.read(template_filename))
41
+ erb.filename = template_filename
42
+ erb
43
+ end
44
+ end
45
+
46
+ TEMPLATE_ERB = erb_for(File.expand_path("../graphql_java_gen/templates/APISchema.java.erb", __FILE__))
47
+ private_constant :TEMPLATE_ERB
48
+
49
+ DEFAULT_SCALAR = Scalar.new(
50
+ type_name: nil,
51
+ java_type: 'String',
52
+ deserialize_expr: ->(expr) { "jsonAsString(#{expr}, key)" },
53
+ )
54
+ private_constant :DEFAULT_SCALAR
55
+
56
+ BUILTIN_SCALARS = [
57
+ Scalar.new(
58
+ type_name: 'Int',
59
+ java_type: 'int',
60
+ deserialize_expr: ->(expr) { "jsonAsInteger(#{expr}, key)" },
61
+ ),
62
+ Scalar.new(
63
+ type_name: 'Float',
64
+ java_type: 'double',
65
+ deserialize_expr: ->(expr) { "jsonAsDouble(#{expr}, key)" },
66
+ ),
67
+ Scalar.new(
68
+ type_name: 'String',
69
+ java_type: 'String',
70
+ deserialize_expr: ->(expr) { "jsonAsString(#{expr}, key)" },
71
+ ),
72
+ Scalar.new(
73
+ type_name: 'Boolean',
74
+ java_type: 'boolean',
75
+ deserialize_expr: ->(expr) { "jsonAsBoolean(#{expr}, key)" },
76
+ ),
77
+ Scalar.new(
78
+ type_name: 'ID',
79
+ java_type: 'ID',
80
+ deserialize_expr: ->(expr) { "new ID(jsonAsString(#{expr}, key))" },
81
+ imports: ['com.shopify.graphql.support.ID'],
82
+ ),
83
+ ]
84
+ private_constant :BUILTIN_SCALARS
85
+
86
+ # From: http://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html
87
+ RESERVED_WORDS = [
88
+ "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float",
89
+ "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super",
90
+ "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while"
91
+ ]
92
+ private_constant :RESERVED_WORDS
93
+
94
+ def escape_reserved_word(word)
95
+ return word unless RESERVED_WORDS.include?(word)
96
+ "#{word}Value"
97
+ end
98
+
99
+ def reformat(code)
100
+ Reformatter.new(indent: " " * 4).reformat(code)
101
+ end
102
+
103
+ def java_input_type(type, non_null: false)
104
+ case type.kind
105
+ when "NON_NULL"
106
+ java_input_type(type.of_type, non_null: true)
107
+ when "SCALAR"
108
+ non_null ? scalars[type.name].non_nullable_type : scalars[type.name].nullable_type
109
+ when 'LIST'
110
+ "List<#{java_input_type(type.of_type.unwrap_non_null)}>"
111
+ when 'INPUT_OBJECT', 'ENUM'
112
+ type.name
113
+ else
114
+ raise NotImplementedError, "Unhandled #{type.kind} input type"
115
+ end
116
+ end
117
+
118
+ def java_output_type(type)
119
+ type = type.unwrap_non_null
120
+ case type.kind
121
+ when "SCALAR"
122
+ scalars[type.name].nullable_type
123
+ when 'LIST'
124
+ "List<#{java_output_type(type.of_type)}>"
125
+ when 'ENUM', 'OBJECT', 'INTERFACE', 'UNION'
126
+ type.name
127
+ else
128
+ raise NotImplementedError, "Unhandled #{type.kind} response type"
129
+ end
130
+ end
131
+
132
+ def generate_build_input_code(expr, type, depth: 1)
133
+ type = type.unwrap_non_null
134
+ case type.kind
135
+ when 'SCALAR'
136
+ if ['Int', 'Float', 'Boolean'].include?(type.name)
137
+ "_queryBuilder.append(#{expr});"
138
+ else
139
+ "Query.appendQuotedString(_queryBuilder, #{expr}.toString());"
140
+ end
141
+ when 'ENUM'
142
+ "_queryBuilder.append(#{expr}.toString());"
143
+ when 'LIST'
144
+ item_type = type.of_type
145
+ <<-JAVA
146
+ _queryBuilder.append('[');
147
+
148
+ String listSeperator#{depth} = "";
149
+ for (#{java_input_type(item_type)} item#{depth} : #{expr}) {
150
+ _queryBuilder.append(listSeperator#{depth});
151
+ listSeperator#{depth} = ",";
152
+ #{generate_build_input_code("item#{depth}", item_type, depth: depth + 1)}
153
+ }
154
+ _queryBuilder.append(']');
155
+ JAVA
156
+ when 'INPUT_OBJECT'
157
+ "#{expr}.appendTo(_queryBuilder);"
158
+ else
159
+ raise NotImplementedError, "Unexpected #{type.kind} argument type"
160
+ end
161
+ end
162
+
163
+ def generate_build_output_code(expr, type, depth: 1, non_null: false, &block)
164
+ if type.non_null?
165
+ return generate_build_output_code(expr, type.of_type, depth: depth, non_null: true, &block)
166
+ end
167
+
168
+ statements = ""
169
+ unless non_null
170
+ optional_name = "optional#{depth}"
171
+ generate_build_output_code(expr, type, depth: depth, non_null: true) do |item_statements, item_expr|
172
+ statements = <<-JAVA
173
+ #{java_output_type(type)} #{optional_name} = null;
174
+ if (!#{expr}.isJsonNull()) {
175
+ #{item_statements}
176
+ #{optional_name} = #{item_expr};
177
+ }
178
+ JAVA
179
+ end
180
+ return yield statements, optional_name
181
+ end
182
+
183
+ expr = case type.kind
184
+ when 'SCALAR'
185
+ scalars[type.name].deserialize(expr)
186
+ when 'LIST'
187
+ list_name = "list#{depth}"
188
+ element_name = "element#{depth}"
189
+ generate_build_output_code(element_name, type.of_type, depth: depth + 1) do |item_statements, item_expr|
190
+ statements = <<-JAVA
191
+ #{java_output_type(type)} #{list_name} = new ArrayList<>();
192
+ for (JsonElement #{element_name} : jsonAsArray(#{expr}, key)) {
193
+ #{item_statements}
194
+ #{list_name}.add(#{item_expr});
195
+ }
196
+ JAVA
197
+ end
198
+ list_name
199
+ when 'OBJECT'
200
+ "new #{type.name}(jsonAsObject(#{expr}, key))"
201
+ when 'INTERFACE', 'UNION'
202
+ "Unknown#{type.name}.create(jsonAsObject(#{expr}, key))"
203
+ when 'ENUM'
204
+ "#{type.name}.fromGraphQl(jsonAsString(#{expr}, key))"
205
+ else
206
+ raise NotImplementedError, "Unexpected #{type.kind} argument type"
207
+ end
208
+ yield statements, expr
209
+ end
210
+
211
+ def java_arg_defs(field, skip_optional: false)
212
+ defs = []
213
+ field.required_args.each do |arg|
214
+ defs << "#{java_input_type(arg.type)} #{escape_reserved_word(arg.camelize_name)}"
215
+ end
216
+ unless field.optional_args.empty? || skip_optional
217
+ defs << "#{field.classify_name}ArgumentsDefinition argsDef"
218
+ end
219
+ if field.subfields?
220
+ defs << "#{field.type.unwrap.name}QueryDefinition queryDef"
221
+ end
222
+ defs.join(', ')
223
+ end
224
+
225
+ def java_required_arg_defs(field)
226
+ defs = []
227
+ field.required_args.each do |arg|
228
+ defs << "#{java_input_type(arg.type)} #{escape_reserved_word(arg.camelize_name)}"
229
+ end
230
+ unless field.optional_args.empty?
231
+ defs << "#{field.classify_name}ArgumentsDefinition argsDef"
232
+ end
233
+ if field.subfields?
234
+ defs << "#{field.type.unwrap.classify_name}QueryDefinition queryDef"
235
+ end
236
+ defs.join(', ')
237
+ end
238
+
239
+ def java_arg_expresions_with_empty_optional_args(field)
240
+ expressions = field.required_args.map { |arg| escape_reserved_word(arg.camelize_name) }
241
+ expressions << "args -> {}"
242
+ if field.subfields?
243
+ expressions << "queryDef"
244
+ end
245
+ expressions.join(', ')
246
+ end
247
+
248
+ def java_implements(type)
249
+ return "implements #{type.name} " unless type.object?
250
+ return "" if type.interfaces.empty?
251
+ "implements #{type.interfaces.map{ |interface| interface.name }.join(', ')} "
252
+ end
253
+
254
+ def java_annotations(field)
255
+ @annotations.map do |annotation|
256
+ "@#{annotation.name}" if annotation.annotate?(field)
257
+ end.compact.join("\n")
258
+ end
259
+
260
+ def type_names_set
261
+ @type_names_set ||= schema.types.map(&:name).to_set
262
+ end
263
+ end
@@ -0,0 +1,15 @@
1
+ class GraphQLJavaGen
2
+ class Annotation
3
+ attr_reader :name, :imports
4
+
5
+ def initialize(name, imports: [], &block)
6
+ @name = name
7
+ @imports = imports
8
+ @condition = block
9
+ end
10
+
11
+ def annotate?(field)
12
+ @condition.call(field)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,43 @@
1
+ class GraphQLJavaGen
2
+ # Reformat code that uses curly brace blocks
3
+ class Reformatter
4
+ INDENT_START_CHARS = ["{", "("]
5
+ INDENT_END_CHARS = ["}", ")"]
6
+
7
+ def initialize(indent: "\t")
8
+ @indent = indent
9
+ end
10
+
11
+ def reformat(code)
12
+ output = ""
13
+ indent_level = 0
14
+ squeeze_newlines = true
15
+
16
+ code.lines.each do |line|
17
+ stripped_line = line.strip
18
+
19
+ if INDENT_END_CHARS.include?(stripped_line[0])
20
+ indent_level -= 1
21
+ # no blank lines immediately preceding end of block
22
+ output.rstrip!
23
+ output << "\n"
24
+ end
25
+
26
+ if stripped_line.empty?
27
+ output << "\n" unless squeeze_newlines
28
+ squeeze_newlines = true
29
+ else
30
+ output << @indent * indent_level << line.lstrip
31
+ squeeze_newlines = false
32
+ end
33
+
34
+ if INDENT_START_CHARS.include?(stripped_line[-1])
35
+ indent_level += 1
36
+ # no blank lines following start of block
37
+ squeeze_newlines = true
38
+ end
39
+ end
40
+ output
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,33 @@
1
+ class GraphQLJavaGen
2
+ class Scalar
3
+ attr_reader :type_name, :imports
4
+
5
+ WRAPPER_OBJECT = {
6
+ 'int' => 'Integer',
7
+ 'long' => 'Long',
8
+ 'double' => 'Double',
9
+ 'boolean' => 'Boolean',
10
+ }
11
+ WRAPPER_OBJECT.default_proc = ->(_, key) { key }
12
+ private_constant :WRAPPER_OBJECT
13
+
14
+ def initialize(type_name:, java_type:, deserialize_expr:, imports: [])
15
+ @type_name = type_name
16
+ @java_type = java_type
17
+ @deserialize_expr = deserialize_expr
18
+ @imports = imports
19
+ end
20
+
21
+ def nullable_type
22
+ WRAPPER_OBJECT[@java_type]
23
+ end
24
+
25
+ def non_nullable_type
26
+ @java_type
27
+ end
28
+
29
+ def deserialize(expr)
30
+ @deserialize_expr.call(expr)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,369 @@
1
+ // Generated from <%= script_name %>
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 com.shopify.graphql.support.AbstractResponse;
10
+ import com.shopify.graphql.support.Arguments;
11
+ import com.shopify.graphql.support.Error;
12
+ import com.shopify.graphql.support.Query;
13
+ import com.shopify.graphql.support.SchemaViolationError;
14
+ import com.shopify.graphql.support.TopLevelResponse;
15
+ <% imports.each do |import| %>
16
+ import <%= import %>;
17
+ <% end %>
18
+
19
+ import java.io.Serializable;
20
+ import java.util.ArrayList;
21
+ import java.util.List;
22
+ import java.util.Map;
23
+
24
+ public class <%= schema_name %> {
25
+ <% [[:query, schema.query_root_name], [:mutation, schema.mutation_root_name]].each do |operation_type, root_name| %>
26
+ <% next unless schema.mutation_root_name %>
27
+ public static <%= root_name %>Query <%= operation_type %>(<%= root_name %>QueryDefinition queryDef) {
28
+ StringBuilder queryString = new StringBuilder("<%= operation_type unless operation_type == :query %>{");
29
+ <%= root_name %>Query query = new <%= root_name %>Query(queryString);
30
+ queryDef.define(query);
31
+ queryString.append('}');
32
+ return query;
33
+ }
34
+
35
+ public static class <%= operation_type.capitalize %>Response {
36
+ private TopLevelResponse response;
37
+ private <%= root_name %> data;
38
+
39
+ public <%= operation_type.capitalize %>Response(TopLevelResponse response) throws SchemaViolationError {
40
+ this.response = response;
41
+ this.data = response.getData() != null ? new <%= root_name %>(response.getData()) : null;
42
+ }
43
+
44
+ public <%= root_name %> getData() {
45
+ return data;
46
+ }
47
+
48
+ public List<Error> getErrors() {
49
+ return response.getErrors();
50
+ }
51
+
52
+ public String toJson() {
53
+ return new Gson().toJson(response);
54
+ }
55
+
56
+ public String prettyPrintJson() {
57
+ final Gson gson = new GsonBuilder().setPrettyPrinting().create();
58
+ return gson.toJson(response);
59
+ }
60
+
61
+ public static <%= operation_type.capitalize %>Response fromJson(String json) throws SchemaViolationError {
62
+ final TopLevelResponse response = new Gson().fromJson(json, TopLevelResponse.class);
63
+ return new <%= operation_type.capitalize %>Response(response);
64
+ }
65
+ }
66
+ <% end %>
67
+
68
+ <% schema.types.reject{ |type| type.name.start_with?('__') || type.scalar? }.each do |type| %>
69
+ <% case type.kind when 'OBJECT', 'INTERFACE', 'UNION' %>
70
+ public interface <%= type.name %>QueryDefinition {
71
+ void define(<%= type.name %>Query _queryBuilder);
72
+ }
73
+
74
+ public static class <%= type.name %>Query extends Query<<%= type.name %>Query> {
75
+ <%= type.name %>Query(StringBuilder _queryBuilder) {
76
+ super(_queryBuilder);
77
+ <% if type.object? && type.implement?("Node") %>
78
+ startField("id");
79
+ <% end %>
80
+ <% unless type.object? %>
81
+ startField("__typename");
82
+ <% end %>
83
+ }
84
+
85
+ <% type.fields.each do |field| %>
86
+ <% next if field.name == "id" && type.object? && type.implement?("Node") %>
87
+ <% unless field.optional_args.empty? %>
88
+ public class <%= field.classify_name %>Arguments extends Arguments {
89
+ <%= field.classify_name %>Arguments(StringBuilder _queryBuilder) {
90
+ super(_queryBuilder, <%= !!field.required_args.empty? %>);
91
+ }
92
+
93
+ <% field.optional_args.each do |arg| %>
94
+ public <%= field.classify_name %>Arguments <%= escape_reserved_word(arg.camelize_name) %>(<%= java_input_type(arg.type) %> value) {
95
+ if (value != null) {
96
+ startArgument("<%= arg.name %>");
97
+ <%= generate_build_input_code('value', arg.type) %>
98
+ }
99
+ return this;
100
+ }
101
+ <% end %>
102
+ }
103
+
104
+ public interface <%= field.classify_name %>ArgumentsDefinition {
105
+ void define(<%= field.classify_name %>Arguments args);
106
+ }
107
+
108
+ public <%= type.name %>Query <%= escape_reserved_word(field.camelize_name) %>(<%= java_arg_defs(field, skip_optional: true) %>) {
109
+ return <%= escape_reserved_word(field.camelize_name) %>(<%= java_arg_expresions_with_empty_optional_args(field) %>);
110
+ }
111
+ <% end %>
112
+
113
+ public <%= type.name %>Query <%= escape_reserved_word(field.camelize_name) %>(<%= java_arg_defs(field) %>) {
114
+ startField("<%= field.name %>");
115
+ <% unless field.args.empty? %>
116
+ <% if field.required_args.empty? %>
117
+ <%= field.classify_name %>Arguments args = new <%= field.classify_name %>Arguments(_queryBuilder);
118
+ argsDef.define(args);
119
+ <%= field.classify_name %>Arguments.end(args);
120
+ <% else %>
121
+ <% field.required_args.each_with_index do |arg, i| %>
122
+ _queryBuilder.append("<%= i == 0 ? "(" : "," %><%= arg.name %>:");
123
+ <%= generate_build_input_code(escape_reserved_word(arg.camelize_name), arg.type) %>
124
+ <% end %>
125
+ <% unless field.optional_args.empty? %>
126
+ argsDef.define(new <%= field.classify_name %>Arguments(_queryBuilder));
127
+ <% end %>
128
+ _queryBuilder.append(')');
129
+ <% end %>
130
+ <% end %>
131
+ <% if field.subfields? %>
132
+ _queryBuilder.append('{');
133
+ queryDef.define(new <%= field.type.unwrap.name %>Query(_queryBuilder));
134
+ _queryBuilder.append('}');
135
+ <% end %>
136
+ return this;
137
+ }
138
+ <% end %>
139
+ <% unless type.object? %>
140
+ <% type.possible_types.each do |possible_type| %>
141
+ public <%= type.name %>Query on<%= possible_type.name %>(<%= possible_type.name %>QueryDefinition queryDef) {
142
+ startInlineFragment("<%= possible_type.name %>");
143
+ queryDef.define(new <%= possible_type.name %>Query(_queryBuilder));
144
+ _queryBuilder.append('}');
145
+ return this;
146
+ }
147
+ <% end %>
148
+ <% end %>
149
+
150
+ <% if schema.root_name?(type.name) %>
151
+ public String toString() {
152
+ return _queryBuilder.toString();
153
+ }
154
+ <% end %>
155
+ }
156
+
157
+ <% unless type.object? %>
158
+ public interface <%= type.name %> {
159
+ String getGraphQlTypeName();
160
+ <% type.fields.each do |field| %>
161
+ <%= java_output_type(field.type) %> get<%= field.classify_name %>();
162
+ <% end %>
163
+ }
164
+ <% end %>
165
+
166
+ <% class_name = type.object? ? type.name : "Unknown#{type.name}" %>
167
+ public static class <%= class_name %> extends AbstractResponse<<%= class_name %>> <%= java_implements(type) %>{
168
+ public <%= class_name %>() {
169
+ }
170
+
171
+ public <%= class_name %>(JsonObject fields) throws SchemaViolationError {
172
+ for (Map.Entry<String, JsonElement> field : fields.entrySet()) {
173
+ String key = field.getKey();
174
+ String fieldName = getFieldName(key);
175
+ switch (fieldName) {
176
+ <% type.fields.each do |field| %>
177
+ case "<%= field.name %>": {
178
+ <% generate_build_output_code("field.getValue()", field.type) do |statements, expr| %>
179
+ <%= statements %>
180
+ responseData.put(key, <%= expr %>);
181
+ <% end %>
182
+ break;
183
+ }
184
+ <% end %>
185
+ case "__typename": {
186
+ responseData.put(key, jsonAsString(field.getValue(), key));
187
+ break;
188
+ }
189
+ default: {
190
+ throw new SchemaViolationError(this, key, field.getValue());
191
+ }
192
+ }
193
+ }
194
+ }
195
+
196
+ <% if type.object? && type.implement?("Node") %>
197
+ public <%= class_name %>(<%= scalars['ID'].non_nullable_type %> id) {
198
+ this();
199
+ optimisticData.put("id", id);
200
+ }
201
+ <% end %>
202
+
203
+ <% if type_names_set.include?('Node') %>
204
+ public List<Node> getNodes() {
205
+ List<Node> children = new ArrayList<>();
206
+ <% if type.object? && type.implement?("Node") %>
207
+ children.add(this);
208
+ <% end %>
209
+ <% type.fields.each do |field| %>
210
+ <% next unless field.type.unwrap.object? %>
211
+ if (get<%= field.classify_name %>() != null) {
212
+ <% if field.type.unwrap_non_null.kind == 'LIST' %>
213
+ for (<%= java_output_type(field.type.unwrap) %> elem: get<%= field.classify_name %>()) {
214
+ children.addAll(elem.getNodes());
215
+ }
216
+ <% else %>
217
+ children.addAll(get<%= field.classify_name %>().getNodes());
218
+ <% end %>
219
+ }
220
+ <% end %>
221
+ return children;
222
+ }
223
+ <% end %>
224
+
225
+ <% if type.object? %>
226
+ public String getGraphQlTypeName() {
227
+ return "<%= type.name %>";
228
+ }
229
+ <% else %>
230
+ public static <%= type.name %> create(JsonObject fields) throws SchemaViolationError {
231
+ String typeName = fields.getAsJsonPrimitive("__typename").getAsString();
232
+ switch (typeName) {
233
+ <% type.possible_types.each do |possible_type| %>
234
+ case "<%= possible_type.name %>": {
235
+ return new <%= possible_type.name %>(fields);
236
+ }
237
+ <% end %>
238
+ default: {
239
+ return new <%= class_name %>(fields);
240
+ }
241
+ }
242
+ }
243
+
244
+ public String getGraphQlTypeName() {
245
+ return (String) get("__typename");
246
+ }
247
+ <% end %>
248
+
249
+ <% type.fields.each do |field| %>
250
+ <%= java_annotations(field) %>
251
+ public <%= java_output_type(field.type) %> get<%= field.classify_name %>() {
252
+ return (<%= java_output_type(field.type) %>) get("<%= field.name %>");
253
+ }
254
+
255
+ <% next if field.name == "id" && type.object? && type.implement?("Node") %>
256
+ public <%= class_name %> set<%= field.classify_name %>(<%= java_output_type(field.type) %> arg) {
257
+ optimisticData.put("<%= field.name %>", arg);
258
+ return this;
259
+ }
260
+ <% end %>
261
+
262
+ public boolean unwrapsToObject(String key) {
263
+ switch (key) {
264
+ <% type.fields.each do |field| %>
265
+ case "<%= field.name %>": return <%= field.type.unwrap.object? %>;
266
+ <% end %>
267
+ default: return false;
268
+ }
269
+ }
270
+ }
271
+ <% when 'INPUT_OBJECT' %>
272
+ public static class <%= type.name %> implements Serializable {
273
+ <% type.required_input_fields.each do |field| %>
274
+ private <%= java_input_type(field.type) %> <%= escape_reserved_word(field.camelize_name) %>;
275
+ <% end %>
276
+ <% type.optional_input_fields.each do |field| %>
277
+ private <%= java_input_type(field.type) %> <%= escape_reserved_word(field.camelize_name) %>;
278
+ <% end %>
279
+
280
+ <% unless type.required_input_fields.empty? %>
281
+ public <%= type.name %>(<%= type.required_input_fields.map{ |field| "#{java_input_type(field.type)} #{escape_reserved_word(field.camelize_name)}" }.join(', ') %>) {
282
+ <% type.required_input_fields.each do |field| %>
283
+ this.<%= escape_reserved_word(field.camelize_name) %> = <%= escape_reserved_word(field.camelize_name) %>;
284
+ <% end %>
285
+ }
286
+ <% end %>
287
+
288
+ <% type.required_input_fields.each do |field| %>
289
+ public <%= java_input_type(field.type) %> get<%= field.classify_name %>() {
290
+ return <%= escape_reserved_word(field.camelize_name) %>;
291
+ }
292
+
293
+ public <%= type.name %> set<%= field.classify_name %>(<%= java_input_type(field.type) %> <%= escape_reserved_word(field.camelize_name) %>) {
294
+ this.<%= escape_reserved_word(field.camelize_name) %> = <%= escape_reserved_word(field.camelize_name) %>;
295
+ return this;
296
+ }
297
+ <% end %>
298
+ <% type.optional_input_fields.each do |field| %>
299
+ public <%= java_input_type(field.type) %> get<%= field.classify_name %>() {
300
+ return <%= escape_reserved_word(field.camelize_name) %>;
301
+ }
302
+
303
+ public <%= type.name %> set<%= field.classify_name %>(<%= java_input_type(field.type) %> <%= escape_reserved_word(field.camelize_name) %>) {
304
+ this.<%= escape_reserved_word(field.camelize_name) %> = <%= escape_reserved_word(field.camelize_name) %>;
305
+ return this;
306
+ }
307
+ <% end %>
308
+
309
+ public void appendTo(StringBuilder _queryBuilder) {
310
+ String separator = "";
311
+ _queryBuilder.append('{');
312
+ <% type.required_input_fields.each do |field| %>
313
+ _queryBuilder.append(separator);
314
+ separator = ",";
315
+ _queryBuilder.append("<%= field.name %>:");
316
+ <%= generate_build_input_code(escape_reserved_word(field.camelize_name), field.type) %>
317
+ <% end %>
318
+ <% type.optional_input_fields.each do |field| %>
319
+ if (<%= escape_reserved_word(field.camelize_name) %> != null) {
320
+ _queryBuilder.append(separator);
321
+ separator = ",";
322
+ _queryBuilder.append("<%= field.name %>:");
323
+ <%= generate_build_input_code(escape_reserved_word(field.camelize_name), field.type) %>
324
+ }
325
+ <% end %>
326
+ _queryBuilder.append('}');
327
+ }
328
+ }
329
+ <% when 'ENUM' %>
330
+ public enum <%= type.name %> {
331
+ <% type.enum_values.each do |value| %>
332
+ <%= value.upcase_name %>,
333
+ <% end %>
334
+ UNKNOWN_VALUE;
335
+
336
+ public static <%= type.name %> fromGraphQl(String value) {
337
+ if (value == null) {
338
+ return null;
339
+ }
340
+
341
+ switch (value) {
342
+ <% type.enum_values.each do |value| %>
343
+ case "<%= value.name %>": {
344
+ return <%= value.upcase_name %>;
345
+ }
346
+ <% end %>
347
+ default: {
348
+ return UNKNOWN_VALUE;
349
+ }
350
+ }
351
+ }
352
+ public String toString() {
353
+ switch (this) {
354
+ <% type.enum_values.each do |value| %>
355
+ case <%= value.upcase_name %>: {
356
+ return "<%= value.name %>";
357
+ }
358
+ <% end %>
359
+ default: {
360
+ return "";
361
+ }
362
+ }
363
+ }
364
+ }
365
+ <% else %>
366
+ <% raise NotImplementedError, "unhandled #{type.kind} type #{type.name}" %>
367
+ <% end %>
368
+ <% end %>
369
+ }
@@ -0,0 +1,3 @@
1
+ class GraphQLJavaGen
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: graphql_java_gen
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dylan Thacker-Smith
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-02-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: graphql_schema
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.14'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.14'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '12.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '12.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.10'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.10'
69
+ - !ruby/object:Gem::Dependency
70
+ name: graphql
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.3'
83
+ description: Generates java code based on the GraphQL schema to provide type-safe
84
+ API for building GraphQL queries and using their responses.
85
+ email:
86
+ - gems@shopify.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - LICENSE.txt
92
+ - README.md
93
+ - codegen/lib/graphql_java_gen.rb
94
+ - codegen/lib/graphql_java_gen/annotation.rb
95
+ - codegen/lib/graphql_java_gen/reformatter.rb
96
+ - codegen/lib/graphql_java_gen/scalar.rb
97
+ - codegen/lib/graphql_java_gen/templates/APISchema.java.erb
98
+ - codegen/lib/graphql_java_gen/version.rb
99
+ homepage: https://github.com/Shopify/graphql_java_gen
100
+ licenses:
101
+ - MIT
102
+ metadata: {}
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - codegen/lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 2.1.0
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.6.10
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: GraphQL java client code generator
123
+ test_files: []