mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: Add codersdk.NullTime, change workspace build deadline (#3552)
Fixes #2015 Co-authored-by: Joe Previte <jjprevite@gmail.com>
This commit is contained in:
co-authored by
Joe Previte
parent
a21a6d2f4a
commit
78a24941fe
@@ -0,0 +1,59 @@
|
||||
package codersdk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
var nullBytes = []byte("null")
|
||||
|
||||
// NullTime represents a nullable time.Time.
|
||||
// @typescript-ignore NullTime
|
||||
type NullTime struct {
|
||||
sql.NullTime
|
||||
}
|
||||
|
||||
// NewNullTime returns a new NullTime with the given time.Time.
|
||||
func NewNullTime(t time.Time, valid bool) NullTime {
|
||||
return NullTime{
|
||||
NullTime: sql.NullTime{
|
||||
Time: t,
|
||||
Valid: valid,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (t NullTime) MarshalJSON() ([]byte, error) {
|
||||
if !t.Valid {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
b, err := t.Time.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("codersdk.NullTime: json encode failed: %w", err)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (t *NullTime) UnmarshalJSON(data []byte) error {
|
||||
t.Valid = false
|
||||
if bytes.Equal(data, nullBytes) {
|
||||
return nil
|
||||
}
|
||||
err := json.Unmarshal(data, &t.Time)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("codersdk.NullTime: json decode failed: %w", err)
|
||||
}
|
||||
t.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsZero return true if the time is null or zero.
|
||||
func (t NullTime) IsZero() bool {
|
||||
return !t.Valid || t.Time.IsZero()
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package codersdk_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
func TestNullTime_MarshalJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t1, err := time.Parse(time.RFC3339, "2022-08-18T00:00:00Z")
|
||||
require.NoError(t, err)
|
||||
bt1, err := json.Marshal(t1)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input sql.NullTime
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "valid zero",
|
||||
input: sql.NullTime{Valid: true},
|
||||
want: `"0001-01-01T00:00:00Z"`,
|
||||
},
|
||||
{
|
||||
name: "invalid zero",
|
||||
input: sql.NullTime{Valid: false},
|
||||
want: "null",
|
||||
},
|
||||
{
|
||||
name: "valid time",
|
||||
input: sql.NullTime{Time: t1, Valid: true},
|
||||
want: string(bt1),
|
||||
},
|
||||
{
|
||||
name: "null time",
|
||||
input: sql.NullTime{Time: t1, Valid: false},
|
||||
want: "null",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tr := codersdk.NewNullTime(tt.input.Time, tt.input.Valid)
|
||||
got, err := tr.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNullTime_UnmarshalJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t1, err := time.Parse(time.RFC3339, "2022-08-18T00:00:00Z")
|
||||
require.NoError(t, err)
|
||||
bt1, err := json.Marshal(t1)
|
||||
require.NoError(t, err)
|
||||
|
||||
type request struct {
|
||||
Time codersdk.NullTime `json:"time"`
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
data string
|
||||
want codersdk.NullTime
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "null",
|
||||
data: `{"time": null}`,
|
||||
want: codersdk.NullTime{},
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
data: `{}`,
|
||||
want: codersdk.NullTime{},
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
data: `{"time": ""}`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid time",
|
||||
data: fmt.Sprintf(`{"time": %s}`, bt1),
|
||||
want: codersdk.NewNullTime(t1, true),
|
||||
},
|
||||
{
|
||||
name: "invalid time",
|
||||
data: fmt.Sprintf(`{"time": %q}`, `2022-08-18T00:00:00`),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var req request
|
||||
err := json.Unmarshal([]byte(tt.data), &req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.want, req.Time)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNullTime_IsZero(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input sql.NullTime
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "zero",
|
||||
input: sql.NullTime{},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "not zero",
|
||||
input: sql.NullTime{Time: time.Now(), Valid: true},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "null is zero",
|
||||
input: sql.NullTime{Time: time.Now(), Valid: false},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tr := codersdk.NullTime{NullTime: tt.input}
|
||||
require.Equal(t, tt.want, tr.IsZero())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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"`
|
||||
Reason BuildReason `db:"reason" json:"reason"`
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user