Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit699dd8e

Browse files
authored
chore: create interface for pkgs to return codersdk errors (#18719)
This interface allows it to create rich codersdk errors and pass them up to the `wsbuilder` error handling.
1 parent7d412c2 commit699dd8e

File tree

6 files changed

+182
-119
lines changed

6 files changed

+182
-119
lines changed

‎coderd/dynamicparameters/error.go‎

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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+
funcParameterValidationError(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+
funcTagValidationError(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+
typeDiagnosticErrorstruct {
30+
// Message is the human-readable message that will be returned to the user.
31+
Messagestring
32+
// Diagnostics are top level diagnostics that will be returned as "Detail" in the response.
33+
Diagnostics hcl.Diagnostics
34+
// KeyedDiagnostics translate to Validation errors in the response. A key could
35+
// be a parameter name, or a tag name. This allows diagnostics to be more closely
36+
// associated with a specific index/parameter/tag.
37+
KeyedDiagnosticsmap[string]hcl.Diagnostics
38+
}
39+
40+
// Error is a pretty bad format for these errors. Try to avoid using this.
41+
func (e*DiagnosticError)Error()string {
42+
vardiags hcl.Diagnostics
43+
diags=diags.Extend(e.Diagnostics)
44+
for_,d:=rangee.KeyedDiagnostics {
45+
diags=diags.Extend(d)
46+
}
47+
48+
returndiags.Error()
49+
}
50+
51+
func (e*DiagnosticError)HasError()bool {
52+
ife.Diagnostics.HasErrors() {
53+
returntrue
54+
}
55+
56+
for_,diags:=rangee.KeyedDiagnostics {
57+
ifdiags.HasErrors() {
58+
returntrue
59+
}
60+
}
61+
returnfalse
62+
}
63+
64+
func (e*DiagnosticError)Append(keystring,diag*hcl.Diagnostic) {
65+
e.Extend(key, hcl.Diagnostics{diag})
66+
}
67+
68+
func (e*DiagnosticError)Extend(keystring,diag hcl.Diagnostics) {
69+
ife.KeyedDiagnostics==nil {
70+
e.KeyedDiagnostics=make(map[string]hcl.Diagnostics)
71+
}
72+
if_,ok:=e.KeyedDiagnostics[key];!ok {
73+
e.KeyedDiagnostics[key]= hcl.Diagnostics{}
74+
}
75+
e.KeyedDiagnostics[key]=e.KeyedDiagnostics[key].Extend(diag)
76+
}
77+
78+
func (e*DiagnosticError)Response() (int, codersdk.Response) {
79+
resp:= codersdk.Response{
80+
Message:e.Message,
81+
Validations:nil,
82+
}
83+
84+
// Sort the parameter names so that the order is consistent.
85+
sortedNames:=make([]string,0,len(e.KeyedDiagnostics))
86+
forname:=rangee.KeyedDiagnostics {
87+
sortedNames=append(sortedNames,name)
88+
}
89+
sort.Strings(sortedNames)
90+
91+
for_,name:=rangesortedNames {
92+
diag:=e.KeyedDiagnostics[name]
93+
resp.Validations=append(resp.Validations, codersdk.ValidationError{
94+
Field:name,
95+
Detail:DiagnosticsErrorString(diag),
96+
})
97+
}
98+
99+
ife.Diagnostics.HasErrors() {
100+
resp.Detail=DiagnosticsErrorString(e.Diagnostics)
101+
}
102+
103+
returnhttp.StatusBadRequest,resp
104+
}
105+
106+
funcDiagnosticErrorString(d*hcl.Diagnostic)string {
107+
returnfmt.Sprintf("%s; %s",d.Summary,d.Detail)
108+
}
109+
110+
funcDiagnosticsErrorString(d hcl.Diagnostics)string {
111+
count:=len(d)
112+
switch {
113+
casecount==0:
114+
return"no diagnostics"
115+
casecount==1:
116+
returnDiagnosticErrorString(d[0])
117+
default:
118+
for_,d:=ranged {
119+
// Render the first error diag.
120+
// If there are warnings, do not priority them over errors.
121+
ifd.Severity==hcl.DiagError {
122+
returnfmt.Sprintf("%s, and %d other diagnostic(s)",DiagnosticErrorString(d),count-1)
123+
}
124+
}
125+
126+
// All warnings? ok...
127+
returnfmt.Sprintf("%s, and %d other diagnostic(s)",DiagnosticErrorString(d[0]),count-1)
128+
}
129+
}

‎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
SourceparameterValueSource
2727
}
2828

29-
typeResolverErrorstruct {
30-
Diagnostics hcl.Diagnostics
31-
Parametermap[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-
vardiags hcl.Diagnostics
37-
diags=diags.Extend(e.Diagnostics)
38-
for_,d:=rangee.Parameter {
39-
diags=diags.Extend(d)
40-
}
41-
42-
returndiags.Error()
43-
}
44-
45-
func (e*ResolverError)HasError()bool {
46-
ife.Diagnostics.HasErrors() {
47-
returntrue
48-
}
49-
50-
for_,diags:=rangee.Parameter {
51-
ifdiags.HasErrors() {
52-
returntrue
53-
}
54-
}
55-
returnfalse
56-
}
57-
58-
func (e*ResolverError)Extend(parameterNamestring,diag hcl.Diagnostics) {
59-
ife.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
funcResolveParameters(
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-
returnnil,&ResolverError{
116-
Diagnostics:diags,
117-
Parameter:nil,
118-
}
76+
returnnil,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
ifdiags.HasErrors() {
158-
returnnil,&ResolverError{
159-
Diagnostics:diags,
160-
Parameter:nil,
161-
}
116+
returnnil,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:=rangeoutput.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+
typeResponderinterface {
10+
Response() (int, codersdk.Response)
11+
}
12+
13+
funcIsResponder(errerror) (Responder,bool) {
14+
varresponseErrResponder
15+
iferrors.As(err,&responseErr) {
16+
returnresponseErr,true
17+
}
18+
returnnil,false
19+
}

‎coderd/httpapi/httperror/wsbuild.go‎

Lines changed: 3 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,60 +2,17 @@ package httperror
22

33
import (
44
"context"
5-
"errors"
6-
"fmt"
75
"net/http"
8-
"sort"
96

10-
"github.com/hashicorp/hcl/v2"
11-
12-
"github.com/coder/coder/v2/coderd/dynamicparameters"
137
"github.com/coder/coder/v2/coderd/httpapi"
14-
"github.com/coder/coder/v2/coderd/wsbuilder"
158
"github.com/coder/coder/v2/codersdk"
169
)
1710

1811
funcWriteWorkspaceBuildError(ctx context.Context,rw http.ResponseWriter,errerror) {
19-
varbuildErr wsbuilder.BuildError
20-
iferrors.As(err,&buildErr) {
21-
ifhttpapi.IsUnauthorizedError(err) {
22-
buildErr.Status=http.StatusForbidden
23-
}
24-
25-
httpapi.Write(ctx,rw,buildErr.Status, codersdk.Response{
26-
Message:buildErr.Message,
27-
Detail:buildErr.Error(),
28-
})
29-
return
30-
}
31-
32-
varparameterErr*dynamicparameters.ResolverError
33-
iferrors.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-
forname:=rangeparameterErr.Parameter {
42-
sortedNames=append(sortedNames,name)
43-
}
44-
sort.Strings(sortedNames)
45-
46-
for_,name:=rangesortedNames {
47-
diag:=parameterErr.Parameter[name]
48-
resp.Validations=append(resp.Validations, codersdk.ValidationError{
49-
Field:name,
50-
Detail:DiagnosticsErrorString(diag),
51-
})
52-
}
12+
ifresponseErr,ok:=IsResponder(err);ok {
13+
code,resp:=responseErr.Response()
5314

54-
ifparameterErr.Diagnostics.HasErrors() {
55-
resp.Detail=DiagnosticsErrorString(parameterErr.Diagnostics)
56-
}
57-
58-
httpapi.Write(ctx,rw,http.StatusBadRequest,resp)
15+
httpapi.Write(ctx,rw,code,resp)
5916
return
6017
}
6118

@@ -64,28 +21,3 @@ func WriteWorkspaceBuildError(ctx context.Context, rw http.ResponseWriter, err e
6421
Detail:err.Error(),
6522
})
6623
}
67-
68-
funcDiagnosticError(d*hcl.Diagnostic)string {
69-
returnfmt.Sprintf("%s; %s",d.Summary,d.Detail)
70-
}
71-
72-
funcDiagnosticsErrorString(d hcl.Diagnostics)string {
73-
count:=len(d)
74-
switch {
75-
casecount==0:
76-
return"no diagnostics"
77-
casecount==1:
78-
returnDiagnosticError(d[0])
79-
default:
80-
for_,d:=ranged {
81-
// Render the first error diag.
82-
// If there are warnings, do not priority them over errors.
83-
ifd.Severity==hcl.DiagError {
84-
returnfmt.Sprintf("%s, and %d other diagnostic(s)",DiagnosticError(d),count-1)
85-
}
86-
}
87-
88-
// All warnings? ok...
89-
returnfmt.Sprintf("%s, and %d other diagnostic(s)",DiagnosticError(d[0]),count-1)
90-
}
91-
}

‎coderd/wsbuilder/wsbuilder.go‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,13 +240,23 @@ type BuildError struct {
240240
}
241241

242242
func (eBuildError)Error()string {
243+
ife.Wrapped==nil {
244+
returne.Message
245+
}
243246
returne.Wrapped.Error()
244247
}
245248

246249
func (eBuildError)Unwrap()error {
247250
returne.Wrapped
248251
}
249252

253+
func (eBuildError)Response() (int, codersdk.Response) {
254+
returne.Status, codersdk.Response{
255+
Message:e.Message,
256+
Detail:e.Error(),
257+
}
258+
}
259+
250260
// Build computes and inserts a new workspace build into the database. If authFunc is provided, it also performs
251261
// authorization preflight checks.
252262
func (b*Builder)Build(

‎coderd/wsbuilder/wsbuilder_test.go‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/coder/coder/v2/coderd/coderdtest"
1414
"github.com/coder/coder/v2/coderd/files"
15+
"github.com/coder/coder/v2/coderd/httpapi/httperror"
1516
"github.com/coder/coder/v2/provisionersdk"
1617

1718
"github.com/google/uuid"
@@ -1000,6 +1001,23 @@ func TestWorkspaceBuildDeleteOrphan(t *testing.T) {
10001001
})
10011002
}
10021003

1004+
funcTestWsbuildError(t*testing.T) {
1005+
t.Parallel()
1006+
1007+
constmsg="test error"
1008+
varbuildErrerror= wsbuilder.BuildError{
1009+
Status:http.StatusBadRequest,
1010+
Message:msg,
1011+
}
1012+
1013+
respErr,ok:=httperror.IsResponder(buildErr)
1014+
require.True(t,ok,"should be a Coder SDK error")
1015+
1016+
code,resp:=respErr.Response()
1017+
require.Equal(t,http.StatusBadRequest,code)
1018+
require.Equal(t,msg,resp.Message)
1019+
}
1020+
10031021
typetxExpectfunc(mTx*dbmock.MockStore)
10041022

10051023
funcexpectDB(t*testing.T,opts...txExpect)*dbmock.MockStore {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp