Skip to content
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

Omit setting grpc fields to zero defaults #3119

Merged
merged 1 commit into from Aug 10, 2022

Conversation

MichaelUrman
Copy link
Contributor

The expected behavior for zero-valued defaults bothered me; it needlessly compared the proto message fields against zeros only to set the goa fields to zeros. This PR explicitly tests a variety of built-in types and avoids the compare and set.

It's possible the isNonZero would be better written with reflect, to reduce its code. Or moved to, say, expr.AttributeType, so that we could instead write something like this in protobuf_transform:

- if tdef := tgtc.DefaultValue; tdef != nil && ta.TargetCtx.UseDefault && isNonZero(tdef) {
+ if tgtc.HasNonzeroDefault() && ta.TargetCtx.UseDefault {
+ 	tdef := tgtc.DefaultValue

@MichaelUrman
Copy link
Contributor Author

MichaelUrman commented Aug 8, 2022

I was slightly surprised to learn the following code issues an error. Should I file a bug report, and/or attempt to address it?

    		Field(14, "typed", Int64, func() {
			Meta("struct:field:type", "time.Duration", "time")
			Default(0 * time.Second)
		})

testing.go:18: [testdata/dsls.go:932] default value 0 is incompatible with attribute of type int64 in attribute

Namely it appears that a default of time.Duration(0) is not accepted, despite the field type being overridden to time.Duration. (In the meantime, it seems that isNonZero doesn't have to verify underlying types.)

@MichaelUrman
Copy link
Contributor Author

While writing this I had several misgivings over the new-ish defaults behavior in gRPC at all. On the one hand, it means that setting a nonzero default makes it impossible to send a zero value over gRPC. On the other hand, without it a nonzero default silently does something you may not expect. I don't have a good resolution to offer here, but suspect we should err towards allowing it, and just clarify the documentation for dsl.Default(). If there's enough context to prove an unexpected interaction, perhaps issue a warning, or create a linter for this.

I'm assuming any of that would be better off in a separate issue or PR. Let me know if you think it should be here.

@raphael
Copy link
Member

raphael commented Aug 8, 2022

The PR looks good - thank you. Could you please rebase on the latest v3?

As you probably know the challenge with gRPC is that the encoding doesn't support the notion of "missing" vs "has default value" since primitive values used in a gRPC message cannot be nil. We had a proposal to tackle this explicitly: #2221 - however this never got implemented. The proposal allows designers to define the desired behavior (i.e., allow or prevent a primitive field to have a zero value).

@MichaelUrman
Copy link
Contributor Author

Rebased.

Yes, the missing means zero, zero means default, etc. as covered on 2221 is why I was surprised to see missing mapped to defaults in v3. I must have missed the fanfare when it was introduced. :)

Any thoughts on the inability to specify a default value of the type specified by Meta("struct:field:type")? Since the error seems to come from Default(), which presumably can't see a later Meta call, I expect at best a non-trivial fix.

@raphael
Copy link
Member

raphael commented Aug 8, 2022

Wrt specifying default values for types equipped with Meta("struct:field:type") - Maybe there's a way to improve IsCompatible to look for that metadata and use reflection to figure out that the name of the Go literal value matches the name provided to Meta. See for example

func (p Primitive) IsCompatible(val interface{}) bool {
(every type implements this method).

@MichaelUrman
Copy link
Contributor Author

I don't think IsCompatible has enough information. Maybe you can use eval.Current().(*expr.AttributeExpr) like its caller dsls.Default, but either location only works if Meta("struct:field:type", ...) precedes Default(...). To become order-independent, we have to defer verification, say until after dsl.Attribute's call to eval.Execute. This alters error reporting, at least for imported types. (I chose to still validate defaults of builtin types in the original location.)

- testing.go:18: [testdata/dsls.go:935] default value "" is incompatible with attribute of type int64 in attribute
+ testing.go:18: [../../dsl/attribute.go:236] default value _ is incompatible with attribute of type int64 in attribute

The attempt did find one existing error worth fixing. I'm not satisfied with this approach yet so have kept it off the PR for now: f4b5a44

@raphael
Copy link
Member

raphael commented Aug 10, 2022

Yeah the type compatibility check for default values would have to be moved to the validation step of the DSL execution, probably here: https://github.com/goadesign/goa/blob/v3/expr/attribute.go#L189 (and it probably should have been there from the get go). Thank you again for this PR, merged!

@raphael raphael merged commit c9a9e21 into goadesign:v3 Aug 10, 2022
@MichaelUrman MichaelUrman deleted the grpc-zero-default branch September 27, 2022 18:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants