ref: move httpapi.Reponse into codersdk (#2954)

This commit is contained in:
Jon Ayers
2022-07-12 19:15:02 -05:00
committed by GitHub
parent dde51f1caa
commit 7e9819f2a8
53 changed files with 524 additions and 486 deletions

View File

@ -11,6 +11,8 @@ import (
"strings"
"github.com/go-playground/validator/v10"
"github.com/coder/coder/codersdk"
)
var (
@ -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.",
})
}
@ -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(),
})
@ -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(),
})

View File

@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/coder/coder/coderd/httpapi"
"github.com/coder/coder/codersdk"
)
func TestWrite(t *testing.T) {
@ -19,7 +20,7 @@ func TestWrite(t *testing.T) {
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{}
@ -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)

View File

@ -8,6 +8,8 @@ import (
"github.com/google/uuid"
"github.com/coder/coder/codersdk"
"golang.org/x/xerrors"
)
@ -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()),
})
@ -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),
})
@ -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()),
})
@ -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()),
})