graphql_swift_gen 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +108 -0
- data/codegen/lib/graphql_swift_gen.rb +257 -0
- data/codegen/lib/graphql_swift_gen/reformatter.rb +43 -0
- data/codegen/lib/graphql_swift_gen/scalar.rb +26 -0
- data/codegen/lib/graphql_swift_gen/templates/ApiSchema.swift.erb +11 -0
- data/codegen/lib/graphql_swift_gen/templates/type.swift.erb +248 -0
- data/codegen/lib/graphql_swift_gen/version.rb +3 -0
- metadata +123 -0
checksums.yaml
ADDED
@@ -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
|
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,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
|
+
}
|
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: []
|