mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
refactor: drop the URL-based error classifier and hint lookups
Errors are named by the service call that failed: Classify, NotFoundError and ConflictError at the call site. Remove the endpoint parser that guessed the resource from the request path, and the follow-up reads the explainer made at error time (repository state, issue-vs-pull-request, the existing pull request's URL) together with the credentialed transport each API error carried for them. Calls that the endpoint parser covered and the services did not now classify themselves: issues, pull requests, reviews, comments, releases, branches, wiki pages, repositories, organizations and SSH keys. Unclassified failures fall back to the generic "not found (HTTP 404 GET ...)" text. Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
20 files changed
+129
-495
No files matched your search
@@ -55,7 +55,7 @@ func (t captureTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
}
|
||||
data, _ := io.ReadAll(io.LimitReader(resp.Body, maxErrorBody))
|
||||
failure := newAPIError(resp.StatusCode, req.Method, req.URL.Path, t.token, data)
|
||||
failure.bind(req.URL, t.base)
|
||||
failure.bind(req.URL)
|
||||
holder.Store(failure)
|
||||
resp.Body = struct {
|
||||
io.Reader
|
||||
|
||||
@@ -35,8 +35,6 @@ type APIError struct {
|
||||
// Instance is the installation URL the failed request was sent to, when it was recorded
|
||||
// from a request.
|
||||
Instance string
|
||||
// lookup repeats reads on the failed request's instance with its credential.
|
||||
lookup http.RoundTripper
|
||||
}
|
||||
|
||||
func (e *APIError) Error() string {
|
||||
@@ -68,24 +66,11 @@ func (e *APIError) request() string {
|
||||
return strings.TrimSpace(fmt.Sprintf("HTTP %d %s %s", e.Status, e.Method, e.Endpoint))
|
||||
}
|
||||
|
||||
// bind records the instance of the request to u and the credentialed transport rt, so the
|
||||
// failure can later be explained with LookupClient.
|
||||
func (e *APIError) bind(u *url.URL, rt http.RoundTripper) {
|
||||
prefix, _, ok := strings.Cut(u.Path, "/api/v1/")
|
||||
if !ok {
|
||||
return
|
||||
// bind records the instance of the request to u.
|
||||
func (e *APIError) bind(u *url.URL) {
|
||||
if prefix, _, ok := strings.Cut(u.Path, "/api/v1/"); ok {
|
||||
e.Instance = (&url.URL{Scheme: u.Scheme, Host: u.Host, Path: prefix}).String()
|
||||
}
|
||||
instance := url.URL{Scheme: u.Scheme, Host: u.Host, Path: prefix}
|
||||
e.Instance, e.lookup = instance.String(), rt
|
||||
}
|
||||
|
||||
// LookupClient returns an HTTP client that repeats reads on the instance of the failed request,
|
||||
// Instance, with its credential, or nil when the error was not recorded from a request.
|
||||
func (e *APIError) LookupClient() *http.Client {
|
||||
if e.lookup == nil {
|
||||
return nil
|
||||
}
|
||||
return &http.Client{Transport: e.lookup, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -213,30 +213,3 @@ func TestDebugRedactsOnlySensitiveQueryValues(t *testing.T) {
|
||||
require.Equal(t, tc.redacted, sensitiveQuery(tc.path, tc.key), "%s %s", tc.path, tc.key)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIErrorLookupRepeatsReadsWithTheFailedCredential(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, "token secret", r.Header.Get("Authorization"))
|
||||
require.Equal(t, "bob", r.Header.Get("Sudo"))
|
||||
if r.URL.Path == "/base/api/v1/repos/a/b" {
|
||||
_, _ = w.Write([]byte(`{"full_name":"a/b","has_issues":false}`))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_, _ = w.Write([]byte(`{"message":"not found"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
client := New(server.URL+"/base", "secret", false)
|
||||
client.Sudo = "bob"
|
||||
_, err := client.Do(context.Background(), http.MethodGet, "repos/a/b/issues/9", nil, nil)
|
||||
var apiErr *APIError
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, server.URL+"/base", apiErr.Instance)
|
||||
lookup := apiErr.LookupClient()
|
||||
require.NotNil(t, lookup)
|
||||
resp, err := lookup.Get(server.URL + "/base/api/v1/repos/a/b")
|
||||
require.NoError(t, err)
|
||||
_ = resp.Body.Close()
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
require.Nil(t, (&APIError{Status: http.StatusNotFound}).LookupClient(), "errors built without a request cannot look up")
|
||||
}
|
||||
Reference in new issue
Block a user