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.
- 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
|
+
[](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: []
|