Skip to content

Commit 7e9819f

Browse files
authored
ref: move httpapi.Reponse into codersdk (coder#2954)
1 parent dde51f1 commit 7e9819f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+524
-486
lines changed

coderd/coderd.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ func New(options *Options) *API {
134134

135135
r.Route("/api/v2", func(r chi.Router) {
136136
r.NotFound(func(rw http.ResponseWriter, r *http.Request) {
137-
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
137+
httpapi.Write(rw, http.StatusNotFound, codersdk.Response{
138138
Message: "Route not found.",
139139
})
140140
})
@@ -144,7 +144,7 @@ func New(options *Options) *API {
144144
debugLogRequest(api.Logger),
145145
)
146146
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
147-
httpapi.Write(w, http.StatusOK, httpapi.Response{
147+
httpapi.Write(w, http.StatusOK, codersdk.Response{
148148
//nolint:gocritic
149149
Message: "👋",
150150
})

coderd/csp.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/http"
66

77
"github.com/coder/coder/coderd/httpapi"
8+
"github.com/coder/coder/codersdk"
89

910
"cdr.dev/slog"
1011
)
@@ -22,7 +23,7 @@ func (api *API) logReportCSPViolations(rw http.ResponseWriter, r *http.Request)
2223
err := dec.Decode(&v)
2324
if err != nil {
2425
api.Logger.Warn(ctx, "csp violation", slog.Error(err))
25-
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
26+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
2627
Message: "Failed to read body, invalid json.",
2728
Detail: err.Error(),
2829
})

coderd/files.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) {
3232
switch contentType {
3333
case "application/x-tar":
3434
default:
35-
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
35+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
3636
Message: fmt.Sprintf("Unsupported content type header %q.", contentType),
3737
})
3838
return
@@ -41,7 +41,7 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) {
4141
r.Body = http.MaxBytesReader(rw, r.Body, 10*(10<<20))
4242
data, err := io.ReadAll(r.Body)
4343
if err != nil {
44-
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
44+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
4545
Message: "Failed to read file from request.",
4646
Detail: err.Error(),
4747
})
@@ -65,7 +65,7 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) {
6565
Data: data,
6666
})
6767
if err != nil {
68-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
68+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
6969
Message: "Internal error saving file.",
7070
Detail: err.Error(),
7171
})
@@ -80,7 +80,7 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) {
8080
func (api *API) fileByHash(rw http.ResponseWriter, r *http.Request) {
8181
hash := chi.URLParam(r, "hash")
8282
if hash == "" {
83-
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
83+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
8484
Message: "File hash must be provided in url.",
8585
})
8686
return
@@ -91,7 +91,7 @@ func (api *API) fileByHash(rw http.ResponseWriter, r *http.Request) {
9191
return
9292
}
9393
if err != nil {
94-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
94+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
9595
Message: "Internal error fetching file.",
9696
Detail: err.Error(),
9797
})

coderd/gitsshkey.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func (api *API) regenerateGitSSHKey(rw http.ResponseWriter, r *http.Request) {
2121

2222
privateKey, publicKey, err := gitsshkey.Generate(api.SSHKeygenAlgorithm)
2323
if err != nil {
24-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
24+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
2525
Message: "Internal error generating a new SSH keypair.",
2626
Detail: err.Error(),
2727
})
@@ -35,7 +35,7 @@ func (api *API) regenerateGitSSHKey(rw http.ResponseWriter, r *http.Request) {
3535
PublicKey: publicKey,
3636
})
3737
if err != nil {
38-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
38+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
3939
Message: "Internal error updating user's git SSH key.",
4040
Detail: err.Error(),
4141
})
@@ -44,7 +44,7 @@ func (api *API) regenerateGitSSHKey(rw http.ResponseWriter, r *http.Request) {
4444

4545
newKey, err := api.Database.GetGitSSHKey(r.Context(), user.ID)
4646
if err != nil {
47-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
47+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
4848
Message: "Internal error fetching user's git SSH key.",
4949
Detail: err.Error(),
5050
})
@@ -70,7 +70,7 @@ func (api *API) gitSSHKey(rw http.ResponseWriter, r *http.Request) {
7070

7171
gitSSHKey, err := api.Database.GetGitSSHKey(r.Context(), user.ID)
7272
if err != nil {
73-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
73+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
7474
Message: "Internal error fetching user's SSH key.",
7575
Detail: err.Error(),
7676
})
@@ -90,7 +90,7 @@ func (api *API) agentGitSSHKey(rw http.ResponseWriter, r *http.Request) {
9090
agent := httpmw.WorkspaceAgent(r)
9191
resource, err := api.Database.GetWorkspaceResourceByID(r.Context(), agent.ResourceID)
9292
if err != nil {
93-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
93+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
9494
Message: "Internal error fetching workspace resource.",
9595
Detail: err.Error(),
9696
})
@@ -99,7 +99,7 @@ func (api *API) agentGitSSHKey(rw http.ResponseWriter, r *http.Request) {
9999

100100
job, err := api.Database.GetWorkspaceBuildByJobID(r.Context(), resource.JobID)
101101
if err != nil {
102-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
102+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
103103
Message: "Internal error fetching workspace build.",
104104
Detail: err.Error(),
105105
})
@@ -108,7 +108,7 @@ func (api *API) agentGitSSHKey(rw http.ResponseWriter, r *http.Request) {
108108

109109
workspace, err := api.Database.GetWorkspaceByID(r.Context(), job.WorkspaceID)
110110
if err != nil {
111-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
111+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
112112
Message: "Internal error fetching workspace.",
113113
Detail: err.Error(),
114114
})
@@ -117,7 +117,7 @@ func (api *API) agentGitSSHKey(rw http.ResponseWriter, r *http.Request) {
117117

118118
gitSSHKey, err := api.Database.GetGitSSHKey(r.Context(), workspace.OwnerID)
119119
if err != nil {
120-
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
120+
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
121121
Message: "Internal error fetching git SSH key.",
122122
Detail: err.Error(),
123123
})

