graphql_swift_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.
@@ -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: []