-
-
Notifications
You must be signed in to change notification settings - Fork 954
Allow generating nullable.Nullable
for nullable properties
#1404
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
Allow generating nullable.Nullable
for nullable properties
#1404
Conversation
79d1b49
to
2c4fc12
Compare
952ab96
to
60ceb79
Compare
internal/test/go.mod
Outdated
@@ -13,6 +13,7 @@ require ( | |||
github.com/gorilla/mux v1.8.0 | |||
github.com/kataras/iris/v12 v12.2.6-0.20230908161203-24ba4e8933b9 | |||
github.com/labstack/echo/v4 v4.11.3 | |||
github.com/oapi-codegen/nullable v1.0.0 |
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've pushed a new release (docs changes only) so worth updating too:
github.com/oapi-codegen/nullable v1.0.0 | |
github.com/oapi-codegen/nullable v1.0.1 |
pkg/codegen/schema.go
Outdated
if !p.Schema.SkipOptionalPointer && | ||
(!p.Required || p.Nullable || | ||
(p.ReadOnly && (!p.Required || !globalState.options.Compatibility.DisableRequiredReadOnlyAsPointer)) || | ||
p.WriteOnly) { | ||
|
||
typeDef = "*" + typeDef | ||
} | ||
|
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.
needed?
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.
No. Wondering if this gets caught by the linter? I remember running make lint
.
I will remove it anyways.
|
||
gotJSON, err := json.Marshal(updateReq) | ||
require.NoError(t, err) | ||
fmt.Println(string(gotJSON)) |
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.
fmt.Println(string(gotJSON)) |
|
||
gotJSON2, err := json.Marshal(updateReq2) | ||
require.NoError(t, err) | ||
fmt.Println(string(gotJSON2)) |
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.
fmt.Println(string(gotJSON2)) |
pkg/codegen/schema.go
Outdated
@@ -681,10 +685,15 @@ func GenFieldsFromProperties(props []Property) []string { | |||
|
|||
field += fmt.Sprintf(" %s %s", goFieldName, p.GoTypeDef()) | |||
|
|||
omitEmpty := !p.Nullable && | |||
(!p.Required || p.ReadOnly || p.WriteOnly) && | |||
commonConditions := (!p.Required || p.ReadOnly || p.WriteOnly) && |
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.
Not sure I like this name - maybe shouldOmitEmpty
?
func TestIssue(t *testing.T) { | ||
swagger, err := openapi3.NewLoader().LoadFromData(spec) | ||
require.NoError(t, err) | ||
|
||
opts := codegen.Configuration{ | ||
PackageName: "issue1039", | ||
Generate: codegen.GenerateOptions{ | ||
ChiServer: true, | ||
Client: true, | ||
Models: true, | ||
EmbeddedSpec: true, | ||
NullableType: true, | ||
}, | ||
} | ||
|
||
_, err = codegen.Generate(swagger, opts) | ||
require.NoError(t, err) | ||
} |
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.
Was this just to check that generating the code works? If so, this shouldn't be needed - by having the go:generate
s in this package, that's good enough, unless there's something else you're trying to test?
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.
Yes. 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.
Is this the minimal spec we could use? If we can trim it down it'd be appreciated, so it's clear for long-term maintenance what's actually important about this
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.
Sure. Let me look into this.
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.
Update the spec.
750f993
to
ec6ff58
Compare
pkg/codegen/configuration.go
Outdated
@@ -35,6 +35,7 @@ type GenerateOptions struct { | |||
Client bool `yaml:"client,omitempty"` // Client specifies whether to generate client boilerplate | |||
Models bool `yaml:"models,omitempty"` // Models specifies whether to generate type definitions | |||
EmbeddedSpec bool `yaml:"embedded-spec,omitempty"` // Whether to embed the swagger spec in the generated code | |||
NullableType bool `yaml:"nullable-type,omitempty"` // Whether to generate nullable type for nullable fields |
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, would this be able to move to the OutputOptions
? That's a better place for it
func ToStringPointer(str string) *string { | ||
return &str | ||
} |
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.
nit: but doesn't need to be exported:
func ToStringPointer(str string) *string { | |
return &str | |
} | |
func toStringPointer(str string) *string { | |
return &str | |
} |
func ToStringPointer(str string) *string { | ||
return &str | ||
} |
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.
alternatively with generics:
func ToStringPointer(str string) *string { | |
return &str | |
} | |
func ptr[T any](v T) *T { | |
return &v | |
} |
pkg/codegen/schema_test.go
Outdated
@@ -213,3 +213,243 @@ func TestProperty_GoTypeDef(t *testing.T) { | |||
}) | |||
} | |||
} | |||
|
|||
func TestPropertyNullable_GoTypeDef(t *testing.T) { |
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 test should be collapsed in with the above, no? At the very least:
func TestPropertyNullable_GoTypeDef(t *testing.T) { | |
func TestProperty_GoTypeDef_nullable(t *testing.T) { |
To indicate what it's testing
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.
Couple of comments, and just want to make sure that we've made sure this is the minimal set of specs + tests needed to validate the functionality?
Draft release notes: Notable features
|
// exclude some fields in patch request | ||
patchReq2 := issue1039nullable.PatchRequest{ | ||
BreakoutGroup: &issue1039nullable.BreakoutGroup{ | ||
Name: ToStringPointer("test-bk"), | ||
}, | ||
Organization: &issue1039nullable.Organization{ | ||
AliasName: ToStringPointer("org-alias-name"), | ||
Name: ToStringPointer("org-name"), | ||
}, | ||
Popularity: &popularity, | ||
} |
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.
// exclude some fields in patch request | |
patchReq2 := issue1039nullable.PatchRequest{ | |
BreakoutGroup: &issue1039nullable.BreakoutGroup{ | |
Name: ToStringPointer("test-bk"), | |
}, | |
Organization: &issue1039nullable.Organization{ | |
AliasName: ToStringPointer("org-alias-name"), | |
Name: ToStringPointer("org-name"), | |
}, | |
Popularity: &popularity, | |
} | |
// omit some fields | |
patchReq2 := issue1039nullable.PatchRequest{ | |
BreakoutGroup: &issue1039nullable.BreakoutGroup{ | |
Name: ToStringPointer("test-bk"), | |
}, | |
// HomeCountry is omitted | |
Organization: &issue1039nullable.Organization{ | |
AliasName: ToStringPointer("org-alias-name"), | |
Name: ToStringPointer("org-name"), | |
}, | |
Popularity: &popularity, | |
// SearchBox is omitted | |
} |
assert.Falsef(t, obj.BreakoutGroup.IsNull(), "breakout group should not be null") | ||
|
||
// check for non-nullable field | ||
assert.Truef(t, obj.HomeCountry == nil, "home country should be 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.
assert.Truef(t, obj.HomeCountry == nil, "home country should be nil") | |
assert.Nilf(t, obj.HomeCountry, "home country should be nil") |
embedded-spec: true | ||
output-options: | ||
skip-prune: true | ||
output: nullable-disabled/types.gen.go |
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 think this would be clearer if this and the other package were named differently - nullabledisabled
is a bit unclear at a first glance.
Maybe defaultbehaviour
?
return &str | ||
} | ||
|
||
func TestNullableDisabled(t *testing.T) { |
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'd also recommend moving this into the package with the default behaviour, or making it clearer what this is - it was a little confusing reading this one first, then seeing the new behaviour - maybe we should keep this next to the existing behaviour's generated code?
BreakoutGroup nullable.Nullable[BreakoutGroup] `json:"breakout_group"` | ||
|
||
// HomeCountry Home country of the user. | ||
HomeCountry *HomeCountry `json:"home_country,omitempty"` |
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.
At first glance of the tests, I wasn't sure why this was nil
It may be worth us tweaking the spec to make the property names clearer what they're testing - i.e. this could be OptionalButNotNullableProperty
then it's super clear what's being tested (and why it's being checked for nil
instead of expecting a nullable.Nullable
)
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 a very good point. Let me do that
}, | ||
|
||
{ | ||
name: "when search box is null and organization has non zero value", |
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.
If this is the key part of what we're testing, I'm not sure we should also be checking i.e. the Popularity
, etc?
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 have checking that to ensure that because the other fields are not provided -- it should be basically not set and not null. But I think now when I am reading your comment, probably, we do not need to duplicate this in every test 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.
The empty test case should already cover that and I will remove this
// check for nullable field | ||
// check for nullable fields |
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.
duplicate?
internal/test/issues/issue-1039/defaultbehaviour/defaultbehaviour_test.go
Show resolved
Hide resolved
old-aliasing: true | ||
old-merge-schemas: true | ||
disable-flatten-additional-properties: true |
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.
are these definitely required to make this a minimal reproduction case?
package: defaultbehaviour | ||
generate: | ||
models: true | ||
embedded-spec: true |
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.
not needed any more?
package: issue1039 | ||
generate: | ||
models: true | ||
embedded-spec: true |
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.
as above
compatibility: | ||
old-aliasing: true | ||
old-merge-schemas: true | ||
disable-flatten-additional-properties: true |
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.
as above
old-aliasing: true | ||
old-merge-schemas: true |
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.
needed?
compatibility: | ||
old-aliasing: true | ||
old-merge-schemas: true |
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.
needed?
AliasName: nullable.Nullable[string]{ | ||
true: "foo-alias", | ||
}, |
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'd prefer to avoid the implementation details leaking - please use NewNullableWithValue
func TestNullableTypesMarshal(t *testing.T) { | ||
// include all fields in patch request | ||
patchReq := PatchRequest{ | ||
ComplexOptionalNullable: nullable.Nullable[ComplexOptionalNullable]{ |
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.
can the order of fields in this function mirror what's in the other file? Will make it easier to diff
2f4e90b
to
224d94f
Compare
As part of oapi-codegen#1039, we've created a new library `oapi-codegen/nullable`, which allows tracking whether: - a field is not sent - a field is sent with an explicit `null` - a field is sent with an explicit value This introduces an opt-in `output-options` flag, `nullable-type`, which can generate the `nullable.Nullable` types. This is opt-in, as existing code will break due to the signature change, as well as a behaviour change. Closes oapi-codegen#1039. Co-authored-by: Ashutosh Kumar <ashutosh.kumar@elastic.co>
224d94f
to
bc97778
Compare
nullable.Nullable
for nullable properties
This PR adds capability to use Nullable types via opt in flag for Nullable types
Ref Issue: #1039
Maintainer edit: Release notes