diff --git a/coderd/workspaces.go b/coderd/workspaces.go index afc2152841ce7..1469807b8f6c7 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -288,7 +288,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req dbTTL, err := validWorkspaceTTLMillis(createWorkspace.TTLMillis, time.Duration(template.MaxTtl)) if err != nil { httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ - Message: "Invalid Workspace TTL.", + Message: "Invalid Workspace Time to Shutdown.", Validations: []codersdk.ValidationError{{Field: "ttl_ms", Detail: err.Error()}}, }) return @@ -523,8 +523,6 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) { return } - var validErrs []codersdk.ValidationError - err := api.Database.InTx(func(s database.Store) error { template, err := s.GetTemplateByID(r.Context(), workspace.TemplateID) if err != nil { @@ -536,29 +534,31 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) { dbTTL, err := validWorkspaceTTLMillis(req.TTLMillis, time.Duration(template.MaxTtl)) if err != nil { - validErrs = append(validErrs, codersdk.ValidationError{Field: "ttl_ms", Detail: err.Error()}) - return err + return codersdk.ValidationError{Field: "ttl_ms", Detail: err.Error()} } if err := s.UpdateWorkspaceTTL(r.Context(), database.UpdateWorkspaceTTLParams{ ID: workspace.ID, Ttl: dbTTL, }); err != nil { - return xerrors.Errorf("update workspace TTL: %w", err) + return xerrors.Errorf("update workspace time until shutdown: %w", err) } return nil }) if err != nil { - code := http.StatusInternalServerError - if len(validErrs) > 0 { - code = http.StatusBadRequest + resp := codersdk.Response{ + Message: "Error updating workspace time until shutdown.", } - httpapi.Write(rw, code, codersdk.Response{ - Message: "Error updating workspace time until shutdown!", - Validations: validErrs, - Detail: err.Error(), - }) + var validErr codersdk.ValidationError + if errors.As(err, &validErr) { + resp.Validations = []codersdk.ValidationError{validErr} + httpapi.Write(rw, http.StatusBadRequest, resp) + return + } + + resp.Detail = err.Error() + httpapi.Write(rw, http.StatusInternalServerError, resp) return } @@ -895,15 +895,15 @@ func validWorkspaceTTLMillis(millis *int64, max time.Duration) (sql.NullInt64, e dur := time.Duration(*millis) * time.Millisecond truncated := dur.Truncate(time.Minute) if truncated < time.Minute { - return sql.NullInt64{}, xerrors.New("ttl must be at least one minute") + return sql.NullInt64{}, xerrors.New("time until shutdown must be at least one minute") } if truncated > 24*7*time.Hour { - return sql.NullInt64{}, xerrors.New("ttl must be less than 7 days") + return sql.NullInt64{}, xerrors.New("time until shutdown must be less than 7 days") } if truncated > max { - return sql.NullInt64{}, xerrors.Errorf("ttl must be below template maximum %s", max.String()) + return sql.NullInt64{}, xerrors.Errorf("time until shutdown must be below template maximum %s", max.String()) } return sql.NullInt64{ diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 2d607a5e3fa0f..eb275e0f8a0bc 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -207,7 +207,7 @@ func TestPostWorkspacesByOrganization(t *testing.T) { require.Equal(t, http.StatusBadRequest, apiErr.StatusCode()) require.Len(t, apiErr.Validations, 1) require.Equal(t, apiErr.Validations[0].Field, "ttl_ms") - require.Equal(t, apiErr.Validations[0].Detail, "ttl must be at least one minute") + require.Equal(t, "time until shutdown must be at least one minute", apiErr.Validations[0].Detail) }) t.Run("AboveMax", func(t *testing.T) { @@ -220,7 +220,7 @@ func TestPostWorkspacesByOrganization(t *testing.T) { req := codersdk.CreateWorkspaceRequest{ TemplateID: template.ID, Name: "testing", - TTLMillis: ptr.Ref((24*7*time.Hour + time.Minute).Milliseconds()), + TTLMillis: ptr.Ref(template.MaxTTLMillis + time.Minute.Milliseconds()), } _, err := client.CreateWorkspace(context.Background(), template.OrganizationID, req) require.Error(t, err) @@ -229,7 +229,7 @@ func TestPostWorkspacesByOrganization(t *testing.T) { require.Equal(t, http.StatusBadRequest, apiErr.StatusCode()) require.Len(t, apiErr.Validations, 1) require.Equal(t, apiErr.Validations[0].Field, "ttl_ms") - require.Equal(t, apiErr.Validations[0].Detail, "ttl must be less than 7 days") + require.Equal(t, "time until shutdown must be less than 7 days", apiErr.Validations[0].Detail) }) }) @@ -934,7 +934,7 @@ func TestWorkspaceUpdateTTL(t *testing.T) { { name: "below minimum ttl", ttlMillis: ptr.Ref((30 * time.Second).Milliseconds()), - expectedError: "ttl must be at least one minute", + expectedError: "time until shutdown must be at least one minute", }, { name: "minimum ttl", @@ -949,12 +949,12 @@ func TestWorkspaceUpdateTTL(t *testing.T) { { name: "above maximum ttl", ttlMillis: ptr.Ref((24*7*time.Hour + time.Minute).Milliseconds()), - expectedError: "ttl must be less than 7 days", + expectedError: "time until shutdown must be less than 7 days", }, { name: "above template maximum ttl", ttlMillis: ptr.Ref((12 * time.Hour).Milliseconds()), - expectedError: "ttl_ms: ttl must be below template maximum 8h0m0s", + expectedError: "ttl_ms: time until shutdown must be below template maximum 8h0m0s", modifyTemplate: func(ctr *codersdk.CreateTemplateRequest) { ctr.MaxTTLMillis = ptr.Ref((8 * time.Hour).Milliseconds()) }, }, } diff --git a/codersdk/error.go b/codersdk/error.go index 316d15d888c62..9b99ef97cfe18 100644 --- a/codersdk/error.go +++ b/codersdk/error.go @@ -1,6 +1,7 @@ package codersdk import ( + "fmt" "net" "golang.org/x/xerrors" @@ -32,6 +33,12 @@ type ValidationError struct { Detail string `json:"detail" validate:"required"` } +func (e ValidationError) Error() string { + return fmt.Sprintf("field: %s detail: %s", e.Field, e.Detail) +} + +var _ error = (*ValidationError)(nil) + // IsConnectionErr is a convenience function for checking if the source of an // error is due to a 'connection refused', 'no such host', etc. func IsConnectionErr(err error) bool { diff --git a/codersdk/workspaces.go b/codersdk/workspaces.go index c596b684896d6..bedfcf124b55e 100644 --- a/codersdk/workspaces.go +++ b/codersdk/workspaces.go @@ -192,7 +192,7 @@ func (c *Client) UpdateWorkspaceTTL(ctx context.Context, id uuid.UUID, req Updat path := fmt.Sprintf("/api/v2/workspaces/%s/ttl", id.String()) res, err := c.Request(ctx, http.MethodPut, path, req) if err != nil { - return xerrors.Errorf("update workspace ttl: %w", err) + return xerrors.Errorf("update workspace time until shutdown: %w", err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { @@ -212,7 +212,7 @@ func (c *Client) PutExtendWorkspace(ctx context.Context, id uuid.UUID, req PutEx path := fmt.Sprintf("/api/v2/workspaces/%s/extend", id.String()) res, err := c.Request(ctx, http.MethodPut, path, req) if err != nil { - return xerrors.Errorf("extend workspace ttl: %w", err) + return xerrors.Errorf("extend workspace time until shutdown: %w", err) } defer res.Body.Close() if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusNotModified { diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index e80e333b0df2b..72af42722d13a 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -250,7 +250,7 @@ export interface PutExtendWorkspaceRequest { readonly deadline: string } -// From codersdk/error.go:10:6 +// From codersdk/error.go:11:6 export interface Response { readonly message: string readonly detail?: string @@ -386,7 +386,7 @@ export interface UsersRequest extends Pagination { readonly q?: string } -// From codersdk/error.go:30:6 +// From codersdk/error.go:31:6 export interface ValidationError { readonly field: string readonly detail: string