Skip to content

ref: move httpapi.Reponse into codersdk #2954

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func New(options *Options) *API {

r.Route("/api/v2", func(r chi.Router) {
r.NotFound(func(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
httpapi.Write(rw, http.StatusNotFound, codersdk.Response{
Message: "Route not found.",
})
})
Expand All @@ -144,7 +144,7 @@ func New(options *Options) *API {
debugLogRequest(api.Logger),
)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
httpapi.Write(w, http.StatusOK, httpapi.Response{
httpapi.Write(w, http.StatusOK, codersdk.Response{
//nolint:gocritic
Message: "👋",
})
Expand Down
3 changes: 2 additions & 1 deletion coderd/csp.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"

"github.com/coder/coder/coderd/httpapi"
"github.com/coder/coder/codersdk"

"cdr.dev/slog"
)
Expand All @@ -22,7 +23,7 @@ func (api *API) logReportCSPViolations(rw http.ResponseWriter, r *http.Request)
err := dec.Decode(&v)
if err != nil {
api.Logger.Warn(ctx, "csp violation", slog.Error(err))
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
Message: "Failed to read body, invalid json.",
Detail: err.Error(),
})
Expand Down
10 changes: 5 additions & 5 deletions coderd/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) {
switch contentType {
case "application/x-tar":
default:
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
Message: fmt.Sprintf("Unsupported content type header %q.", contentType),
})
return
Expand All @@ -41,7 +41,7 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(rw, r.Body, 10*(10<<20))
data, err := io.ReadAll(r.Body)
if err != nil {
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
Message: "Failed to read file from request.",
Detail: err.Error(),
})
Expand All @@ -65,7 +65,7 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) {
Data: data,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error saving file.",
Detail: err.Error(),
})
Expand All @@ -80,7 +80,7 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) {
func (api *API) fileByHash(rw http.ResponseWriter, r *http.Request) {
hash := chi.URLParam(r, "hash")
if hash == "" {
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
Message: "File hash must be provided in url.",
})
return
Expand All @@ -91,7 +91,7 @@ func (api *API) fileByHash(rw http.ResponseWriter, r *http.Request) {
return
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching file.",
Detail: err.Error(),
})
Expand Down
16 changes: 8 additions & 8 deletions coderd/gitsshkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func (api *API) regenerateGitSSHKey(rw http.ResponseWriter, r *http.Request) {

privateKey, publicKey, err := gitsshkey.Generate(api.SSHKeygenAlgorithm)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error generating a new SSH keypair.",
Detail: err.Error(),
})
Expand All @@ -35,7 +35,7 @@ func (api *API) regenerateGitSSHKey(rw http.ResponseWriter, r *http.Request) {
PublicKey: publicKey,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error updating user's git SSH key.",
Detail: err.Error(),
})
Expand All @@ -44,7 +44,7 @@ func (api *API) regenerateGitSSHKey(rw http.ResponseWriter, r *http.Request) {

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

gitSSHKey, err := api.Database.GetGitSSHKey(r.Context(), user.ID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching user's SSH key.",
Detail: err.Error(),
})
Expand All @@ -90,7 +90,7 @@ func (api *API) agentGitSSHKey(rw http.ResponseWriter, r *http.Request) {
agent := httpmw.WorkspaceAgent(r)
resource, err := api.Database.GetWorkspaceResourceByID(r.Context(), agent.ResourceID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching workspace resource.",
Detail: err.Error(),
})
Expand All @@ -99,7 +99,7 @@ func (api *API) agentGitSSHKey(rw http.ResponseWriter, r *http.Request) {

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

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

gitSSHKey, err := api.Database.GetGitSSHKey(r.Context(), workspace.OwnerID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching git SSH key.",
Detail: err.Error(),
})
Expand Down
42 changes: 9 additions & 33 deletions coderd/httpapi/httpapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"strings"

"github.com/go-playground/validator/v10"

"github.com/coder/coder/codersdk"
)

var (
Expand Down Expand Up @@ -50,42 +52,16 @@ func init() {
}
}

// Response represents a generic HTTP response.
type Response struct {
// Message is an actionable message that depicts actions the request took.
// These messages should be fully formed sentences with proper punctuation.
// Examples:
// - "A user has been created."
// - "Failed to create a user."
Message string `json:"message"`
// Detail is a debug message that provides further insight into why the
// action failed. This information can be technical and a regular golang
// err.Error() text.
// - "database: too many open connections"
// - "stat: too many open files"
Detail string `json:"detail,omitempty"`
// Validations are form field-specific friendly error messages. They will be
// shown on a form field in the UI. These can also be used to add additional
// context if there is a set of errors in the primary 'Message'.
Validations []Error `json:"validations,omitempty"`
}

// Error represents a scoped error to a user input.
type Error struct {
Field string `json:"field" validate:"required"`
Detail string `json:"detail" validate:"required"`
}

