From 13f3d71cffaa2660216569c897a58b398e9d4e5b Mon Sep 17 00:00:00 2001 From: Dima Bart Date: Thu, 7 Sep 2017 14:08:43 -0400 Subject: [PATCH 1/2] 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 a62729f..6c452df 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 @@ -228,8 +235,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 3be81ab..f1f72b7 100644 --- a/codegen/lib/graphql_swift_gen/templates/type.swift.erb +++ b/codegen/lib/graphql_swift_gen/templates/type.swift.erb @@ -180,7 +180,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. /// @@ -190,11 +190,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 cd63ea6..1309d96 100644 --- a/support/Sources/GraphQL.swift +++ b/support/Sources/GraphQL.swift @@ -298,6 +298,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) From 58b7f983e8919f11dddf0e3e0483ba569477c5bd Mon Sep 17 00:00:00 2001 From: Dima Bart Date: Fri, 8 Sep 2017 13:57:38 -0400 Subject: [PATCH 2/2] Generate private initializer to avoid exposing the deprecated one. --- codegen/lib/graphql_swift_gen.rb | 84 +++++++++++++++++-- .../templates/type.swift.erb | 9 ++ 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/codegen/lib/graphql_swift_gen.rb b/codegen/lib/graphql_swift_gen.rb index 6c452df..8baf571 100644 --- a/codegen/lib/graphql_swift_gen.rb +++ b/codegen/lib/graphql_swift_gen.rb @@ -230,22 +230,90 @@ def deserialize_value_code(field_name, expr, type, untyped: true) end def generate_input_init(type) - text = "public init(" + text = "public static func create(" + 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, wrapped: true) + text << (field.type.non_null? ? "" : " = .undefined") + text << (field == input_fields.last ? "" : ", ") + end + + text << ") -> #{type.name} {" + text << "\n" + text << "return #{type.name}(" + text << input_fields.map { |field| + "#{field.name}: #{field.name}" + }.join(", ") + text << ")\n" + text << "}" + end + + def deprecated_input_init_required(type) + type.input_fields.each do |field| + unless field.type.non_null? + return true + end + end + false + end + + def generate_private_input_init(type) + text = "private 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, wrapped: true) + text << (field.type.non_null? ? "" : " = .undefined") + 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 generate_deprecated_input_init(type) + convenience = deprecated_input_init_required(type) ? "convenience " : "" + deprecation = deprecated_input_init_required(type) ? "@available(*, deprecated)\n" : "" + text = "#{deprecation}public #{convenience}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, wrapped: true) - text << (field.type.non_null? ? "" : " = .undefined") + text << swift_input_type(field.type, wrapped: false) + 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 + + if deprecated_input_init_required(type) + text << "self.init(" + text << input_fields.map { |field| + param = "#{field.name}: #{field.name}" + if !field.type.non_null? + param << ".orNull" + end + param + }.join(", ") + text << ")\n" + else + type.input_fields.each do |field| + name = escape_reserved_word(field.camelize_name) + text << "self." + name + " = " + name + if !field.type.non_null? + text << ".orNull" + end + text << "\n" + end + end text << "}" end diff --git a/codegen/lib/graphql_swift_gen/templates/type.swift.erb b/codegen/lib/graphql_swift_gen/templates/type.swift.erb index f1f72b7..b6a9855 100644 --- a/codegen/lib/graphql_swift_gen/templates/type.swift.erb +++ b/codegen/lib/graphql_swift_gen/templates/type.swift.erb @@ -186,6 +186,15 @@ extension <%= schema_name %> { /// <%= input_field_description(type) %> <%= generate_input_init(type) %> + + <% if deprecated_input_init_required(type) %> + <%= generate_private_input_init(type) %> + <% end %> + + /// Creates the input object. + /// + <%= input_field_description(type) %> + <%= generate_deprecated_input_init(type) %> internal func serialize() -> String { var fields: [String] = []