Skip to content

Commit b43bd8e

Browse files
committed
chore: interface for pkgs to return codersdk errors
1 parent 01163ea commit b43bd8e

File tree

4 files changed

+153
-108
lines changed

4 files changed

+153
-108
lines changed

coderd/dynamicparameters/error.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package dynamicparameters
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"sort"
7+
8+
"github.com/hashicorp/hcl/v2"
9+
10+
"github.com/coder/coder/v2/codersdk"
11+
)
12+
13+
func ParameterValidationError(diags hcl.Diagnostics) *DiagnosticError {
14+
return &DiagnosticError{
15+
Message: "Unable to validate parameters",
16+
Diagnostics: diags,
17+
KeyedDiagnostics: make(map[string]hcl.Diagnostics),
18+
}
19+
}
20+
21+
func TagValidationError(diags hcl.Diagnostics) *DiagnosticError {
22+
return &DiagnosticError{
23+
Message: "Failed to parse workspace tags",
24+
Diagnostics: diags,
25+
KeyedDiagnostics: make(map[string]hcl.Diagnostics),
26+
}
27+
}
28+
29+
type DiagnosticError struct {
30+
Message string
31+
Diagnostics hcl.Diagnostics
32+
KeyedDiagnostics map[string]hcl.Diagnostics
33+
}
34+
35+
// Error is a pretty bad format for these errors. Try to avoid using this.
36+
func (e *DiagnosticError) Error() string {
37+
var diags hcl.Diagnostics
38+
diags = diags.Extend(e.Diagnostics)
39+
for _, d := range e.KeyedDiagnostics {
40+
diags = diags.Extend(d)
41+
}
42+
43+
return diags.Error()
44+
}
45+
46+
func (e *DiagnosticError) HasError() bool {
47+
if e.Diagnostics.HasErrors() {
48+
return true
49+
}
50+
51+
for _, diags := range e.KeyedDiagnostics {
52+
if diags.HasErrors() {
53+
return true
54+
}
55+
}
56+
return false
57+
}
58+
59+
func (e *DiagnosticError) Append(key string, diag *hcl.Diagnostic) {
60+
e.Extend(key, hcl.Diagnostics{diag})
61+
}
62+
63+
func (e *DiagnosticError) Extend(key string, diag hcl.Diagnostics) {
64+
if e.KeyedDiagnostics == nil {
65+
e.KeyedDiagnostics = make(map[string]hcl.Diagnostics)
66+
}
67+
if _, ok := e.KeyedDiagnostics[key]; !ok {
68+
e.KeyedDiagnostics[key] = hcl.Diagnostics{}
69+
}
70+
e.KeyedDiagnostics[key] = e.KeyedDiagnostics[key].Extend(diag)
71+
}
72+
73+
func (e *DiagnosticError) Response() (int, codersdk.Response) {
74+
resp := codersdk.Response{
75+
Message: e.Message,
76+
Validations: nil,
77+
}
78+
79+
// Sort the parameter names so that the order is consistent.
80+
sortedNames := make([]string, 0, len(e.KeyedDiagnostics))
81+
for name := range e.KeyedDiagnostics {
82+
sortedNames = append(sortedNames, name)
83+
}
84+
sort.Strings(sortedNames)
85+
86+
for _, name := range sortedNames {
87+
diag := e.KeyedDiagnostics[name]
88+
resp.Validations = append(resp.Validations, codersdk.ValidationError{
89+
Field: name,
90+
Detail: DiagnosticsErrorString(diag),
91+
})
92+
}
93+
94+
if e.Diagnostics.HasErrors() {
95+
resp.Detail = DiagnosticsErrorString(e.Diagnostics)
96+
}
97+
98+
return http.StatusBadRequest, resp
99+
}
100+
101+
func DiagnosticErrorString(d *hcl.Diagnostic) string {
102+
return fmt.Sprintf("%s; %s", d.Summary, d.Detail)
103+
}
104+
105+
func DiagnosticsErrorString(d hcl.Diagnostics) string {
106+
count := len(d)
107+
switch {
108+
case count == 0:
109+
return "no diagnostics"
110+
case count == 1:
111+
return DiagnosticErrorString(d[0])
112+
default:
113+
for _, d := range d {
114+
// Render the first error diag.
115+
// If there are warnings, do not priority them over errors.
116+
if d.Severity == hcl.DiagError {
117+
return fmt.Sprintf("%s, and %d other diagnostic(s)", DiagnosticErrorString(d), count-1)
118+
}
119+
}
120+
121+
// All warnings? ok...
122+
return fmt.Sprintf("%s, and %d other diagnostic(s)", DiagnosticErrorString(d[0]), count-1)
123+
}
124+
}

coderd/dynamicparameters/resolver.go

Lines changed: 3 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -26,45 +26,6 @@ type parameterValue struct {
2626
Source parameterValueSource
2727
}
2828

