graphql_swift_gen 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d1bd610a6536380a8b7d44d7dd68f218977b122f
4
+ data.tar.gz: 4ef1d09670a2998b0003cebf2a7c42cf481b4936
5
+ SHA512:
6
+ metadata.gz: 489e21ce87384e75d5e795ebd37d5d2dc25556ecbd7791c8e6c9613c942154ebb3c67701eced6de4a0cbbcce5ca8edd9c2990beb1811baa9181176a10ae7849e
7
+ data.tar.gz: d89e560d4a5d20f27140db68b41a3beb182f3e176a9bb486bfa186e2a95ee1ec81bd101e857d9b972117a421d23eb10ddb6d3c14a0f754b874705261138326b9
@@ -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.
@@ -0,0 +1,108 @@
1
+ # GraphQLSwiftGen
2
+ [![Build Status](https://travis-ci.org/Shopify/graphql_swift_gen.svg?branch=master)](https://travis-ci.org/Shopify/graphql_swift_gen)
3
+
4
+ Generate swift code for any specific GraphQL schema that provides
5
+ query builders and response classes.
6
+
7
+ ## Installation
8
+
9
+ The code generator requires ruby version 2.1 or later.
10
+
11
+ Until this project is released, it is recommended to include it into
12
+ a project as a git submodule.
13
+
14
+ $ git submodule https://github.com/Shopify/graphql_swift_gen.git
15
+
16
+ It is recommended to use [bundler](http://bundler.io/) to install
17
+ the code generators ruby package.
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem 'graphql_swift_gen', path: 'graphql_swift_gen'
23
+ ```
24
+
25
+ And then execute:
26
+
27
+ $ bundle
28
+
29
+ The generated code depends on support/Sources/GraphQL.swift from
30
+ this repo which should be added to the project along with the
31
+ generated code.
32
+
33
+ ## Usage
34
+
35
+ Create a script that generates the code for a GraphQL API
36
+
37
+ ```ruby
38
+ require 'graphql_swift_gen'
39
+ require 'graphql_schema'
40
+ require 'json'
41
+
42
+ introspection_result = File.read("graphql_schema.json")
43
+ schema = GraphQLSchema.new(JSON.parse(introspection_result))
44
+
45
+ GraphQLSwiftGen.new(schema,
46
+ nest_under: 'ExampleSchema',
47
+ custom_scalars: [
48
+ GraphQLSwiftGen::Scalar.new(
49
+ type_name: 'Money',
50
+ swift_type: 'NSDecimalNumber',
51
+ deserialize_expr: ->(expr) { "NSDecimalNumber(string: #{expr}, locale: GraphQL.posixLocale)" },
52
+ serialize_expr: ->(expr) { "#{expr}.description(withLocale: GraphQL.posixLocale)" },
53
+ ),
54
+ ]
55
+ ).save("#{Dir.pwd}/../MyApp/Source")
56
+ ```
57
+
58
+ That generated code depends on the GraphQLSupport package.
59
+
60
+ The generated code includes a query builder that can be used to
61
+ create a GraphQL query in a type-safe mannar.
62
+
63
+ ```swift
64
+ let queryString = ExampleSchema.buildQuerya { $0
65
+ .user { $0
66
+ .firstName()
67
+ .lastName()
68
+ }
69
+ }
70
+ ```
71
+
72
+ The generated code also includes response classes that will deserialize the response
73
+ and provide methods for accessing the field data with it already coerced to the
74
+ correct type.
75
+
76
+ ```swift
77
+ guard let response = try? GraphQLResponse<ExampleSchema.QueryRoot>(jsonData: response) else {
78
+ print("Invalid GraphQL response")
79
+ return
80
+ }
81
+ if let errors = response.errors {
82
+ for error in errors {
83
+ print("GraphQL error: \(error.message)")
84
+ }
85
+ }
86
+ if let data = response.data {
87
+ let user = data.user
88
+ print("\(user.firstName) \(user.lastName)")
89
+ }
90
+ ```
91
+
92
+ ## Development
93
+
94
+ After checking out the repo, run `bundle` to install ruby dependencies.
95
+ Then, run `rake test` to run the tests.
96
+
97
+ To install this gem onto your local machine, run `bundle exec rake
98
+ install` or reference it from a Gemfile using the path option
99
+ (e.g. `gem 'graphql_swift_gen', path: '~/src/graphql_swift_gen'`).
100
+
101
+ ## Contributing
102
+
103
+ See our [contributing guidelines](CONTRIBUTING.md) for more information.
104
+
105
+ ## License
106
+
107
+ The gem is available as open source under the terms of the
108
+ [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,257 @@
1
+ require 'graphql_swift_gen/reformatter'
2
+ require 'graphql_swift_gen/scalar'
3
+
4
+ require 'erb'
5
+
6
+ class GraphQLSwiftGen
7
+ attr_reader :schema, :scalars, :script_name, :schema_name, :import_graphql_support
8
+
9
+ def initialize(schema, custom_scalars: [], nest_under:, script_name: 'graphql_swift_gen gem', import_graphql_support: false)
10
+ @schema = schema
11
+ @schema_name = nest_under
12
+ @script_name = script_name
13
+ @scalars = (BUILTIN_SCALARS + custom_scalars).reduce({}) { |hash, scalar| hash[scalar.type_name] = scalar; hash }
14
+ @scalars.default_proc = ->(hash, key) { DEFAULT_SCALAR }
15
+ @import_graphql_support = import_graphql_support
16
+ end
17
+
18
+ def save(path)
19
+ output = generate
20
+ begin
21
+ Dir.mkdir("#{path}/#{schema_name}")
22
+ rescue Errno::EEXIST
23
+ end
24
+ output.each do |relative_path, file_contents|
25
+ File.write("#{path}/#{relative_path}", file_contents)
26
+ end
27
+ end
28
+
29
+ def generate
30
+ output = {}
31
+ output["#{schema_name}.swift"] = generate_schema_file
32
+ schema.types.reject{ |type| type.name.start_with?('__') || type.scalar? }.each do |type|
33
+ output["#{schema_name}/#{type.name}.swift"] = generate_type(type)
34
+ end
35
+ output
36
+ end
37
+
38
+ private
39
+
40
+ class << self
41
+ private
42
+
43
+ def erb_for(template_filename)
44
+ path = File.expand_path("../graphql_swift_gen/templates/#{template_filename}", __FILE__)
45
+ erb = ERB.new(File.read(path))
46
+ erb.filename = path
47
+ erb
48
+ end
49
+ end
50
+
51
+ SCHEMA_ERB = erb_for("ApiSchema.swift.erb")
52
+ TYPE_ERB = erb_for("type.swift.erb")
53
+ private_constant :SCHEMA_ERB, :TYPE_ERB
54
+
55
+ DEFAULT_SCALAR = Scalar.new(type_name: nil, swift_type: 'String', json_type: 'String')
56
+ private_constant :DEFAULT_SCALAR
57
+
58
+ BUILTIN_SCALARS = [
59
+ Scalar.new(
60
+ type_name: 'Int',
61
+ swift_type: 'Int32',
62
+ json_type: 'Int',
63
+ deserialize_expr: ->(expr) { "Int32(#{expr})" },
64
+ ),
65
+ Scalar.new(
66
+ type_name: 'Float',
67
+ swift_type: 'Double',
68
+ json_type: 'Double',
69
+ ),
70
+ Scalar.new(
71
+ type_name: 'String',
72
+ swift_type: 'String',
73
+ json_type: 'String',
74
+ ),
75
+ Scalar.new(
76
+ type_name: 'Boolean',
77
+ swift_type: 'Bool',
78
+ json_type: 'Bool',
79
+ ),
80
+ Scalar.new(
81
+ type_name: 'ID',
82
+ swift_type: 'GraphQL.ID',
83
+ json_type: 'String',
84
+ serialize_expr: ->(expr) { "#{expr}.rawValue" },
85
+ deserialize_expr: ->(expr) { "GraphQL.ID(rawValue: #{expr})" },
86
+ ),
87
+ ]
88
+ private_constant :BUILTIN_SCALARS
89
+
90
+ # From: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html
91
+ RESERVED_WORDS = [
92
+ "associatedtype", "class", "deinit", "enum", "extension", "fileprivate", "func", "import", "init", "inout", "internal", "let", "open", "operator", "private", "protocol", "public", "static", "struct", "subscript", "typealias", "var",
93
+ "break", "case", "continue", "default", "defer", "do", "else", "fallthrough", "for", "guard", "if", "in", "repeat", "return", "switch", "where", "while",
94
+ "as", "Any", "catch", "false", "is", "nil", "rethrows", "super", "self", "Self", "throw", "throws", "true", "try",
95
+ "associativity", "convenience", "dynamic", "didSet", "final", "get", "infix", "indirect", "lazy", "left", "mutating", "none", "nonmutating", "optional", "override", "postfix", "precedence", "prefix", "Protocol", "required", "right", "set", "Type", "unowned", "weak", "willSet"
96
+ ]
97
+ private_constant :RESERVED_WORDS
98
+
99
+ def escape_reserved_word(word)
100
+ return word unless RESERVED_WORDS.include?(word)
101
+ return "`#{word}`"
102
+ end
103
+
104
+ def generate_schema_file
105
+ reformat(SCHEMA_ERB.result(binding))
106
+ end
107
+
108
+ def generate_type(type)
109
+ reformat(TYPE_ERB.result(binding))
110
+ end
111
+
112
+ def reformat(code)
113
+ Reformatter.new(indent: "\t").reformat(code)
114
+ end
115
+
116
+ def swift_input_type(type, non_null: false)
117
+ code = case type.kind
118
+ when 'NON_NULL'
119
+ return swift_input_type(type.of_type, non_null: true)
120
+ when 'SCALAR'
121
+ scalars[type.name].swift_type
122
+ when 'LIST'
123
+ "[#{swift_input_type(type.of_type, non_null: true)}]"
124
+ when 'INPUT_OBJECT', 'ENUM'
125
+ type.name
126
+ else
127
+ raise NotImplementedError, "Unhandled #{type.kind} input type"
128
+ end
129
+ code += "?" unless non_null
130
+ code
131
+ end
132
+
133
+ def swift_json_type(type, non_null: false)
134
+ if !non_null && !type.non_null?
135
+ return 'Any'
136
+ end
137
+ case type.kind
138
+ when "NON_NULL"
139
+ swift_json_type(type.of_type, non_null: true)
140
+ when 'SCALAR'
141
+ scalars[type.name].json_type
142
+ when 'OBJECT', 'INTERFACE', 'UNION'
143
+ "[String: Any]"
144
+ when 'LIST'
145
+ "[#{swift_json_type(type.of_type)}]"
146
+ when 'ENUM'
147
+ 'String'
148
+ else
149
+ raise NotImplementedError, "Unexpected #{type.kind} response type"
150
+ end
151
+ end
152
+
153
+ def swift_output_type(type, non_null: false)
154
+ code = case type.kind
155
+ when 'NON_NULL'
156
+ return swift_output_type(type.of_type, non_null: true)
157
+ when 'SCALAR'
158
+ scalars[type.name].swift_type
159
+ when 'LIST'
160
+ "[#{swift_output_type(type.of_type)}]"
161
+ when 'OBJECT', 'ENUM'
162
+ "#{schema_name}.#{type.name}"
163
+ when 'INTERFACE', 'UNION'
164
+ type.name
165
+ else
166
+ raise NotImplementedError, "Unhandled #{type.kind} response type"
167
+ end
168
+ code += "?" unless non_null
169
+ code
170
+ end
171
+
172
+ def generate_build_input_code(expr, type, wrap: true)
173
+ case type.kind
174
+ when 'SCALAR'
175
+ scalars[type.name].serialize_expr(expr)
176
+ when 'ENUM'
177
+ "\\(#{expr}.rawValue)"
178
+ when 'LIST'
179
+ map_block = generate_build_input_code('$0', type.of_type.unwrap_non_null)
180
+ map_code = map_block == '$0' ? expr : "#{expr}.map{ \"#{map_block}\" }"
181
+ elements = "#{map_code}.joined(separator: \",\")"
182
+ "[\\(#{elements})]"
183
+ when 'INPUT_OBJECT'
184
+ "\\(#{expr}.serialize())"
185
+ else
186
+ raise NotImplementedError, "Unexpected #{type.kind} argument type"
187
+ end
188
+ end
189
+
190
+ def deserialize_value_code(field_name, expr, type, untyped: true)
191
+ statements = ""
192
+
193
+ if untyped
194
+ json_type = swift_json_type(type.unwrap_non_null, non_null: true)
195
+ statements << "if #{expr} is NSNull { return nil }\n" unless type.non_null?
196
+ statements << <<-SWIFT
197
+ guard let value = #{expr} as? #{json_type} else {
198
+ throw SchemaViolationError(type: type(of: self), field: fieldName, value: fieldValue)
199
+ }
200
+ SWIFT
201
+ expr = 'value'
202
+ end
203
+ type = type.unwrap_non_null
204
+
205
+ statements << "return " + case type.kind
206
+ when 'SCALAR'
207
+ scalars[type.name].deserialize_expr(expr)
208
+ when 'LIST'
209
+ rethrow = "try " if %w(OBJECT INTERFACE UNION).include?(type.unwrap.kind)
210
+ "#{rethrow}#{expr}.map { #{deserialize_value_code(field_name, '$0', type.of_type, untyped: !type.of_type.non_null?)} }"
211
+ when 'OBJECT'
212
+ "try #{type.name}(fields: #{expr})"
213
+ when 'INTERFACE', 'UNION'
214
+ "try Unknown#{type.name}.create(fields: #{expr})"
215
+ when 'ENUM'
216
+ "#{escape_reserved_word(type.name)}(rawValue: #{expr}) ?? .unknownValue"
217
+ else
218
+ raise NotImplementedError, "Unexpected #{type.kind} argument type"
219
+ end
220
+ end
221
+
222
+ def swift_arg_defs(field)
223
+ defs = ["aliasSuffix: String? = nil"]
224
+ field.args.each do |arg|
225
+ arg_def = "#{escape_reserved_word(arg.name)}: #{swift_input_type(arg.type)}"
226
+ arg_def << " = nil" unless arg.type.non_null?
227
+ defs << arg_def
228
+ end
229
+ if field.subfields?
230
+ defs << "_ subfields: (#{field.type.unwrap.name}Query) -> Void"
231
+ end
232
+ defs.join(', ')
233
+ end
234
+
235
+ def generate_append_objects_code(expr, type, non_null: false)
236
+ if type.non_null?
237
+ non_null = true
238
+ type = type.of_type
239
+ end
240
+ unless non_null
241
+ return "if let value = #{expr} {\n#{generate_append_objects_code('value', type, non_null: true)}\n}"
242
+ end
243
+ return "#{expr}.forEach {\n#{generate_append_objects_code('$0', type.of_type)}\n}" if type.list?
244
+
245
+ abstract_response = type.object? ? expr : "#{expr} as! GraphQL.AbstractResponse"
246
+ "response.append(#{abstract_response})\n" \
247
+ "response.append(contentsOf: #{expr}.childResponseObjectMap())"
248
+ end
249
+
250
+ def swift_attributes(deprecatable)
251
+ return unless deprecatable.deprecated?
252
+ if deprecatable.deprecation_reason
253
+ message_argument = ", message:#{deprecatable.deprecation_reason.inspect}"
254
+ end
255
+ "@available(*, deprecated#{message_argument})"
256
+ end
257
+ end
@@ -0,0 +1,43 @@
1
+ class GraphQLSwiftGen
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,26 @@
1
+ class GraphQLSwiftGen
2
+ class Scalar
3
+ attr_reader :type_name, :swift_type, :json_type
4
+
5
+ def initialize(type_name:, swift_type:, json_type: 'String', serialize_expr: nil, deserialize_expr: nil)
6
+ @type_name = type_name
7
+ @swift_type = swift_type
8
+ @json_type = json_type
9
+ @serialize_expr = serialize_expr || ->(expr) { expr }
10
+ @deserialize_expr = deserialize_expr || ->(expr) { expr }
11
+ end
12
+
13
+ def serialize_expr(expr)
14
+ expr = @serialize_expr.call(expr)
15
+ if json_type == 'String'
16
+ expr = "\"\\(#{expr})\"" unless swift_type == 'String'
17
+ expr = "GraphQL.quoteString(input: #{expr})"
18
+ end
19
+ "\\(#{expr})"
20
+ end
21
+
22
+ def deserialize_expr(expr)
23
+ @deserialize_expr.call(expr)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ // Generated from <%= script_name %>
2
+
3
+ open class <%= schema_name %> {
4
+ <% [['Query', schema.query_root_name], ['Mutation', schema.mutation_root_name]].each do |operation_type, root_name| %>
5
+ open static func build<%= operation_type %>(_ subfields: (<%= root_name %>Query) -> Void) -> <%= root_name %>Query {
6
+ let root = <%= root_name %>Query()
7
+ subfields(root)
8
+ return root
9
+ }
10
+ <% end %>
11
+ }
@@ -0,0 +1,248 @@
1
+ // Generated from <%= script_name %>
2
+ import Foundation
3
+ <% if import_graphql_support %>
4
+ import GraphQLSupport
5
+ <% end %>
6
+
7
+ <% if type.interface? || type.union? %>
8
+ public protocol <%= type.name %> {
9
+ var typeName: String { get }
10
+ <% type.fields(include_deprecated: true).each do |field| %>
11
+ <%= swift_attributes(field) %>
12
+ var <%= escape_reserved_word(field.camelize_name) %>: <%= swift_output_type(field.type) %> { get }
13
+ <% end %>
14
+ func childResponseObjectMap() -> [GraphQL.AbstractResponse]
15
+
16
+ func responseObject() -> GraphQL.AbstractResponse
17
+ }
18
+ <% end %>
19
+
20
+ extension <%= schema_name %> {
21
+ <% case type.kind; when 'OBJECT', 'INTERFACE', 'UNION' %>
22
+ open class <%= type.name %>Query: GraphQL.AbstractQuery {
23
+ <% if type.name == schema.mutation_root_name %>
24
+ open override var description: String {
25
+ return "mutation" + super.description
26
+ }
27
+ <% end %>
28
+ <% type.fields(include_deprecated: true).each do |field| %>
29
+ <%= swift_attributes(field) %>
30
+ @discardableResult
31
+ open func <%= escape_reserved_word(field.camelize_name) %>(<%= swift_arg_defs(field) %>) -> <%= type.name %>Query {
32
+ <% unless field.args.empty? %>
33
+ var args: [String] = []
34
+ <% field.required_args.each do |arg| %>
35
+ args.append("<%= arg.name %>:<%= generate_build_input_code(arg.name, arg.type.unwrap_non_null) %>")
36
+ <% end %>
37
+ <% field.optional_args.each do |arg| %>
38
+ if let <%= escape_reserved_word(arg.name) %> = <%= escape_reserved_word(arg.name) %> {
39
+ args.append("<%= arg.name %>:<%= generate_build_input_code(arg.name, arg.type) %>")
40
+ }
41
+ <% end %>
42
+ <% if field.optional_args.empty? %>
43
+ let argsString = "(\(args.joined(separator: ",")))"
44
+ <% else %>
45
+ let argsString: String? = args.isEmpty ? nil : "(\(args.joined(separator: ",")))"
46
+ <% end %>
47
+ <% end %>
48
+
49
+ <% if field.subfields? %>
50
+ let subquery = <%= field.type.unwrap.name %>Query()
51
+ subfields(subquery)
52
+ <% end %>
53
+
54
+ addField(field: "<%= field.name %>", aliasSuffix: aliasSuffix<% unless field.args.empty? %>, args: argsString<% end %><% if field.subfields? %>, subfields: subquery<% end %>)
55
+ return self
56
+ }
57
+ <% end %>
58
+ <% unless type.object? %>
59
+ override init() {
60
+ super.init()
61
+ addField(field: "__typename")
62
+ }
63
+ <% type.possible_types.each do |possible_type| %>
64
+ @discardableResult
65
+ open func on<%= possible_type.name %>(subfields: (<%= possible_type.name %>Query) -> Void) -> <%= type.name %>Query {
66
+ let subquery = <%= possible_type.name %>Query()
67
+ subfields(subquery)
68
+ addInlineFragment(on: "<%= possible_type.name %>", subfields: subquery)
69
+ return self
70
+ }
71
+ <% end %>
72
+ <% end %>
73
+ }
74
+
75
+ <% class_name = type.object? ? type.name : "Unknown#{type.name}" %>
76
+ <% protocols = type.object? ? type.interfaces.map { |iface| ", #{iface.name}" }.join : ", #{type.name}" %>
77
+ open class <%= class_name %>: GraphQL.AbstractResponse<%= protocols %>
78
+ {
79
+ open override func deserializeValue(fieldName: String, value: Any) throws -> Any? {
80
+ let fieldValue = value
81
+ switch fieldName {
82
+ <% type.fields(include_deprecated: true).each do |field| %>
83
+ case "<%= field.name %>":
84
+ <%= deserialize_value_code(field.name, 'value', field.type) %>
85
+ <% end %>
86
+ default:
87
+ throw SchemaViolationError(type: type(of: self), field: fieldName, value: fieldValue)
88
+ }
89
+ }
90
+
91
+ <% if type.object? %>
92
+ open var typeName: String { return "<%= type.name %>" }
93
+ <% else %>
94
+ open var typeName: String { return field(field: "__typename") as! String }
95
+
96
+ open static func create(fields: [String: Any]) throws -> <%= type.name %> {
97
+ guard let typeName = fields["__typename"] as? String else {
98
+ throw SchemaViolationError(type: <%= class_name %>.self, field: "__typename", value: fields["__typename"] ?? NSNull())
99
+ }
100
+ switch typeName {
101
+ <% type.possible_types.each do |possible_type| %>
102
+ case "<%= possible_type.name %>":
103
+ return try <%= possible_type.name %>.init(fields: fields)
104
+ <% end %>
105
+ default:
106
+ return try <%= class_name %>.init(fields: fields)
107
+ }
108
+ }
109
+ <% end %>
110
+
111
+ <% type.fields(include_deprecated: true).each do |field| %>
112
+ <%= swift_attributes(field) %>
113
+ open var <%= escape_reserved_word(field.camelize_name) %>: <%= swift_output_type(field.type) %> {
114
+ return internalGet<%= field.classify_name %>()
115
+ }
116
+
117
+ <% unless field.args.empty? %>
118
+ <%= swift_attributes(field) %>
119
+ open func aliased<%= field.classify_name %>(aliasSuffix: String) -> <%= swift_output_type(field.type) %> {
120
+ return internalGet<%= field.classify_name %>(aliasSuffix: aliasSuffix)
121
+ }
122
+ <% end %>
123
+
124
+ func internalGet<%= field.classify_name %>(aliasSuffix: String? = nil) -> <%= swift_output_type(field.type) %> {
125
+ return field(field: "<%= field.name %>", aliasSuffix: aliasSuffix) as! <%= swift_output_type(field.type) %>
126
+ }
127
+ <% end %>
128
+
129
+ override open func childObjectType(key: String) -> GraphQL.ChildObjectType {
130
+ switch(key) {
131
+ <% type.fields(include_deprecated: true).each do |field| %>
132
+ case "<%= field.name %>":
133
+ <% if ['OBJECT', 'INTERFACE'].include?(field.type.unwrap_non_null.kind) %>
134
+ return .Object
135
+ <% elsif field.type.unwrap_non_null.kind == 'LIST' && ['OBJECT', 'INTERFACE'].include?(field.type.unwrap.kind) %>
136
+ return .ObjectList
137
+ <% elsif field.type.unwrap_non_null.kind == 'LIST' && field.type.unwrap.kind != 'OBJECT' %>
138
+ return .ScalarList
139
+ <% else %>
140
+ return .Scalar
141
+ <% end %>
142
+ <% end %>
143
+ default:
144
+ return .Scalar
145
+ }
146
+ }
147
+
148
+ override open func fetchChildObject(key: String) -> GraphQL.AbstractResponse? {
149
+ switch(key) {
150
+ <% type.fields(include_deprecated: true).each do |field| %>
151
+ <% if field.type.unwrap_non_null.kind == 'OBJECT' %>
152
+ case "<%= field.name %>":
153
+ return internalGet<%= field.classify_name %>()
154
+ <% elsif field.type.unwrap_non_null.kind == 'INTERFACE' %>
155
+ case "<%= field.name %>":
156
+ return internalGet<%= field.classify_name %>()<%= field.type.non_null? ? '' : '?' %>.responseObject()
157
+ <% end %>
158
+ <% end %>
159
+ default:
160
+ break
161
+ }
162
+ return nil
163
+ }
164
+
165
+ override open func fetchChildObjectList(key: String) -> [GraphQL.AbstractResponse] {
166
+ switch(key) {
167
+ <% type.fields(include_deprecated: true).each do |field| %>
168
+ <% if field.type.unwrap_non_null.kind == 'LIST' && field.type.unwrap.kind == 'OBJECT' %>
169
+ case "<%= field.name %>":
170
+ return internalGet<%= field.classify_name %>()<% if !field.type.non_null? %> ?? []<% end %>
171
+ <% end %>
172
+ <% end %>
173
+ default:
174
+ return []
175
+ }
176
+ }
177
+
178
+ open func childResponseObjectMap() -> [GraphQL.AbstractResponse] {
179
+ <% if type.fields(include_deprecated: true).any? { |field| ['OBJECT', 'INTERFACE'].include?(field.type.unwrap_non_null.kind) } %>
180
+ var response: [GraphQL.AbstractResponse] = []
181
+ objectMap.keys.forEach({
182
+ key in
183
+ switch(key) {
184
+ <% type.fields(include_deprecated: true).each do |field| %>
185
+ <% if %w(OBJECT INTERFACE UNION).include?(field.type.unwrap.kind) %>
186
+ case "<%= field.name %>":
187
+ <%= generate_append_objects_code("internalGet#{field.classify_name}()", field.type) %>
188
+ <% end %>
189
+ <% end %>
190
+ default:
191
+ break
192
+ }
193
+ })
194
+ return response
195
+ <% else %>
196
+ return []
197
+ <% end %>
198
+ }
199
+
200
+ open func responseObject() -> GraphQL.AbstractResponse {
201
+ return self as GraphQL.AbstractResponse
202
+ }
203
+ }
204
+ <% when 'INPUT_OBJECT' %>
205
+ open class <%= type.name %> {
206
+ <% type.input_fields.each do |field| %>
207
+ open var <%= escape_reserved_word(field.camelize_name) %>: <%= swift_input_type(field.type) %>
208
+ <% end %>
209
+
210
+ public init(
211
+ <% input_fields = type.required_input_fields + type.optional_input_fields %>
212
+ <% input_fields.each do |field| %>
213
+ <% default = field.type.non_null? ? "" : " = nil" %>
214
+ <% seperator = field == input_fields.last ? "" : "," %>
215
+ <%= escape_reserved_word(field.camelize_name) %>: <%= swift_input_type(field.type) %><%= default %><%= seperator %>
216
+ <% end %>
217
+ ) {
218
+ <% type.input_fields.each do |field| %>
219
+ self.<%= escape_reserved_word(field.camelize_name) %> = <%= escape_reserved_word(field.camelize_name) %>
220
+ <% end %>
221
+ }
222
+
223
+ func serialize() -> String {
224
+ var fields: [String] = []
225
+ <% type.input_fields.each do |field| %>
226
+ <% unless field.type.non_null? %>
227
+ if let <%= escape_reserved_word(field.camelize_name) %> = <%= escape_reserved_word(field.camelize_name) %> {
228
+ <% end %>
229
+ fields.append("<%= field.name %>:<%= generate_build_input_code(field.camelize_name, field.type.unwrap_non_null) %>")
230
+ <% unless field.type.non_null? %>
231
+ }
232
+ <% end %>
233
+ <% end %>
234
+ return "{\(fields.joined(separator: ","))}"
235
+ }
236
+ }
237
+ <% when 'ENUM' %>
238
+ public enum <%= type.name %>: String {
239
+ <% type.enum_values.each do |value| %>
240
+ <%= swift_attributes(value) %>
241
+ case <%= escape_reserved_word(value.camelize_name) %> = "<%= value.name %>"
242
+ <% end %>
243
+ case unknownValue = ""
244
+ }
245
+ <% else %>
246
+ <% raise NotImplementedError, "unhandled #{type.kind} type #{type.name}" %>
247
+ <% end %>
248
+ }
@@ -0,0 +1,3 @@
1
+ class GraphQLSwiftGen
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: graphql_swift_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-22 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.13'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.13'
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 swift 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_swift_gen.rb
94
+ - codegen/lib/graphql_swift_gen/reformatter.rb
95
+ - codegen/lib/graphql_swift_gen/scalar.rb
96
+ - codegen/lib/graphql_swift_gen/templates/ApiSchema.swift.erb
97
+ - codegen/lib/graphql_swift_gen/templates/type.swift.erb
98
+ - codegen/lib/graphql_swift_gen/version.rb
99
+ homepage: https://github.com/Shopify/graphql_swift_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 swift client code generator
123
+ test_files: []