This is an automated email from the ASF dual-hosted git repository.
zrhoffman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git
The following commit(s) were added to refs/heads/master by this push:
new 258bcfc6ac To/api errors (#7914)
258bcfc6ac is described below
commit 258bcfc6acc3be5c588f15357438d1fedb75bb50
Author: ocket8888 <[email protected]>
AuthorDate: Fri Jan 19 10:24:27 2024 -0700
To/api errors (#7914)
* Add api.Errors interface
* Add usage example
---
traffic_ops/traffic_ops_golang/api/errors.go | 260 ++++++++++++++++++++++
traffic_ops/traffic_ops_golang/api/errors_test.go | 176 +++++++++++++++
2 files changed, 436 insertions(+)
diff --git a/traffic_ops/traffic_ops_golang/api/errors.go
b/traffic_ops/traffic_ops_golang/api/errors.go
new file mode 100644
index 0000000000..22e2aef14a
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/api/errors.go
@@ -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,
+ }
+}
diff --git a/traffic_ops/traffic_ops_golang/api/errors_test.go
b/traffic_ops/traffic_ops_golang/api/errors_test.go
new file mode 100644
index 0000000000..f2fff8a410
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/api/errors_test.go
@@ -0,0 +1,176 @@
+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"
+)
+
+func ExampleErrors_String() {
+ fmt.Println(NewErrors(http.StatusOK, nil, nil).String())
+
+ // Output: 200 OK, SystemError='<nil>', UserError='<nil>'
+}
+func ExampleErrors_Error() {
+ fmt.Println(NewErrors(http.StatusAccepted, errors.New("user error"),
errors.New("system error")).Error())
+ // Output: user error
+}
+func ExampleErrors_Code() {
+ e := NewErrors(http.StatusOK, nil, nil)
+ fmt.Println(e.Code())
+ // Output: 200
+}
+func ExampleErrors_SetCode() {
+ e := NewErrors(http.StatusOK, errors.New("something happened"), nil)
+ e.SetCode(http.StatusBadRequest)
+ fmt.Println(e.String())
+ // Output: 400 Bad Request, SystemError='<nil>', UserError='something
happened'
+}
+func ExampleErrors_SystemError() {
+ e := NewErrors(http.StatusAccepted, nil, errors.New("testquest"))
+ fmt.Println(e.SystemError())
+ // Output: testquest
+}
+func ExampleErrors_UserError() {
+ e := NewErrors(http.StatusAccepted, errors.New("testquest"), nil)
+ fmt.Println(e.UserError())
+ // Output: testquest
+}
+func ExampleErrors_SetSystemError() {
+ e := NewErrors(http.StatusInternalServerError, nil, nil)
+ e.SetSystemError(errors.New("testquest"))
+ fmt.Println(e.String())
+ // Output: 500 Internal Server Error, SystemError='testquest',
UserError='<nil>'
+}
+func ExampleErrors_SetSystemErrorString() {
+ e := NewErrors(http.StatusInternalServerError, nil, nil)
+ e.SetSystemErrorString("testquest")
+ fmt.Println(e.String())
+ // Output: 500 Internal Server Error, SystemError='testquest',
UserError='<nil>'
+}
+func ExampleErrors_SetSystemErrorf() {
+ e := NewErrors(http.StatusInternalServerError, nil, nil)
+ e.SetSystemErrorf("test: %w", errors.New("quest"))
+ fmt.Println(e.String())
+ // Output: 500 Internal Server Error, SystemError='test: quest',
UserError='<nil>'
+}
+func ExampleErrors_SetUserError() {
+ e := NewErrors(http.StatusInternalServerError, nil, nil)
+ e.SetUserError(errors.New("testquest"))
+ fmt.Println(e.String())
+ // Output: 500 Internal Server Error, SystemError='<nil>',
UserError='testquest'
+}
+func ExampleErrors_SetUserErrorString() {
+ e := NewErrors(http.StatusInternalServerError, nil, nil)
+ e.SetUserErrorString("testquest")
+ fmt.Println(e.String())
+ // Output: 500 Internal Server Error, SystemError='<nil>',
UserError='testquest'
+}
+func ExampleErrors_SetUserErrorf() {
+ e := NewErrors(http.StatusInternalServerError, nil, nil)
+ e.SetUserErrorf("test: %w", errors.New("quest"))
+ fmt.Println(e.String())
+ // Output: 500 Internal Server Error, SystemError='<nil>',
UserError='test: quest'
+}
+func ExampleErrors() {
+ handler := func(fail bool) Errors {
+ if fail {
+ return NewSystemErrorString("failed")
+ }
+ return nil
+ }
+
+ var errs error = handler(true)
+ fmt.Println(errs)
+
+ errs = handler(false)
+ fmt.Println(errs)
+
+ // Output: failed
+ // <nil>
+}
+
+func ExampleErrs_Code() {
+ var e *Errs
+ fmt.Println(e.Code())
+ // Output: 200
+}
+func ExampleErrs_SystemError() {
+ var e *Errs
+ fmt.Println(e.SystemError())
+ // Output: <nil>
+}
+func ExampleErrs_UserError() {
+ var e *Errs
+ fmt.Println(e.UserError())
+ // Output: <nil>
+}
+func ExampleErrs_Error() {
+ e := &Errs{}
+ fmt.Println(e.Error())
+
+ e.SetSystemError(errors.New("testquest"))
+ fmt.Println(e.Error())
+
+ // Output:
+ // testquest
+}
+
+func ExamlpleNewErrors() {
+ fmt.Println(
+ NewErrors(
+ http.StatusForbidden,
+ errors.New("you don't have permission to do that in
that Tenant"),
+ errors.New("user tried to access a forbidden tenant"),
+ ),
+ )
+
+ // Output: 403 Forbidden, SystemError='user tried to access a forbidden
tenant', UserError='you don't have permission to do that in that Tenant'
+}
+func ExampleNewSystemError() {
+ fmt.Println(NewSystemError(errors.New("testquest")).String())
+ // Output: 500 Internal Server Error, SystemError='testquest',
UserError='<nil>'
+}
+func ExampleNewSystemErrorString() {
+ fmt.Println(NewSystemErrorString("testquest").String())
+ // Output: 500 Internal Server Error, SystemError='testquest',
UserError='<nil>'
+}
+func ExampleNewSystemErrorf() {
+ fmt.Println(NewSystemErrorf("test: %w", errors.New("quest")).String())
+ // Output: 500 Internal Server Error, SystemError='test: quest',
UserError='<nil>'
+}
+func ExampleNewUserError() {
+ fmt.Println(NewUserError(errors.New("testquest")).String())
+ // Output: 500 Internal Server Error, SystemError='<nil>',
UserError='testquest'
+}
+func ExampleNewUserErrorString() {
+ fmt.Println(NewUserErrorString("testquest").String())
+ // Output: 500 Internal Server Error, SystemError='<nil>',
UserError='testquest'
+}
+func ExampleNewUserErrorf() {
+ fmt.Println(NewUserErrorf("test: %w", errors.New("quest")).String())
+ // Output: 500 Internal Server Error, SystemError='<nil>',
UserError='test: quest'
+}
+func ExampleNewResourceModifiedError() {
+ fmt.Println(NewResourceModifiedError().String())
+ // Output: 412 Precondition Failed, SystemError='<nil>',
UserError='resource was modified since the time specified by the request
headers'
+}