Skip to content

Commit

Permalink
To/api errors (#7914)
Browse files Browse the repository at this point in the history
* Add api.Errors interface

* Add usage example
  • Loading branch information
ocket8888 authored Jan 19, 2024
1 parent d5b3f30 commit 258bcfc
Show file tree
Hide file tree
Showing 2 changed files with 436 additions and 0 deletions.
260 changes: 260 additions & 0 deletions traffic_ops/traffic_ops_golang/api/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
package api

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import (
"errors"
"fmt"
"net/http"
)

// Errs is the concrete implementation of Errors, which is used so that we can
// deal with Errors everywhere without having to handle a bunch of
// (de/)referencing everywhere.
type Errs struct {
// code is the HTTP response code. If no error has occurred, this *should*
// be http.StatusOK.
code int
// systemError is an error that occurred internally; not safe for exposure
// to the user.
systemError error
// userError is an error that should be shown to the user to explain why
// their request failed.
userError error
}

// Code returns the HTTP response code. If no error has occurred, this
// *should* return http.StatusOK.
func (e *Errs) Code() int {
if e == nil {
return http.StatusOK
}
return e.code
}

// SetCode sets the HTTP response code returned by `Code`.
func (e *Errs) SetCode(code int) {
e.code = code
}

// SystemError returns an error that occurred internally; not safe for
// exposure to the user.
func (e *Errs) SystemError() error {
if e == nil {
return nil
}
return e.systemError
}

// UserError returns an error that should be shown to the user to explain
// why their request failed. This **must** return `nil` whenever `Code`
// returns a value above 499.
func (e *Errs) UserError() error {
if e == nil {
return nil
}
return e.userError
}

// SetSystemErrorString sets the Errs' system-level error to a new error
// containing the passed message.
func (e *Errs) SetSystemErrorString(err string) {
e.systemError = errors.New(err)
}

// SetSystemError sets the Errs' system-level error to the passed error.
func (e *Errs) SetSystemError(err error) {
e.systemError = err
}

// SetSystemErrorf sets the Errs' system-level error to a new error containing
// the passed formatted message, in the fmt package's syntax (allows wrapping
// via %w).
func (e *Errs) SetSystemErrorf(format string, args ...any) {
e.systemError = fmt.Errorf(format, args...)
}

// SetUserError sets the Errs' user-level error to the passed error.
func (e *Errs) SetUserError(err error) {
e.userError = err
}

// SetUserErrorString sets the Errs' user-level error to a new error containing
// the passed message.
func (e *Errs) SetUserErrorString(err string) {
e.userError = errors.New(err)
}

// SetUserErrorf sets the Errs' user-level error to a new error containing the
// passed formatted message, in the fmt package's syntax (allows wrapping via
// %w).
func (e *Errs) SetUserErrorf(format string, args ...any) {
e.userError = fmt.Errorf(format, args...)
}

// String implements the fmt.Stringer interface.
func (e *Errs) String() string {
return fmt.Sprintf("%d %s, SystemError='%v', UserError='%v'", e.code, http.StatusText(e.code), e.systemError, e.userError)
}

// Error implements the error interface. This will return the user error, if
// there is one, otherwise it will return the system error. In the case that no
// actual error has occurred, it will neturn an empty string. This panics if the
// Errs it is called on is nil (just like a normal error would).
func (e *Errs) Error() string {
if e.userError != nil {
return e.userError.Error()
}
if e.systemError != nil {
return e.systemError.Error()
}
return ""
}

// Errors represents a set of zero to two errors to be handled by the API.
type Errors interface {
// Code returns the HTTP response code. If no error has occurred, this
// *should* return http.StatusOK.
Code() int
// SetCode sets the HTTP response code returned by `Code`. In general, you
// probably won't need to use this.
SetCode(int)
// SystemError returns an error that occurred internally; not safe for
// exposure to the user.
SystemError() error
// SetSystemErrorString sets the Errors' system-level error to the passed
// error. In general, you probably won't need to use this.
SetSystemError(err error)
// SetSystemErrorf sets the Errors' system-level error to a new error
// containing the passed formatted message, in the fmt package's syntax
// (allows wrapping via %w). In general, you probably won't need to use
// this.
SetSystemErrorf(format string, args ...any)
// SetSystemErrorString sets the Errors' system-level error to a new error
// containing the passed message. In general, you probably won't need to use
// this.
SetSystemErrorString(err string)
// UserError returns an error that should be shown to the user to explain
// why their request failed. This **must** return `nil` whenever `Code`
// returns a value above 499.
UserError() error
// SetUserErrorString sets the Errors' user-level error to the passed error.
// In general, you probably won't need to use this.
SetUserError(err error)
// SetUserErrorf sets the Errors' user-level error to a new error containing
// the passed formatted message, in the fmt package's syntax (allows
// wrapping via %w). In general, you probably won't need to use this.
SetUserErrorf(format string, args ...any)
// SetUserErrorString sets the Errors' user-level error to a new error
// containing the passed message. In general, you probably won't need to use
// this.
SetUserErrorString(err string)

// String implements the fmt.Stringer interface.
String() string
// Error implements the error interface. This will return the user error, if
// there is one, otherwise it will return the system error. In the case that
// no actual error has occurred, it will neturn an empty string. This panics
// if the Errors it is called on is nil (just like a normal error would).
Error() string
}

// NewErrors directly constructs a new Errors using the passed information.
func NewErrors(code int, userError, systemError error) Errors {
return &Errs{
code: code,
systemError: systemError,
userError: userError,
}
}

// NewSystemError creates an Errors that only contains the given system error,
// and has the appropriate response code.
func NewSystemError(err error) Errors {
return &Errs{
code: http.StatusInternalServerError,
systemError: err,
userError: nil,
}
}

// NewSystemErrorString creates an Errors that only contains a system error,
// having the appropriate response code and containing the given message.
func NewSystemErrorString(err string) Errors {
return &Errs{
code: http.StatusInternalServerError,
systemError: errors.New(err),
userError: nil,
}
}

// NewSystemErrorf creates an Errors that only contains a system error, having
// the appropriate response code, and containing a message from the given
// format arguments (using the fmt package's syntax, with %w allowed for
// wrapping).
func NewSystemErrorf(format string, args ...any) Errors {
return &Errs{
code: http.StatusInternalServerError,
systemError: fmt.Errorf(format, args...),
userError: nil,
}
}

// NewUserError creates an Errors that only contains the given user error,
// and has the appropriate response code.
func NewUserError(err error) Errors {
return &Errs{
code: http.StatusInternalServerError,
systemError: nil,
userError: err,
}
}

// NewUserErrorString creates an Errors that only contains a user error,
// having the appropriate response code and containing the given message.
func NewUserErrorString(err string) Errors {
return &Errs{
code: http.StatusInternalServerError,
systemError: nil,
userError: errors.New(err),
}
}

// NewUserErrorf creates an Errors that only contains a user error, having
// the appropriate response code, and containing a message from the given
// format arguments (using the fmt package's syntax, with %w allowed for
// wrapping).
func NewUserErrorf(format string, args ...any) Errors {
return &Errs{
code: http.StatusInternalServerError,
systemError: nil,
userError: fmt.Errorf(format, args...),
}
}

// NewResourceModifiedError creates an Errors that only contains an HTTP
// Precondition Failed status code and associated error message.
func NewResourceModifiedError() Errors {
return &Errs{
code: http.StatusPreconditionFailed,
systemError: nil,
userError: ResourceModifiedError,
}
}
Loading

0 comments on commit 258bcfc

Please sign in to comment.