...

Source file src/github.com/go-kivik/kivik/v4/int/errors/errors.go

Documentation: github.com/go-kivik/kivik/v4/int/errors

     1  // Licensed under the Apache License, Version 2.0 (the "License"); you may not
     2  // use this file except in compliance with the License. You may obtain a copy of
     3  // the License at
     4  //
     5  //  http://www.apache.org/licenses/LICENSE-2.0
     6  //
     7  // Unless required by applicable law or agreed to in writing, software
     8  // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     9  // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    10  // License for the specific language governing permissions and limitations under
    11  // the License.
    12  
    13  // Package errors provides some internal error types and utilities.
    14  package errors
    15  
    16  import (
    17  	"errors"
    18  	"fmt"
    19  	"net/http"
    20  	"regexp"
    21  	"strconv"
    22  	"strings"
    23  )
    24  
    25  // CompositeError represents an HTTP status, encoded in the first byte as the
    26  // status - 400, plus the error message.
    27  type CompositeError string
    28  
    29  func (c CompositeError) Error() string {
    30  	return "kivik: " + string(c[4:])
    31  }
    32  
    33  // HTTPStatus returns c's HTTP status code.
    34  func (c CompositeError) HTTPStatus() int {
    35  	i, _ := strconv.Atoi(string(c[:3]))
    36  	return i
    37  }
    38  
    39  // Error represents an error returned by Kivik.
    40  //
    41  // This type definition is not guaranteed to remain stable, or even exported.
    42  // When examining errors programmatically, you should rely instead on the
    43  // HTTPStatus() function in this package, rather than on directly observing
    44  // the fields of this type.
    45  type Error struct {
    46  	// Status is the HTTP status code associated with this error. Normally
    47  	// this is the actual HTTP status returned by the server, but in some cases
    48  	// it may be generated by Kivik directly.
    49  	Status int
    50  
    51  	// Message is the error message.
    52  	Message string
    53  
    54  	// Err is the originating error, if any.
    55  	Err error
    56  }
    57  
    58  var (
    59  	_ error       = &Error{}
    60  	_ statusCoder = &Error{}
    61  )
    62  
    63  func (e *Error) Error() string {
    64  	if e.Err == nil {
    65  		return e.msg()
    66  	}
    67  	if e.Message == "" {
    68  		return e.Err.Error()
    69  	}
    70  	return e.Message + ": " + e.Err.Error()
    71  }
    72  
    73  // HTTPStatus returns the HTTP status code associated with the error, or 500
    74  // (internal server error), if none.
    75  func (e *Error) HTTPStatus() int {
    76  	if e.Status == 0 {
    77  		return http.StatusInternalServerError
    78  	}
    79  	return e.Status
    80  }
    81  
    82  // Unwrap satisfies the errors wrapper interface.
    83  func (e *Error) Unwrap() error {
    84  	return e.Err
    85  }
    86  
    87  // Format implements [fmt.Formatter].
    88  func (e *Error) Format(f fmt.State, c rune) {
    89  	const partsLen = 3
    90  	parts := make([]string, 0, partsLen)
    91  	if e.Message != "" {
    92  		parts = append(parts, e.Message)
    93  	}
    94  	if c == 'v' {
    95  		if f.Flag('+') {
    96  			parts = append(parts, fmt.Sprintf("%d / %s", e.Status, http.StatusText(e.Status)))
    97  		}
    98  	}
    99  	if e.Err != nil {
   100  		parts = append(parts, e.Err.Error())
   101  	}
   102  	_, _ = fmt.Fprint(f, strings.Join(parts, ": "))
   103  }
   104  
   105  func (e *Error) msg() string {
   106  	switch e.Message {
   107  	case "":
   108  		return http.StatusText(e.HTTPStatus())
   109  	default:
   110  		return e.Message
   111  	}
   112  }
   113  
   114  type statusCoder interface {
   115  	HTTPStatus() int
   116  }
   117  
   118  // HTTPStatus returns the HTTP status code embedded in the error, or 500
   119  // (internal server error), if there was no specified status code.  If err is
   120  // nil, HTTPStatus returns 0.
   121  func HTTPStatus(err error) int {
   122  	if err == nil {
   123  		return 0
   124  	}
   125  	var coder statusCoder
   126  	for {
   127  		if errors.As(err, &coder) {
   128  			return coder.HTTPStatus()
   129  		}
   130  		if uw := errors.Unwrap(err); uw != nil {
   131  			err = uw
   132  			continue
   133  		}
   134  		return http.StatusInternalServerError
   135  	}
   136  }
   137  
   138  // StatusErrorDiff returns the empty string if the expected error string and
   139  // status match err. Otherwise, it returns a description of the mismatch.
   140  func StatusErrorDiff(wantErr string, wantStatus int, err error) string {
   141  	var (
   142  		msg    string
   143  		status int
   144  	)
   145  	if err != nil {
   146  		status = HTTPStatus(err)
   147  		msg = err.Error()
   148  	}
   149  	if msg != wantErr || status != wantStatus {
   150  		return fmt.Sprintf("Unexpected error: %s [%d] (expected: %s [%d])",
   151  			err, status, wantErr, wantStatus)
   152  	}
   153  	return ""
   154  }
   155  
   156  // StatusErrorDiffRE returns the empty string if the expected error RE and
   157  // status match err. Otherwise, it returns a description of the mismatch.
   158  func StatusErrorDiffRE(wantErrRE string, wantStatus int, err error) string {
   159  	re := regexp.MustCompile(wantErrRE)
   160  	var (
   161  		msg    string
   162  		status int
   163  	)
   164  	if err != nil {
   165  		status = HTTPStatus(err)
   166  		msg = err.Error()
   167  	}
   168  	if !re.MatchString(msg) || status != wantStatus {
   169  		return fmt.Sprintf("Unexpected error: %s [%d] (expected: %s [%d])",
   170  			err, status, re, wantStatus)
   171  	}
   172  	return ""
   173  }
   174  

View as plain text