-
Notifications
You must be signed in to change notification settings - Fork 125
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
Use norman:"pointer" for nullable fields #400
Conversation
This is my other idea for fixing the issue we're having in the KEv2 operators: cmurphy@c685c62 - it's more direct about the problem, but I don't feel great about the extra attribute in the schema when it only has meaning in the generated client code. |
@ibuildthecloud thoughts on this? |
I like this solution. I think it create a little more cumbersome client behavior, but I don't really see another way around the nil/empty issue without writing custom unmarshalers. |
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.
LGTM
generator/generator.go
Outdated
@@ -46,6 +46,8 @@ func getTypeString(nullable bool, typeName string, schema *types.Schema, schemas | |||
return "map[string]" + getTypeString(false, typeName[len("map["):len(typeName)-1], schema, schemas) | |||
case strings.HasPrefix(typeName, "array["): | |||
return "[]" + getTypeString(false, typeName[len("array["):len(typeName)-1], schema, schemas) | |||
case strings.HasPrefix(typeName, "nullable"): | |||
return "*" + getTypeString(false, typeName[len("nullable"):len(typeName)], schema, schemas) |
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.
return "*" + getTypeString(false, typeName[len("nullable"):len(typeName)], schema, schemas) | |
return "*" + getTypeString(false, strings.TrimPrefix(typeName, "nullable"), schema, schemas) |
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.
done
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 did a PR for a similar issue I believe: https://github.com/rancher/norman/pull/382/files. It looks like maps could use the same fix in order to preserve the nil value. By reading the description for this PR it sounds similar but when reading the original issue it sounds like the problem is that omitting a value makes it convert to nil? Could I get some clarification on the issue?
@rmweir The problem happens for arrays too. That case is not getting hit because the JSON blob in the request is not just setting those fields to nil, it's omitting them entirely. So when the map is iterated over here, it never hits e.g. the
It's similar but it's a different layer in the parsing. Those array and map fields never run through the conversion because they're not even there. The result of that is the spec object gets created with those fields set to nil. |
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.
This is probably fine. Ideally we wouldn't have to say it's a map pointer but this fix has a lot less potential to create undesirable side effects in other places so I think it is worth it. Was going to say to leave a comment but by the time someone get's to this logic it will probably be self explanatory. I worry a bit about it being generalized, but null int and similar types are already supported right?
types/definition/definition.go
Outdated
return (strings.HasPrefix(fieldType, "map[") || strings.HasPrefix(fieldType, "nullablemap[")) && strings.HasSuffix(fieldType, "]") | ||
} | ||
|
||
func IsArrayType(fieldType string) bool { | ||
return strings.HasPrefix(fieldType, "array[") && strings.HasSuffix(fieldType, "]") | ||
return (strings.HasPrefix(fieldType, "array[") || strings.HasPrefix(fieldType, "nullablearray[")) && strings.HasSuffix(fieldType, "]") |
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.
It looks like the nullable logic applies to any type now? Should this logic be added to IsReferenceType
in that case?
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 guess it should, for the sake of completeness. I'm not 100% sure what the implications would be for generalizing it to the reference[]
type. It doesn't exactly map to a regular Go type like slices and maps, I can't picture a case where it would make sense to use a nullablereference[]
. But it has to be opted into so it shouldn't hurt anything.
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.
Maybe if a field is optional but otherwise should reference an existing type? If we don't intend for it to be used that way let's just remove it; removing it should cause an error to be returned when the type can't be parsed. We can always add the functionality if we end up wanting it. My bad.
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.
Removed it
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 worry a bit about it being generalized, but null int and similar types are already supported right?
These already fall into the regular case of being nullable primitives so there's no need to explicitly use a nullableint
type:
Lines 58 to 63 in e48df26
case "boolean": | |
name = "bool" | |
case "float": | |
name = "float64" | |
case "int": | |
name = "int64" |
Lines 99 to 101 in e48df26
if nullable { | |
return "*" + name | |
} |
types/definition/definition.go
Outdated
return (strings.HasPrefix(fieldType, "map[") || strings.HasPrefix(fieldType, "nullablemap[")) && strings.HasSuffix(fieldType, "]") | ||
} | ||
|
||
func IsArrayType(fieldType string) bool { | ||
return strings.HasPrefix(fieldType, "array[") && strings.HasSuffix(fieldType, "]") | ||
return (strings.HasPrefix(fieldType, "array[") || strings.HasPrefix(fieldType, "nullablearray[")) && strings.HasSuffix(fieldType, "]") |
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 guess it should, for the sake of completeness. I'm not 100% sure what the implications would be for generalizing it to the reference[]
type. It doesn't exactly map to a regular Go type like slices and maps, I can't picture a case where it would make sense to use a nullablereference[]
. But it has to be opted into so it shouldn't hurt anything.
types/definition/definition.go
Outdated
return (strings.HasPrefix(fieldType, "map[") || strings.HasPrefix(fieldType, "nullablemap[")) && strings.HasSuffix(fieldType, "]") | ||
} | ||
|
||
func IsArrayType(fieldType string) bool { | ||
return strings.HasPrefix(fieldType, "array[") && strings.HasSuffix(fieldType, "]") | ||
return (strings.HasPrefix(fieldType, "array[") || strings.HasPrefix(fieldType, "nullablearray[")) && strings.HasSuffix(fieldType, "]") |
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.
Maybe if a field is optional but otherwise should reference an existing type? If we don't intend for it to be used that way let's just remove it; removing it should cause an error to be returned when the type can't be parsed. We can always add the functionality if we end up wanting it. My bad.
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.
LGTM
Add a new field attribute "pointer" to indicate that the generated client code for the field must be a pointer. This allows clients to differentiate between sending nil/leaving the value unset and sending an empty map or slice. This change also removes the `nullablestring` norman type introduced in 30f8d18 since schemas that need a pointer to a string can now use this field attribute. There are no libraries currently using this feature so it should be safe to remove. Example usage: ``` Labels map[string]string `json:"labels" norman:"pointer"` ``` Resulting API schema: ``` "labels": { "create": true, "nullable": true, "pointer": true, "type": "map[string]", "update": true } ``` Generated client code: ``` Labels *map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` ```
After talking with @ibuildthecloud it made more sense to make this an attribute of the field instead of its type. It is still used to generate a pointer type, as opposed to messing with the Use in gke-operator: rancher/gke-operator#49 |
The approach is good. LGTM on the design. |
norman removed the 'nullablestring' type in favor of a 'pointer' attribute for field types[1]. In addition to string pointers, this can be used on maps and slices to indicate the generated client code should become a pointer, even if the original type was not. [1] rancher/norman#400
Add a new field attribute "pointer" to indicate that the generated
client code for the field must be a pointer. This allows clients to
differentiate between sending nil/leaving the value unset and sending an
empty map or slice.
This change also removes the
nullablestring
norman type introduced in30f8d18 since schemas that need a pointer to a string can now use this
field attribute. There are no libraries currently using this feature so
it should be safe to remove.
Example usage:
Resulting API schema:
Generated client code:
rancher/rancher#32553