// ResourceNotFound is intentionally vague. All 404 responses should be identical
// to prevent leaking existence of resources.
func ResourceNotFound(rw http.ResponseWriter) {
Write(rw, http.StatusNotFound, Response{
Write(rw, http.StatusNotFound, codersdk.Response{
Message: "Resource not found or you do not have access to this resource",
})
}

func Forbidden(rw http.ResponseWriter) {
Write(rw, http.StatusForbidden, Response{
Write(rw, http.StatusForbidden, codersdk.Response{
Message: "Forbidden.",
})
}
Expand Down Expand Up @@ -114,7 +90,7 @@ func Write(rw http.ResponseWriter, status int, response interface{}) {
func Read(rw http.ResponseWriter, r *http.Request, value interface{}) bool {
err := json.NewDecoder(r.Body).Decode(value)
if err != nil {
Write(rw, http.StatusBadRequest, Response{
Write(rw, http.StatusBadRequest, codersdk.Response{
Message: "Request body must be valid JSON.",
Detail: err.Error(),
})
Expand All @@ -123,21 +99,21 @@ func Read(rw http.ResponseWriter, r *http.Request, value interface{}) bool {
err = validate.Struct(value)
var validationErrors validator.ValidationErrors
if errors.As(err, &validationErrors) {
apiErrors := make([]Error, 0, len(validationErrors))
apiErrors := make([]codersdk.ValidationError, 0, len(validationErrors))
for _, validationError := range validationErrors {
apiErrors = append(apiErrors, Error{
apiErrors = append(apiErrors, codersdk.ValidationError{
Field: validationError.Field(),
Detail: fmt.Sprintf("Validation failed for tag %q with value: \"%v\"", validationError.Tag(), validationError.Value()),
})
}
Write(rw, http.StatusBadRequest, Response{
Write(rw, http.StatusBadRequest, codersdk.Response{
Message: "Validation failed.",
Validations: apiErrors,
})
return false
}
if err != nil {
Write(rw, http.StatusInternalServerError, Response{
Write(rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error validating request body payload.",
Detail: err.Error(),
})
Expand Down
5 changes: 3 additions & 2 deletions coderd/httpapi/httpapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import (
"github.com/stretchr/testify/require"

"github.com/coder/coder/coderd/httpapi"
"github.com/coder/coder/codersdk"
)

func TestWrite(t *testing.T) {
t.Parallel()
t.Run("NoErrors", func(t *testing.T) {
t.Parallel()
rw := httptest.NewRecorder()
httpapi.Write(rw, http.StatusOK, httpapi.Response{
httpapi.Write(rw, http.StatusOK, codersdk.Response{
Message: "Wow.",
})
var m map[string]interface{}
Expand Down Expand Up @@ -71,7 +72,7 @@ func TestRead(t *testing.T) {

var validate toValidate
require.False(t, httpapi.Read(rw, r, &validate))
var v httpapi.Response
var v codersdk.Response
err := json.NewDecoder(rw.Body).Decode(&v)
require.NoError(t, err)
require.Len(t, v.Validations, 1)
Expand Down
14 changes: 8 additions & 6 deletions coderd/httpapi/queryparams.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (

"github.com/google/uuid"

"github.com/coder/coder/codersdk"

"golang.org/x/xerrors"
)

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

func NewQueryParamParser() *QueryParamParser {
return &QueryParamParser{
Errors: []Error{},
Errors: []codersdk.ValidationError{},
}
}

func (p *QueryParamParser) Int(vals url.Values, def int, queryParam string) int {
v, err := parseQueryParam(vals, strconv.Atoi, def, queryParam)
if err != nil {
p.Errors = append(p.Errors, Error{
p.Errors = append(p.Errors, codersdk.ValidationError{
Field: queryParam,
Detail: fmt.Sprintf("Query param %q must be a valid integer (%s)", queryParam, err.Error()),
})
Expand All @@ -47,7 +49,7 @@ func (p *QueryParamParser) UUIDorMe(vals url.Values, def uuid.UUID, me uuid.UUID
func (p *QueryParamParser) UUID(vals url.Values, def uuid.UUID, queryParam string) uuid.UUID {
v, err := parseQueryParam(vals, uuid.Parse, def, queryParam)
if err != nil {
p.Errors = append(p.Errors, Error{
p.Errors = append(p.Errors, codersdk.ValidationError{
Field: queryParam,
Detail: fmt.Sprintf("Query param %q must be a valid uuid", queryParam),
})
Expand Down Expand Up @@ -75,7 +77,7 @@ func (p *QueryParamParser) UUIDs(vals url.Values, def []uuid.UUID, queryParam st
return ids, nil
}, def, queryParam)
if err != nil {
p.Errors = append(p.Errors, Error{
p.Errors = append(p.Errors, codersdk.ValidationError{
Field: queryParam,
Detail: fmt.Sprintf("Query param %q has invalid uuids: %q", queryParam, err.Error()),
})
Expand Down Expand Up @@ -105,7 +107,7 @@ func (*QueryParamParser) Strings(vals url.Values, def []string, queryParam strin
func ParseCustom[T any](parser *QueryParamParser, vals url.Values, def T, queryParam string, parseFunc func(v string) (T, error)) T {
v, err := parseQueryParam(vals, parseFunc, def, queryParam)
if err != nil {
parser.Errors = append(parser.Errors, Error{
parser.Errors = append(parser.Errors, codersdk.ValidationError{
Field: queryParam,
Detail: fmt.Sprintf("Query param %q has invalid value: %s", queryParam, err.Error()),
})
Expand Down
Loading