From b387aa81437ff466dd4ec99b2c49a5173f7dbe48 Mon Sep 17 00:00:00 2001 From: Dima Bart Date: Wed, 3 May 2017 11:33:53 -0400 Subject: [PATCH 1/4] Convert needlessly public methods and properties to internal. --- support/Sources/GraphQL.swift | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/support/Sources/GraphQL.swift b/support/Sources/GraphQL.swift index 6daba5f..6457501 100644 --- a/support/Sources/GraphQL.swift +++ b/support/Sources/GraphQL.swift @@ -98,7 +98,7 @@ public class GraphQL { } } - public func addField(field: String, aliasSuffix: String? = nil, args: String? = nil, subfields: AbstractQuery? = nil) { + internal func addField(field: String, aliasSuffix: String? = nil, args: String? = nil, subfields: AbstractQuery? = nil) { var alias: String? = nil if let aliasSuffix = aliasSuffix { alias = "\(field)\(AbstractQuery.aliasSuffixSeparator)\(aliasSuffix)" @@ -106,12 +106,12 @@ public class GraphQL { addSelection(selection: Selection(field: field, alias: alias, args: args, subfields: subfields)) } - public func addInlineFragment(on object: String, subfields: AbstractQuery) { + internal func addInlineFragment(on object: String, subfields: AbstractQuery) { addSelection(selection: Selection(field: "... on \(object)", alias: nil, args: nil, subfields: subfields)) } } - public static func quoteString(input: String) -> String { + internal static func quoteString(input: String) -> String { var escaped = "\"" for c in input.unicodeScalars { switch c { @@ -137,7 +137,7 @@ public class GraphQL { open class AbstractResponse: CustomDebugStringConvertible { public var fields: [String: Any] - public var objectMap: [String : Any?] = [:] + internal var objectMap: [String : Any?] = [:] required public init(fields: [String: Any]) throws { self.fields = fields @@ -153,11 +153,11 @@ public class GraphQL { } } - open func deserializeValue(fieldName: String, value: Any) throws -> Any? { + internal func deserializeValue(fieldName: String, value: Any) throws -> Any? { throw SchemaViolationError(type: type(of: self), field: fieldName, value: value) } - public func field(field: String, aliasSuffix: String? = nil) -> Any? { + internal func field(field: String, aliasSuffix: String? = nil) -> Any? { var responseKey = field if let aliasSuffix = aliasSuffix { responseKey += "\(AbstractQuery.aliasSuffixSeparator)\(aliasSuffix)" @@ -172,19 +172,19 @@ public class GraphQL { return "<\(type(of: self)): \(fields)>" } - open func childObjectType(key: String) -> ChildObjectType { + internal func childObjectType(key: String) -> ChildObjectType { return .Unknown } - open func fetchChildObject(key: String) -> GraphQL.AbstractResponse? { + internal func fetchChildObject(key: String) -> GraphQL.AbstractResponse? { return nil } - open func fetchChildObjectList(key: String) -> [GraphQL.AbstractResponse] { + internal func fetchChildObjectList(key: String) -> [GraphQL.AbstractResponse] { return [] } - func fieldName(from key: String) -> String { + internal func fieldName(from key: String) -> String { if let range = key.range(of: AbstractQuery.aliasSuffixSeparator) { if key.distance(from: key.startIndex, to: range.lowerBound) > 0 { return key.substring(to: range.lowerBound) @@ -217,8 +217,8 @@ public class GraphQL { } public struct GraphQLResponse { - public let data: DataType? - public let errors: [GraphQL.ResponseError]? + internal let data: DataType? + internal let errors: [GraphQL.ResponseError]? public init(object: Any) throws { guard let rootDict = object as? [String: AnyObject] else { @@ -281,12 +281,12 @@ public struct SchemaViolationError: Error { } extension GraphQL.Selection: Equatable {} -public func==(lhs: GraphQL.Selection, rhs: GraphQL.Selection) -> Bool { +public func ==(lhs: GraphQL.Selection, rhs: GraphQL.Selection) -> Bool { return (lhs === rhs) || (lhs.field == rhs.field && lhs.alias == rhs.alias && lhs.args == rhs.args && lhs.subfields == rhs.subfields) } extension GraphQL.AbstractQuery: Equatable {} -public func==(lhs: GraphQL.AbstractQuery, rhs: GraphQL.AbstractQuery) -> Bool { +public func ==(lhs: GraphQL.AbstractQuery, rhs: GraphQL.AbstractQuery) -> Bool { return (lhs === rhs) || (lhs.selections == rhs.selections) } From b9d26ea804ff1b28cf62ec9e6ea14c1cc5e48b70 Mon Sep 17 00:00:00 2001 From: Dima Bart Date: Thu, 18 May 2017 15:10:07 -0400 Subject: [PATCH 2/4] Prune dead code, add doc comments, adjust naming and minor refactor. --- codegen/lib/graphql_swift_gen.rb | 102 +++++++++++- .../templates/ApiSchema.swift.erb | 26 ++- .../templates/type.swift.erb | 154 +++++++----------- support/Sources/GraphQL.swift | 4 + 4 files changed, 184 insertions(+), 102 deletions(-) diff --git a/codegen/lib/graphql_swift_gen.rb b/codegen/lib/graphql_swift_gen.rb index 6d179b1..c12b560 100644 --- a/codegen/lib/graphql_swift_gen.rb +++ b/codegen/lib/graphql_swift_gen.rb @@ -218,9 +218,45 @@ def deserialize_value_code(field_name, expr, type, untyped: true) raise NotImplementedError, "Unexpected #{type.kind} argument type" end end - + + def generate_input_init(type) + text = "public init(" + input_fields = type.required_input_fields + type.optional_input_fields + input_fields.each do |field| + text << escape_reserved_word(field.camelize_name) + text << ": " + text << swift_input_type(field.type) + text << (field.type.non_null? ? "" : " = nil") + text << (field == input_fields.last ? "" : ", ") + end + text << ")" + text << " {\n" + type.input_fields.each do |field| + name = escape_reserved_word(field.camelize_name) + text << "self." + name + " = " + name + text << "\n" + end + text << "}" + end + + def remove_linebreaks(text) + text.gsub("\n", " ") + end + + def input_field_description(type) + unless type.input_fields.count == 0 + text = "/// - parameters:" + "" + type.input_fields.each do |field| + description = (field.description.nil? ? "No description" : remove_linebreaks(field.description)) + text << "\n/// - " + field.name + ": " + description + end + text << "\n///" + text + end + end + def swift_arg_defs(field) - defs = ["aliasSuffix: String? = nil"] + defs = ["alias: String? = nil"] field.args.each do |arg| arg_def = "#{escape_reserved_word(arg.name)}: #{swift_input_type(arg.type)}" arg_def << " = nil" unless arg.type.non_null? @@ -242,9 +278,9 @@ def generate_append_objects_code(expr, type, non_null: false) end return "#{expr}.forEach {\n#{generate_append_objects_code('$0', type.of_type)}\n}" if type.list? - abstract_response = type.object? ? expr : "#{expr} as! GraphQL.AbstractResponse" + abstract_response = type.object? ? expr : "(#{expr} as! GraphQL.AbstractResponse)" "response.append(#{abstract_response})\n" \ - "response.append(contentsOf: #{expr}.childResponseObjectMap())" + "response.append(contentsOf: #{abstract_response}.childResponseObjectMap())" end def swift_attributes(deprecatable) @@ -252,6 +288,62 @@ def swift_attributes(deprecatable) if deprecatable.deprecation_reason message_argument = ", message:#{deprecatable.deprecation_reason.inspect}" end - "@available(*, deprecated#{message_argument})" + "@available(*, deprecated#{message_argument})\n" + end + + def swift_doc(element, include_args=true) + doc = '' + + unless element.description.nil? + description = element.description + description = wrap_text(description, '/// ') + doc << "\n\n" + description + end + + if include_args && element.respond_to?(:args) + if element.args.count > 0 + doc << "\n///\n" + doc << "/// - parameters:" + element.args.each do |arg| + doc << "\n" + doc << '/// - ' + arg.name + ': ' + (arg.description.nil? ? "No description" : format_arg_list(arg.description, 7)) + end + doc << "\n///" + end + end + doc + end + + def wrap_text(text, prefix, width=80) + container = '' + line = "" + prefix + + parts = text.split(" ") + parts.each do |part| + if line.length + part.length < width + line << part + line << ' ' + else + container << line + container << "\n" + line = "" + prefix + line << part + line << ' ' + end + end + + if line.length > 0 + container << line + end + container + end + + def format_arg_list(text, spacing) + parts = text.split("\n") + commented = parts.drop(1).map do |part| + "/// " + (" " * spacing) + part + end + commented.unshift(parts.first) + commented.join("\n") end end diff --git a/codegen/lib/graphql_swift_gen/templates/ApiSchema.swift.erb b/codegen/lib/graphql_swift_gen/templates/ApiSchema.swift.erb index 737fa91..9d68f5d 100644 --- a/codegen/lib/graphql_swift_gen/templates/ApiSchema.swift.erb +++ b/codegen/lib/graphql_swift_gen/templates/ApiSchema.swift.erb @@ -1,4 +1,28 @@ -// Generated from <%= script_name %> +// +// <%= schema_name %>.swift +// Buy +// +// Created by Shopify. +// Copyright (c) 2017 Shopify Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// open class <%= schema_name %> { <% [['Query', schema.query_root_name], ['Mutation', schema.mutation_root_name]].each do |operation_type, root_name| %> diff --git a/codegen/lib/graphql_swift_gen/templates/type.swift.erb b/codegen/lib/graphql_swift_gen/templates/type.swift.erb index cb9ab31..bb27e31 100644 --- a/codegen/lib/graphql_swift_gen/templates/type.swift.erb +++ b/codegen/lib/graphql_swift_gen/templates/type.swift.erb @@ -1,24 +1,48 @@ -// Generated from <%= script_name %> +// +// <%= type.name %>.swift +// Buy +// +// Created by Shopify. +// Copyright (c) 2017 Shopify Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + import Foundation <% if import_graphql_support %> import GraphQLSupport <% end %> <% if type.interface? || type.union? %> + <%= %> + <%= swift_doc(type) %> public protocol <%= type.name %> { - var typeName: String { get } <% type.fields(include_deprecated: true).each do |field| %> <%= swift_attributes(field) %> var <%= escape_reserved_word(field.camelize_name) %>: <%= swift_output_type(field.type) %> { get } <% end %> - func childResponseObjectMap() -> [GraphQL.AbstractResponse] - - func responseObject() -> GraphQL.AbstractResponse } <% end %> extension <%= schema_name %> { <% case type.kind; when 'OBJECT', 'INTERFACE', 'UNION' %> + <%= swift_doc(type) %> open class <%= type.name %>Query: GraphQL.AbstractQuery, GraphQLQuery { public typealias Response = <%= type.name %> @@ -28,8 +52,8 @@ extension <%= schema_name %> { } <% end %> <% type.fields(include_deprecated: true).each do |field| %> - <%= swift_attributes(field) %> - @discardableResult + <%= swift_doc(field) %> + <%= swift_attributes(field) %>@discardableResult open func <%= escape_reserved_word(field.camelize_name) %>(<%= swift_arg_defs(field) %>) -> <%= type.name %>Query { <% unless field.args.empty? %> var args: [String] = [] @@ -53,7 +77,7 @@ extension <%= schema_name %> { subfields(subquery) <% end %> - addField(field: "<%= field.name %>", aliasSuffix: aliasSuffix<% unless field.args.empty? %>, args: argsString<% end %><% if field.subfields? %>, subfields: subquery<% end %>) + addField(field: "<%= field.name %>", aliasSuffix: alias<% unless field.args.empty? %>, args: argsString<% end %><% if field.subfields? %>, subfields: subquery<% end %>) return self } <% end %> @@ -63,6 +87,7 @@ extension <%= schema_name %> { addField(field: "__typename") } <% type.possible_types.each do |possible_type| %> + <%= swift_doc(type) %> @discardableResult open func on<%= possible_type.name %>(subfields: (<%= possible_type.name %>Query) -> Void) -> <%= type.name %>Query { let subquery = <%= possible_type.name %>Query() @@ -76,10 +101,11 @@ extension <%= schema_name %> { <% class_name = type.object? ? type.name : "Unknown#{type.name}" %> <% protocols = type.object? ? type.interfaces.map { |iface| ", #{iface.name}" }.join : ", #{type.name}" %> + <%= swift_doc(type) %> open class <%= class_name %>: GraphQL.AbstractResponse, GraphQLObject<%= protocols %> { public typealias Query = <%= type.name %>Query - open override func deserializeValue(fieldName: String, value: Any) throws -> Any? { + internal override func deserializeValue(fieldName: String, value: Any) throws -> Any? { let fieldValue = value switch fieldName { <% type.fields(include_deprecated: true).each do |field| %> @@ -91,19 +117,14 @@ extension <%= schema_name %> { } } - <% if type.object? %> - open var typeName: String { return "<%= type.name %>" } - <% else %> - open var typeName: String { return field(field: "__typename") as! String } - - open static func create(fields: [String: Any]) throws -> <%= type.name %> { + <% unless type.object? %> + internal static func create(fields: [String: Any]) throws -> <%= type.name %> { guard let typeName = fields["__typename"] as? String else { throw SchemaViolationError(type: <%= class_name %>.self, field: "__typename", value: fields["__typename"] ?? NSNull()) } switch typeName { <% type.possible_types.each do |possible_type| %> - case "<%= possible_type.name %>": - return try <%= possible_type.name %>.init(fields: fields) + case "<%= possible_type.name %>": return try <%= possible_type.name %>.init(fields: fields) <% end %> default: return try <%= class_name %>.init(fields: fields) @@ -112,78 +133,28 @@ extension <%= schema_name %> { <% end %> <% type.fields(include_deprecated: true).each do |field| %> - <%= swift_attributes(field) %> + <%= swift_doc(field, false) %><%= swift_attributes(field) %> open var <%= escape_reserved_word(field.camelize_name) %>: <%= swift_output_type(field.type) %> { return internalGet<%= field.classify_name %>() } <% unless field.args.empty? %> <%= swift_attributes(field) %> - open func aliased<%= field.classify_name %>(aliasSuffix: String) -> <%= swift_output_type(field.type) %> { - return internalGet<%= field.classify_name %>(aliasSuffix: aliasSuffix) + open func aliased<%= field.classify_name %>(alias: String) -> <%= swift_output_type(field.type) %> { + return internalGet<%= field.classify_name %>(alias: alias) } <% end %> - func internalGet<%= field.classify_name %>(aliasSuffix: String? = nil) -> <%= swift_output_type(field.type) %> { - return field(field: "<%= field.name %>", aliasSuffix: aliasSuffix) as! <%= swift_output_type(field.type) %> + func internalGet<%= field.classify_name %>(alias: String? = nil) -> <%= swift_output_type(field.type) %> { + return field(field: "<%= field.name %>", aliasSuffix: alias) as! <%= swift_output_type(field.type) %> } <% end %> - override open func childObjectType(key: String) -> GraphQL.ChildObjectType { - switch(key) { - <% type.fields(include_deprecated: true).each do |field| %> - case "<%= field.name %>": - <% if ['OBJECT', 'INTERFACE'].include?(field.type.unwrap_non_null.kind) %> - return .Object - <% elsif field.type.unwrap_non_null.kind == 'LIST' && ['OBJECT', 'INTERFACE'].include?(field.type.unwrap.kind) %> - return .ObjectList - <% elsif field.type.unwrap_non_null.kind == 'LIST' && field.type.unwrap.kind != 'OBJECT' %> - return .ScalarList - <% else %> - return .Scalar - <% end %> - <% end %> - default: - return .Scalar - } - } - - override open func fetchChildObject(key: String) -> GraphQL.AbstractResponse? { - switch(key) { - <% type.fields(include_deprecated: true).each do |field| %> - <% if field.type.unwrap_non_null.kind == 'OBJECT' %> - case "<%= field.name %>": - return internalGet<%= field.classify_name %>() - <% elsif field.type.unwrap_non_null.kind == 'INTERFACE' %> - case "<%= field.name %>": - return internalGet<%= field.classify_name %>()<%= field.type.non_null? ? '' : '?' %>.responseObject() - <% end %> - <% end %> - default: - break - } - return nil - } - - override open func fetchChildObjectList(key: String) -> [GraphQL.AbstractResponse] { - switch(key) { - <% type.fields(include_deprecated: true).each do |field| %> - <% if field.type.unwrap_non_null.kind == 'LIST' && field.type.unwrap.kind == 'OBJECT' %> - case "<%= field.name %>": - return internalGet<%= field.classify_name %>()<% if !field.type.non_null? %> ?? []<% end %> - <% end %> - <% end %> - default: - return [] - } - } - - open func childResponseObjectMap() -> [GraphQL.AbstractResponse] { + internal override func childResponseObjectMap() -> [GraphQL.AbstractResponse] { <% if type.fields(include_deprecated: true).any? { |field| ['OBJECT', 'INTERFACE'].include?(field.type.unwrap_non_null.kind) } %> var response: [GraphQL.AbstractResponse] = [] - objectMap.keys.forEach({ - key in - switch(key) { + objectMap.keys.forEach { + switch($0) { <% type.fields(include_deprecated: true).each do |field| %> <% if %w(OBJECT INTERFACE UNION).include?(field.type.unwrap.kind) %> case "<%= field.name %>": @@ -193,37 +164,27 @@ extension <%= schema_name %> { default: break } - }) + } return response <% else %> return [] <% end %> } - - open func responseObject() -> GraphQL.AbstractResponse { - return self as GraphQL.AbstractResponse - } } <% when 'INPUT_OBJECT' %> + <%= swift_doc(type) %> open class <%= type.name %> { <% type.input_fields.each do |field| %> + + <%= swift_doc(field) %> open var <%= escape_reserved_word(field.camelize_name) %>: <%= swift_input_type(field.type) %> <% end %> + /// Creates the input object. + /// + <%= input_field_description(type) %> + <%= generate_input_init(type) %> - public init( - <% input_fields = type.required_input_fields + type.optional_input_fields %> - <% input_fields.each do |field| %> - <% default = field.type.non_null? ? "" : " = nil" %> - <% seperator = field == input_fields.last ? "" : "," %> - <%= escape_reserved_word(field.camelize_name) %>: <%= swift_input_type(field.type) %><%= default %><%= seperator %> - <% end %> - ) { - <% type.input_fields.each do |field| %> - self.<%= escape_reserved_word(field.camelize_name) %> = <%= escape_reserved_word(field.camelize_name) %> - <% end %> - } - - func serialize() -> String { + internal func serialize() -> String { var fields: [String] = [] <% type.input_fields.each do |field| %> <% unless field.type.non_null? %> @@ -238,10 +199,11 @@ extension <%= schema_name %> { } } <% when 'ENUM' %> + <%= swift_doc(type) %> public enum <%= type.name %>: String { <% type.enum_values.each do |value| %> - <%= swift_attributes(value) %> - case <%= escape_reserved_word(value.camelize_name) %> = "<%= value.name %>" + <%= swift_doc(value) %> + <%= swift_attributes(value) %>case <%= escape_reserved_word(value.camelize_name) %> = "<%= value.name %>" <% end %> case unknownValue = "" } diff --git a/support/Sources/GraphQL.swift b/support/Sources/GraphQL.swift index 6457501..f8d149f 100644 --- a/support/Sources/GraphQL.swift +++ b/support/Sources/GraphQL.swift @@ -192,6 +192,10 @@ public class GraphQL { } return key } + + internal func childResponseObjectMap() -> [GraphQL.AbstractResponse] { + fatalError() + } } public struct ResponseError { From 9329858e6e3c622f03d001bb435e0862dd708aad Mon Sep 17 00:00:00 2001 From: Dima Bart Date: Fri, 14 Jul 2017 15:26:24 -0400 Subject: [PATCH 3/4] Fix documentation comment generation for deprecated properties. --- codegen/lib/graphql_swift_gen/templates/type.swift.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codegen/lib/graphql_swift_gen/templates/type.swift.erb b/codegen/lib/graphql_swift_gen/templates/type.swift.erb index bb27e31..ba67c9b 100644 --- a/codegen/lib/graphql_swift_gen/templates/type.swift.erb +++ b/codegen/lib/graphql_swift_gen/templates/type.swift.erb @@ -133,8 +133,8 @@ extension <%= schema_name %> { <% end %> <% type.fields(include_deprecated: true).each do |field| %> - <%= swift_doc(field, false) %><%= swift_attributes(field) %> - open var <%= escape_reserved_word(field.camelize_name) %>: <%= swift_output_type(field.type) %> { + <%= swift_doc(field, false) %> + <%= swift_attributes(field) %>open var <%= escape_reserved_word(field.camelize_name) %>: <%= swift_output_type(field.type) %> { return internalGet<%= field.classify_name %>() } From 51a37254254284b2c341c2d2f2b027fc8719718f Mon Sep 17 00:00:00 2001 From: Dima Bart Date: Thu, 7 Sep 2017 14:08:43 -0400 Subject: [PATCH 4/4] Introduce InputValue for sending "null" values in resource updates. - add InputValue type and extensions - modify generation to use InputValue enums in serialize() - declare all optional values in Input objects at InputValue --- codegen/lib/graphql_swift_gen.rb | 19 +++++++---- .../templates/type.swift.erb | 14 ++++---- support/Sources/GraphQL.swift | 32 +++++++++++++++++++ 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/codegen/lib/graphql_swift_gen.rb b/codegen/lib/graphql_swift_gen.rb index c12b560..d820443 100644 --- a/codegen/lib/graphql_swift_gen.rb +++ b/codegen/lib/graphql_swift_gen.rb @@ -113,20 +113,27 @@ def reformat(code) Reformatter.new(indent: "\t").reformat(code) end - def swift_input_type(type, non_null: false) + def swift_input_type(type, non_null: false, wrapped: false) code = case type.kind when 'NON_NULL' - return swift_input_type(type.of_type, non_null: true) + return swift_input_type(type.of_type, non_null: true, wrapped: wrapped) when 'SCALAR' scalars[type.name].swift_type when 'LIST' - "[#{swift_input_type(type.of_type, non_null: true)}]" + "[#{swift_input_type(type.of_type, non_null: true, wrapped: wrapped)}]" when 'INPUT_OBJECT', 'ENUM' type.name else raise NotImplementedError, "Unhandled #{type.kind} input type" end - code += "?" unless non_null + + if wrapped + if !non_null + code = "InputValue<#{code}>" + end + else + code += "?" unless non_null + end code end @@ -225,8 +232,8 @@ def generate_input_init(type) input_fields.each do |field| text << escape_reserved_word(field.camelize_name) text << ": " - text << swift_input_type(field.type) - text << (field.type.non_null? ? "" : " = nil") + text << swift_input_type(field.type, wrapped: true) + text << (field.type.non_null? ? "" : " = .undefined") text << (field == input_fields.last ? "" : ", ") end text << ")" diff --git a/codegen/lib/graphql_swift_gen/templates/type.swift.erb b/codegen/lib/graphql_swift_gen/templates/type.swift.erb index ba67c9b..b6a9051 100644 --- a/codegen/lib/graphql_swift_gen/templates/type.swift.erb +++ b/codegen/lib/graphql_swift_gen/templates/type.swift.erb @@ -177,7 +177,7 @@ extension <%= schema_name %> { <% type.input_fields.each do |field| %> <%= swift_doc(field) %> - open var <%= escape_reserved_word(field.camelize_name) %>: <%= swift_input_type(field.type) %> + open var <%= escape_reserved_word(field.camelize_name) %>: <%= swift_input_type(field.type, wrapped: true) %> <% end %> /// Creates the input object. /// @@ -187,11 +187,13 @@ extension <%= schema_name %> { internal func serialize() -> String { var fields: [String] = [] <% type.input_fields.each do |field| %> - <% unless field.type.non_null? %> - if let <%= escape_reserved_word(field.camelize_name) %> = <%= escape_reserved_word(field.camelize_name) %> { - <% end %> - fields.append("<%= field.name %>:<%= generate_build_input_code(field.camelize_name, field.type.unwrap_non_null) %>") - <% unless field.type.non_null? %> + <% if field.type.non_null? %> + fields.append("<%= field.name %>:<%= generate_build_input_code(field.camelize_name, field.type.unwrap_non_null) %>") + <% else %> + switch <%= escape_reserved_word(field.camelize_name) %> { + case .some(let <%= escape_reserved_word(field.camelize_name) %>): fields.append("<%= field.name %>:<%= generate_build_input_code(field.camelize_name, field.type.unwrap_non_null) %>") + case .null: fields.append("<%= field.name %>:null") + case .undefined: break } <% end %> <% end %> diff --git a/support/Sources/GraphQL.swift b/support/Sources/GraphQL.swift index f8d149f..9d9b741 100644 --- a/support/Sources/GraphQL.swift +++ b/support/Sources/GraphQL.swift @@ -284,6 +284,38 @@ public struct SchemaViolationError: Error { } } +public enum InputValue { + case some(T) + case null + case undefined + + public init(orNull optional: Optional) { + if let value = optional { + self = .some(value) + } else { + self = .null + } + } + + public init(orUndefined optional: Optional) { + if let value = optional { + self = .some(value) + } else { + self = .undefined + } + } +} + +public extension Optional { + public var orNull: InputValue { + return InputValue(orNull: self) + } + + public var orUndefined: InputValue { + return InputValue(orUndefined: self) + } +} + extension GraphQL.Selection: Equatable {} public func ==(lhs: GraphQL.Selection, rhs: GraphQL.Selection) -> Bool { return (lhs === rhs) || (lhs.field == rhs.field && lhs.alias == rhs.alias && lhs.args == rhs.args && lhs.subfields == rhs.subfields)