29-
type ResolverError struct {
30-
Diagnostics hcl.Diagnostics
31-
Parameter map[string]hcl.Diagnostics
32-
}
33-
34-
// Error is a pretty bad format for these errors. Try to avoid using this.
35-
func (e *ResolverError) Error() string {
36-
var diags hcl.Diagnostics
37-
diags = diags.Extend(e.Diagnostics)
38-
for _, d := range e.Parameter {
39-
diags = diags.Extend(d)
40-
}
41-
42-
return diags.Error()
43-
}
44-
45-
func (e *ResolverError) HasError() bool {
46-
if e.Diagnostics.HasErrors() {
47-
return true
48-
}
49-
50-
for _, diags := range e.Parameter {
51-
if diags.HasErrors() {
52-
return true
53-
}
54-
}
55-
return false
56-
}
57-
58-
func (e *ResolverError) Extend(parameterName string, diag hcl.Diagnostics) {
59-
if e.Parameter == nil {
60-
e.Parameter = make(map[string]hcl.Diagnostics)
61-
}
62-
if _, ok := e.Parameter[parameterName]; !ok {
63-
e.Parameter[parameterName] = hcl.Diagnostics{}
64-
}
65-
e.Parameter[parameterName] = e.Parameter[parameterName].Extend(diag)
66-
}
67-
6829
//nolint:revive // firstbuild is a control flag to turn on immutable validation
6930
func ResolveParameters(
7031
ctx context.Context,
@@ -112,10 +73,7 @@ func ResolveParameters(
11273
// always be valid. If there is a case where this is not true, then this has to
11374
// be changed to allow the build to continue with a different set of values.
11475

115-
return nil, &ResolverError{
116-
Diagnostics: diags,
117-
Parameter: nil,
118-
}
76+
return nil, ParameterValidationError(diags)
11977
}
12078

12179
// The user's input now needs to be validated against the parameters.
@@ -155,16 +113,13 @@ func ResolveParameters(
155113
// are fatal. Additional validation for immutability has to be done manually.
156114
output, diags = renderer.Render(ctx, ownerID, values.ValuesMap())
157115
if diags.HasErrors() {
158-
return nil, &ResolverError{
159-
Diagnostics: diags,
160-
Parameter: nil,
161-
}
116+
return nil, ParameterValidationError(diags)
162117
}
163118

164119
// parameterNames is going to be used to remove any excess values that were left
165120
// around without a parameter.
166121
parameterNames := make(map[string]struct{}, len(output.Parameters))
167-
parameterError := &ResolverError{}
122+
parameterError := ParameterValidationError(nil)
168123
for _, parameter := range output.Parameters {
169124
parameterNames[parameter.Name] = struct{}{}
170125

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package httperror
2+
3+
import (
4+
"errors"
5+
6+
"github.com/coder/coder/v2/codersdk"
7+
)
8+
9+
type CoderSDKError interface {
10+
Response() (int, codersdk.Response)
11+
}
12+
13+
func IsCoderSDKError(err error) (CoderSDKError, bool) {
14+
var responseErr CoderSDKError
15+
if errors.As(err, &responseErr) {
16+
return responseErr, true
17+
}
18+
return nil, false
19+
}

coderd/httpapi/httperror/wsbuild.go

Lines changed: 7 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@ package httperror
33
import (
44
"context"
55
"errors"
6-
"fmt"
76
"net/http"
8-
"sort"
97

10-
"github.com/hashicorp/hcl/v2"
11-
12-
"github.com/coder/coder/v2/coderd/dynamicparameters"
138
"github.com/coder/coder/v2/coderd/httpapi"
149
"github.com/coder/coder/v2/coderd/wsbuilder"
1510
"github.com/coder/coder/v2/codersdk"
1611
)
1712

1813
func WriteWorkspaceBuildError(ctx context.Context, rw http.ResponseWriter, err error) {
14+
if responseErr, ok := IsCoderSDKError(err); ok {
15+
code, resp := responseErr.Response()
16+
17+
httpapi.Write(ctx, rw, code, resp)
18+
return
19+
}
20+
1921
var buildErr wsbuilder.BuildError
2022
if errors.As(err, &buildErr) {
2123
if httpapi.IsUnauthorizedError(err) {
@@ -29,63 +31,8 @@ func WriteWorkspaceBuildError(ctx context.Context, rw http.ResponseWriter, err e
2931
return
3032
}
3133

32-
var parameterErr *dynamicparameters.ResolverError
33-
if errors.As(err, &parameterErr) {
34-
resp := codersdk.Response{
35-
Message: "Unable to validate parameters",
36-
Validations: nil,
37-
}
38-
39-
// Sort the parameter names so that the order is consistent.
40-
sortedNames := make([]string, 0, len(parameterErr.Parameter))
41-
for name := range parameterErr.Parameter {
42-
sortedNames = append(sortedNames, name)
43-
}
44-
sort.Strings(sortedNames)
45-
46-
for _, name := range sortedNames {
47-
diag := parameterErr.Parameter[name]
48-
resp.Validations = append(resp.Validations, codersdk.ValidationError{
49-
Field: name,
50-
Detail: DiagnosticsErrorString(diag),
51-
})
52-
}
53-
54-
if parameterErr.Diagnostics.HasErrors() {
55-
resp.Detail = DiagnosticsErrorString(parameterErr.Diagnostics)
56-
}
57-
58-
httpapi.Write(ctx, rw, http.StatusBadRequest, resp)
59-
return
60-
}
61-
6234
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
6335
Message: "Internal error creating workspace build.",
6436
Detail: err.Error(),
6537
})
6638
}
67-
68-
func DiagnosticError(d *hcl.Diagnostic) string {
69-
return fmt.Sprintf("%s; %s", d.Summary, d.Detail)
70-
}
71-
72-
func DiagnosticsErrorString(d hcl.Diagnostics) string {
73-
count := len(d)
74-
switch {
75-
case count == 0:
76-
return "no diagnostics"
77-
case count == 1:
78-
return DiagnosticError(d[0])
79-
default:
80-
for _, d := range d {
81-
// Render the first error diag.
82-
// If there are warnings, do not priority them over errors.
83-
if d.Severity == hcl.DiagError {
84-
return fmt.Sprintf("%s, and %d other diagnostic(s)", DiagnosticError(d), count-1)
85-
}
86-
}
87-
88-
// All warnings? ok...
89-
return fmt.Sprintf("%s, and %d other diagnostic(s)", DiagnosticError(d[0]), count-1)
90-
}
91-
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy