File: errors.go

package info (click to toggle)
sigsum-go 0.14.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,076 kB
  • sloc: sh: 809; makefile: 93
file content (110 lines) | stat: -rw-r--r-- 3,176 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package api

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

// Partial success from AddLeaf, caller should retry.
var ErrAccepted *Error = NewError(http.StatusAccepted, fmt.Errorf("Accepted")) // 202

// E.g., out of range request parameters.
var ErrBadRequest *Error = NewError(http.StatusBadRequest, fmt.Errorf("Bad Request")) // 400

// Unauthorized, typically because signature is invalid, or public key
// not recognized.
var ErrForbidden *Error = NewError(http.StatusForbidden, fmt.Errorf("Forbidden")) // 403

// E.g., GetInclusionProof fails because leaf isn't included.
var ErrNotFound *Error = NewError(http.StatusNotFound, fmt.Errorf("Not Found")) // 404

// Failure of witness AddCheckpoint, caller should retry with correct
// tree size.
var ErrConflict *Error = NewError(http.StatusConflict, fmt.Errorf("Conflict")) // 409

// Failure of witness AddCheckpointHead, invalid consistency proof.
var ErrUnprocessableEntity *Error = NewError(422, fmt.Errorf("Unprocessable Entity"))

// Error due to exceeded rate limit.
var ErrTooManyRequests *Error = NewError(http.StatusTooManyRequests, fmt.Errorf("Too Many Requests")) // 429

// An error with an associated HTTP status code.
type Error struct {
	statusCode int // HTTP status code for this error
	err        error
}

func (e *Error) StatusCode() int {
	return e.statusCode
}

func (e *Error) Error() string {
	return fmt.Sprintf("(%d) %s", e.statusCode, e.err)
}

func (e *Error) Unwrap() error {
	return e.err
}

// Return a new error, with same status code, but the supplied
// underlying error.
func (e *Error) WithError(err error) *Error {
	return &Error{statusCode: e.statusCode, err: err}
}

type errorWithOldSize struct {
	oldSize uint64
}

func (e *errorWithOldSize) Error() string {
	return fmt.Sprintf("old size must be %d", e.oldSize)
}

func (e *Error) WithOldSize(oldSize uint64) *Error {
	if e.statusCode != http.StatusConflict {
		panic("only Conflict (409) errors can have an associated old size")
	}
	return e.WithError(&errorWithOldSize{oldSize})
}

// An error is considered matching if the status code is the same.
// Example usage:
//
//	if errors.Is(api.ErrNotFound, err) {...}
func (e *Error) Is(err error) bool {
	if err, ok := err.(*Error); ok {
		return e.statusCode == err.statusCode
	}
	return false
}

func NewError(statusCode int, err error) *Error {
	// TODO: Allow err == nil, and return nil for that case?
	if statusCode == http.StatusOK || err == nil {
		panic(fmt.Sprintf("Invalid call to NewError, status = %d, err = %v",
			statusCode, err))
	}
	return &Error{statusCode: statusCode, err: err}
}

func ErrorStatusCode(err error) int {
	var apiError *Error
	if errors.As(err, &apiError) {
		return apiError.StatusCode()
	}
	return http.StatusInternalServerError
}

// An error associated with an old size must be a Conflict error with
// the wrapped error being an errorWithOldSize.
func ErrorConflictOldSize(err error) (uint64, bool) {
	var apiError *Error
	if errors.As(err, &apiError) && apiError.statusCode == http.StatusConflict {
		var oldSizeError *errorWithOldSize
		if errors.As(apiError.err, &oldSizeError) {
			return oldSizeError.oldSize, true
		}
	}
	return 0, false
}