graphql_java_gen 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []