-
Notifications
You must be signed in to change notification settings - Fork 881
feat: Add codersdk.NullTime
, change workspace build deadline
#3552
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
Conversation
// ping @johnstcn, what do you think about this approach? |
a7c0920
to
43fbbea
Compare
:chefs-kiss: |
@@ -50,7 +50,7 @@ type WorkspaceBuild struct { | |||
InitiatorID uuid.UUID `json:"initiator_id"` | |||
InitiatorUsername string `json:"initiator_name"` | |||
Job ProvisionerJob `json:"job"` | |||
Deadline time.Time `json:"deadline"` | |||
Deadline NullTime `json:"deadline,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.
Based on the comment in the attached issue, it's unclear what the use-case for knowing the time is null is that a pointer wouldn't solve.
Is this related to the TypeScript generation?
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 is a cleaner approach from a usability perspective in Go code. That is to say, the benefit is not in the frontend talking to the backend, it's using these values in the backend or cli.
IMO it'd be nice for us to consistently use NullTime
over *time.Time
in the API. Things like not being able to take a pointer of a function call &time.Now()
are not an issue when you can write codersdk.NewNullTime(time.Now(), true)
. Likewise when using the type, there's no need for the if t != nil
check.
There are work-arounds for making pointer use a bit more convenient, like using coderd/util/ptr
, but it's an extra step to using them vs accessing the concrete type directly.
One last point in favor of NullTime
, it's safer to use. There's no risk of doing a nil pointer dereference.
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.
FWIW working with pointers makes me sad; I much prefer the NullXXX
approach.
Unless we start going down the lines of having a generic Optional[T]
type?
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'm hesitant to stray from such strong language primitives. Do we imagine having a NullXXX
for every type that could be nullable? This seems odd to me because embedded structs would likely be pointers with omitempty
rather than Null<StructName>
. Are we promoting this only for built-in types?
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 understand why you may be hesitant, but I don't think it will be a problem. If we strive to make the zero value useful in most cases, the need for nullable values will be pretty low. We can also look at codersdk
(today), there we only have a couple of primitives (int, string) and time.
7 *int64
5 *time.Time
3 *string
2 *uuid.UUID
The uuid
package already has uuid.NullUUID
, so it would make sense to use that one instead. In fact, the occurrence of uuid.NullUUID
is much larger in our codebase than that of *uuid.UUID
(14 vs 3), yet we have both.
I also foresee that the need for a nullable struct will be very rare (we have none in codersdk
, currently), and in those cases it would likely be a custom type for which we can simply implement a custom json marshaler/unmarshaler. It's unfortunate Go never got around to adding omitzero
/omitempty
to the json
package, because many times that would solve the use-case for using pointer-structs (i.e. often the reason is not to detect omission, it's to create a specific type of output).
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 appreciate the explanation! I agree that NullUUID
is classically used, but that's typically been used for database operations, not the API.
I'll defer here though. You've spent a lot more time thinking about this than I, so I approve!
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.
Leaving this here for future reference, and perhaps an alternative avenue for achieving the same in a generic way. (Did some prototyping on this with @johnstcn some days ago)
type Optional[T any] struct {
Value T
Valid bool
}
// Of returns a new Optional of the given value.
func Of[T any](o T) Optional[T] {
return Optional[T]{
Value: o,
Valid: true,
}
}
// MarshalJSON implements json.Marshaler.
func (o Optional[T]) MarshalJSON() ([]byte, error) {
// omitted for brevity
}
// UnmarshalJSON implements json.Unmarshaler.
func (o *Optional[T]) UnmarshalJSON(data []byte) error {
// omitted for brevity
}
type APIRequest struct {
Time Optional[time.Time] `json:"time"`
}
// other package
func sample() {
r := codersdk.APIRequest{}
r.Time = codersdk.Of(time.Now())
if r.Time.Valid { // if we care about null.
_ = r.Time.Value
}
// zero value is ok without null check too
_ = r.Time.Value.IsZero()
}
The namings Optional
, Of
, etc are all up in the air, they could be Null
and NewFrom
instead.
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.
✅ FE
I only see an update to the deadline
property being made optional now. I imagine this shouldn't have any consequences assuming we have tests for code using that interface.
If there is something else you want reviewed by FE, ping me!
a77ae97
to
0c66c39
Compare
@jsjoeio Your comment made me take a look at the site code, and some updates were indeed required. Please take a look at I believe none of this was caught due to the function signature of |
3a3cb79
to
e390cef
Compare
89576ed
to
fdb657e
Compare
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.
Thanks for going the extra mile to double-check any side effects on the FE! 👏🏼
site/src/components/WorkspaceScheduleButton/WorkspaceScheduleButton.tsx
Outdated
Show resolved
Hide resolved
…utton.tsx Co-authored-by: Joe Previte <[email protected]>
I took a slightly different approach in this PR than what originally proposed in #2015, it's closer to my proposal in a later comment.
Currently we embedd
sql.NullTime
for one reason, it gives us database scanner/valuer and means we can pass nullable times to the DB by doingt.NullTime
.We can simplify this though, if it's not needed so that we have plain
codersdk.NullTime{Time time.Time; Valid boold}
.Fixes #2015