-
Notifications
You must be signed in to change notification settings - Fork 27
Make explicit NULL easy – and break less API #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -214,36 +214,41 @@ extension <%= schema_name %> { | |
<% when 'INPUT_OBJECT' %> | ||
open class <%= type.name %> { | ||
<% type.required_input_fields.each do |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, non_null: true) %> | ||
<% end %> | ||
<% type.optional_input_fields.each do |field| %> | ||
|
||
open var <%= escape_reserved_word(field.camelize_name) %>: <%= swift_input_type(field.type) %> { | ||
didSet { | ||
<%= escape_reserved_word(field.camelize_name) %>Seen = true | ||
serialize<%= escape_reserved_word(field.classify_name) %> = true | ||
} | ||
} | ||
private var <%= escape_reserved_word(field.camelize_name) %>Seen: Bool = false | ||
// Observes if the <%= escape_reserved_word(field.camelize_name) %> property was set, and indicates whether it should be serialized. | ||
open var serialize<%= escape_reserved_word(field.classify_name) %>: Bool | ||
<% end %> | ||
|
||
public init( | ||
<% input_fields = type.required_input_fields + type.optional_input_fields # manually ordered with required fields first %> | ||
<% input_fields.each do |field| %> | ||
<% seperator = field == input_fields.last ? "" : "," %> | ||
<% separator = field == input_fields.last ? "" : "," %> | ||
<% if field.type.non_null? %> | ||
<%= escape_reserved_word(field.camelize_name) %>: <%= swift_input_type(field.type) %><%= seperator %> | ||
<%= escape_reserved_word(field.camelize_name) %>: <%= swift_input_type(field.type, non_null: true) %><%= separator %> | ||
<% else %> | ||
<%= escape_reserved_word(field.camelize_name) %>: <%= swift_input_type(field.type) %>? = nil<%= seperator %> | ||
<%= escape_reserved_word(field.camelize_name) %>: <%= swift_input_type(field.type) %> = nil, | ||
serialize<%= escape_reserved_word(field.classify_name) %>: Bool = false<%= separator %> | ||
<% end %> | ||
<% end %> | ||
) { | ||
<% type.required_input_fields.each do |field| %> | ||
self.<%= escape_reserved_word(field.camelize_name) %> = <%= escape_reserved_word(field.camelize_name) %> | ||
<% end %> | ||
<% type.optional_input_fields.each do |field| %> | ||
self.serialize<%= escape_reserved_word(field.classify_name) %> = serialize<%= escape_reserved_word(field.classify_name) %> | ||
<% field_name = escape_reserved_word(field.camelize_name) %> | ||
if let <%= field_name %> = <%= field_name %> { | ||
self.<%= field_name %>Seen = true | ||
self.<%= field_name %> = <%= field_name %> | ||
self.serialize<%= escape_reserved_word(field.classify_name) %> = true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, |
||
} | ||
<% end %> | ||
} | ||
|
@@ -254,7 +259,7 @@ extension <%= schema_name %> { | |
fields.append("<%= field.name %>:<%= generate_build_input_code(field.camelize_name, field.type.unwrap_non_null) %>") | ||
<% end %> | ||
<% type.optional_input_fields.each do |field| %> | ||
if <%= escape_reserved_word(field.camelize_name) %>Seen { | ||
if serialize<%= escape_reserved_word(field.classify_name) %> { | ||
if let <%= escape_reserved_word(field.camelize_name) %> = <%= escape_reserved_word(field.camelize_name) %> { | ||
fields.append("<%= field.name %>:<%= generate_build_input_code(field.camelize_name, field.type.unwrap_non_null) %>") | ||
} else { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What determines the set of required vs optional fields?
Do I understand correctly that
optional
means "field can be omitted from the mutation" rather than "field value is nullable", and thatrequired
means "field must be present in the mutation, whether it's nullable or not"?If that's correct, isn't the change below this forcing all
required
fields to be non-nullable as well?Would love some code documentation around this to clarify the implications.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The GraphQL schema doesn't distinguish between "field can be omitted from the mutation" rather than "field value is nullable". The name
optional
used to be more appropriate before graphql allowed anull
input value to be specified. Sorequired
actually means the field is non-null in schema andoptional
that it isn't non-null in the schema. So it is correct for arequired
field to also no be nullable.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In GraphQL there is no distinction between "the field can be omitted from the mutation" and "the field value is nullable". They are one and the same (in the type system) so there is no way we can tell the difference.
In practice you have fields that are implicitly required but in GraphQL are specified as nullable (not very nice right?). For example in a ProductInput the
title
property is nullable in GraphQL but if you tried to update a product with a null title we would send you auserError
.And yes the change below is forcing all required fields to be non nullable because if you make a mutation without a required field the mutation will always fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I replied saying the same thing as Dylan 😂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about update mutations that omit the
title
field altogether? I thought that was allowed, and was what we wanted, so that we wouldn't overwrite fields that we didn't modify ourselves but that might have been changed by another mutation.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is still possible with this PR. If you want to omit the title field all together you can do it multiple ways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought
title
was a required field, and so wasn't generatingserializeTitle
at all? 😕Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry in the example I was treating
title
as a non required field. If Title were a required field it would have to be sent to the server.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, after an in-person discussion with @BenEmdon I have a better handle on the rules now:
optional fields are nillable and expose
serialize{Field}
.required fields are non-nil and do not expose
serialize{Field}
.fields can only be required on Input objects that are creation-only.
e.g.
FulfillmentInput
Input objects that are used for both creation and update need all their fields marked as optional so that we can express minimal changesets.
e.g.
ProductInput
If a creation mutation omits a field which is optional, but which is implicitly required for creation, the mutation will fail with a user error.
e.g. trying to create a product with a
ProductInput
that has notitle
If an update mutation omits a field which is optional, but which is implicitly required for update, then the mutation will also fail with a user error (even though this is really a programmer error.)
e.g. trying to edit an existing product with a
ProductInput
that has noid
(This example will be solved once we refactor update mutations to pass
id
as a separate parameter instead of making it part of the Input object.)