coderd/httpapi/httpapi.go

+9-33
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"strings"
1212

1313
"github.com/go-playground/validator/v10"
14+
15+
"github.com/coder/coder/codersdk"
1416
)
1517

1618
var (
@@ -50,42 +52,16 @@ func init() {
5052
}
5153
}
5254

53-
// Response represents a generic HTTP response.
54-
type Response struct {
55-
// Message is an actionable message that depicts actions the request took.
56-
// These messages should be fully formed sentences with proper punctuation.
57-
// Examples:
58-
// - "A user has been created."
59-
// - "Failed to create a user."
60-
Message string `json:"message"`
61-
// Detail is a debug message that provides further insight into why the
62-
// action failed. This information can be technical and a regular golang
63-
// err.Error() text.
64-
// - "database: too many open connections"
65-
// - "stat: too many open files"
66-
Detail string `json:"detail,omitempty"`
67-
// Validations are form field-specific friendly error messages. They will be
68-
// shown on a form field in the UI. These can also be used to add additional
69-
// context if there is a set of errors in the primary 'Message'.
70-
Validations []Error `json:"validations,omitempty"`
71-
}
72-
73-
// Error represents a scoped error to a user input.
74-
type Error struct {
75-
Field string `json:"field" validate:"required"`
76-
Detail string `json:"detail" validate:"required"`
77-
}
78-
7955
// ResourceNotFound is intentionally vague. All 404 responses should be identical
8056
// to prevent leaking existence of resources.
8157
func ResourceNotFound(rw http.ResponseWriter) {
82-
Write(rw, http.StatusNotFound, Response{
58+
Write(rw, http.StatusNotFound, codersdk.Response{
8359
Message: "Resource not found or you do not have access to this resource",
8460
})
8561
}
8662

8763
func Forbidden(rw http.ResponseWriter) {
88-
Write(rw, http.StatusForbidden, Response{
64+
Write(rw, http.StatusForbidden, codersdk.Response{
8965
Message: "Forbidden.",
9066
})
9167
}
@@ -114,7 +90,7 @@ func Write(rw http.ResponseWriter, status int, response interface{}) {
11490
func Read(rw http.ResponseWriter, r *http.Request, value interface{}) bool {
11591
err := json.NewDecoder(r.Body).Decode(value)
11692
if err != nil {
117-
Write(rw, http.StatusBadRequest, Response{
93+
Write(rw, http.StatusBadRequest, codersdk.Response{
11894
Message: "Request body must be valid JSON.",
11995
Detail: err.Error(),
12096
})
@@ -123,21 +99,21 @@ func Read(rw http.ResponseWriter, r *http.Request, value interface{}) bool {
12399
err = validate.Struct(value)
124100
var validationErrors validator.ValidationErrors
125101
if errors.As(err, &validationErrors) {
126-
apiErrors := make([]Error, 0, len(validationErrors))
102+
apiErrors := make([]codersdk.ValidationError, 0, len(validationErrors))
127103
for _, validationError := range validationErrors {
128-
apiErrors = append(apiErrors, Error{
104+
apiErrors = append(apiErrors, codersdk.ValidationError{
129105
Field: validationError.Field(),
130106
Detail: fmt.Sprintf("Validation failed for tag %q with value: \"%v\"", validationError.Tag(), validationError.Value()),
131107
})
132108
}
133-
Write(rw, http.StatusBadRequest, Response{
109+
Write(rw, http.StatusBadRequest, codersdk.Response{
134110
Message: "Validation failed.",
135111
Validations: apiErrors,
136112
})
137113
return false
138114
}
139115
if err != nil {
140-
Write(rw, http.StatusInternalServerError, Response{
116+
Write(rw, http.StatusInternalServerError, codersdk.Response{
141117
Message: "Internal error validating request body payload.",
142118
Detail: err.Error(),
143119
})

coderd/httpapi/httpapi_test.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ import (
1212
"github.com/stretchr/testify/require"
1313

1414
"github.com/coder/coder/coderd/httpapi"
15+
"github.com/coder/coder/codersdk"
1516
)
1617

1718
func TestWrite(t *testing.T) {
1819
t.Parallel()
1920
t.Run("NoErrors", func(t *testing.T) {
2021
t.Parallel()
2122
rw := httptest.NewRecorder()
22-
httpapi.Write(rw, http.StatusOK, httpapi.Response{
23+
httpapi.Write(rw, http.StatusOK, codersdk.Response{
2324
Message: "Wow.",
2425
})
2526
var m map[string]interface{}
@@ -71,7 +72,7 @@ func TestRead(t *testing.T) {
7172

7273
var validate toValidate
7374
require.False(t, httpapi.Read(rw, r, &validate))
74-
var v httpapi.Response
75+
var v codersdk.Response
7576
err := json.NewDecoder(rw.Body).Decode(&v)
7677
require.NoError(t, err)
7778
require.Len(t, v.Validations, 1)

coderd/httpapi/queryparams.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88

99
"github.com/google/uuid"
1010

11+
"github.com/coder/coder/codersdk"
12+
1113
"golang.org/x/xerrors"
1214
)
1315

@@ -17,19 +19,19 @@ import (
1719
type QueryParamParser struct {
1820
// Errors is the set of errors to return via the API. If the length
1921
// of this set is 0, there are no errors!.
20-
Errors []Error
22+
Errors []codersdk.ValidationError
2123
}
2224

2325
func NewQueryParamParser() *QueryParamParser {
2426
return &QueryParamParser{
25-
Errors: []Error{},
27+
Errors: []codersdk.ValidationError{},
2628
}
2729
}
2830

2931
func (p *QueryParamParser) Int(vals url.Values, def int, queryParam string) int {
3032
v, err := parseQueryParam(vals, strconv.Atoi, def, queryParam)
3133
if err != nil {
32-
p.Errors = append(p.Errors, Error{
34+
p.Errors = append(p.Errors, codersdk.ValidationError{
3335
Field: queryParam,
3436
Detail: fmt.Sprintf("Query param %q must be a valid integer (%s)", queryParam, err.Error()),
3537
})
@@ -47,7 +49,7 @@ func (p *QueryParamParser) UUIDorMe(vals url.Values, def uuid.UUID, me uuid.UUID
4749
func (p *QueryParamParser) UUID(vals url.Values, def uuid.UUID, queryParam string) uuid.UUID {
4850
v, err := parseQueryParam(vals, uuid.Parse, def, queryParam)
4951
if err != nil {
50-
p.Errors = append(p.Errors, Error{
52+
p.Errors = append(p.Errors, codersdk.ValidationError{
5153
Field: queryParam,
5254
Detail: fmt.Sprintf("Query param %q must be a valid uuid", queryParam),
5355
})
@@ -75,7 +77,7 @@ func (p *QueryParamParser) UUIDs(vals url.Values, def []uuid.UUID, queryParam st
7577
return ids, nil
7678
}, def, queryParam)
7779
if err != nil {
78-
p.Errors = append(p.Errors, Error{
80+
p.Errors = append(p.Errors, codersdk.ValidationError{
7981
Field: queryParam,
8082
Detail: fmt.Sprintf("Query param %q has invalid uuids: %q", queryParam, err.Error()),
8183
})
@@ -105,7 +107,7 @@ func (*QueryParamParser) Strings(vals url.Values, def []string, queryParam strin
105107
func ParseCustom[T any](parser *QueryParamParser, vals url.Values, def T, queryParam string, parseFunc func(v string) (T, error)) T {
106108
v, err := parseQueryParam(vals, parseFunc, def, queryParam)
107109
if err != nil {
108-
parser.Errors = append(parser.Errors, Error{
110+
parser.Errors = append(parser.Errors, codersdk.ValidationError{
109111
Field: queryParam,
110112
Detail: fmt.Sprintf("Query param %q has invalid value: %s", queryParam, err.Error()),
111113
})

0 commit comments

Comments
 (0)