File: httplib.go

package info (click to toggle)
golang-github-gravitational-trace 1.1.15-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 168 kB
  • sloc: makefile: 2
file content (127 lines) | stat: -rw-r--r-- 3,235 bytes parent folder | download
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package trace

import (
	"encoding/json"
	"fmt"
	"net/http"
)

// WriteError sets up HTTP error response and writes it to writer w
func WriteError(w http.ResponseWriter, err error) {
	if !IsAggregate(err) {
		replyJSON(w, ErrorToCode(err), err)
		return
	}
	for i := 0; i < maxHops; i++ {
		var aggErr Aggregate
		var ok bool
		if aggErr, ok = Unwrap(err).(Aggregate); !ok {
			break
		}
		errors := aggErr.Errors()
		if len(errors) == 0 {
			break
		}
		err = errors[0]
	}
	replyJSON(w, ErrorToCode(err), err)
}

// ErrorToCode returns an appropriate HTTP status code based on the provided error type
func ErrorToCode(err error) int {
	switch {
	case IsAggregate(err):
		return http.StatusGatewayTimeout
	case IsNotFound(err):
		return http.StatusNotFound
	case IsBadParameter(err) || IsOAuth2(err):
		return http.StatusBadRequest
	case IsNotImplemented(err):
		return http.StatusNotImplemented
	case IsCompareFailed(err):
		return http.StatusPreconditionFailed
	case IsAccessDenied(err):
		return http.StatusForbidden
	case IsAlreadyExists(err):
		return http.StatusConflict
	case IsLimitExceeded(err):
		return http.StatusTooManyRequests
	case IsConnectionProblem(err):
		return http.StatusGatewayTimeout
	default:
		return http.StatusInternalServerError
	}
}

// ReadError converts http error to internal error type
// based on HTTP response code and HTTP body contents
// if status code does not indicate error, it will return nil
func ReadError(statusCode int, respBytes []byte) error {
	if statusCode >= http.StatusOK && statusCode < http.StatusBadRequest {
		return nil
	}
	var err error
	switch statusCode {
	case http.StatusNotFound:
		err = &NotFoundError{}
	case http.StatusBadRequest:
		err = &BadParameterError{}
	case http.StatusNotImplemented:
		err = &NotImplementedError{}
	case http.StatusPreconditionFailed:
		err = &CompareFailedError{}
	case http.StatusForbidden:
		err = &AccessDeniedError{}
	case http.StatusConflict:
		err = &AlreadyExistsError{}
	case http.StatusTooManyRequests:
		err = &LimitExceededError{}
	case http.StatusGatewayTimeout:
		err = &ConnectionProblemError{}
	default:
		err = &RawTrace{}
	}
	return wrapProxy(unmarshalError(err, respBytes))
}

func replyJSON(w http.ResponseWriter, code int, err error) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	var out []byte
	// wrap regular errors in order to achieve unification
	// and provide structurally consistent responses
	var obj interface{} = err
	if _, ok := err.(*TraceErr); !ok {
		obj = &TraceErr{Err: err}
	}
	out, err = json.MarshalIndent(obj, "", "    ")
	if err != nil {
		out = []byte(fmt.Sprintf(`{"error": {"message": "internal marshal error: %v"}}`, err))
	}
	w.Write(out)
}

func unmarshalError(err error, responseBody []byte) error {
	if len(responseBody) == 0 {
		return err
	}
	var raw RawTrace
	if err2 := json.Unmarshal(responseBody, &raw); err2 != nil {
		return err
	}
	if len(raw.Traces) != 0 && len(raw.Err) != 0 {
		err2 := json.Unmarshal(raw.Err, err)
		if err2 != nil {
			return err
		}
		return &TraceErr{
			Traces:   raw.Traces,
			Err:      err,
			Message:  raw.Message,
			Messages: raw.Messages,
			Fields:   raw.Fields,
		}
	}
	json.Unmarshal(responseBody, err)
	return err
}