1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package rpc
4
5import (
6 "context"
7 "errors"
8
9 "connectrpc.com/connect"
10 "github.com/bnema/zerowrap"
11
12 "bnema.dev/ermite/internal/domain"
13)
14
15// connectError maps an error of a use case to a Connect error with the code
16// the contract documents. Errors it does not know are internal: they are
17// logged here and the client only sees a generic message.
18func connectError(ctx context.Context, err error) error {
19 if err == nil {
20 return nil
21 }
22 var ce *connect.Error
23 if errors.As(err, &ce) {
24 return err
25 }
26 if code, ok := codeOf(err); ok {
27 return connect.NewError(code, err)
28 }
29 log := zerowrap.FromCtx(ctx)
30 log.Error().Err(err).Msg("api request failed")
31 return connect.NewError(connect.CodeInternal, errors.New("internal error"))
32}
33
34// codeOf finds the Connect code of a domain error.
35func codeOf(err error) (connect.Code, bool) {
36 switch {
37 case errors.Is(err, context.Canceled):
38 return connect.CodeCanceled, true
39 case errors.Is(err, context.DeadlineExceeded):
40 return connect.CodeDeadlineExceeded, true
41 case errors.Is(err, domain.ErrNotFound):
42 return connect.CodeNotFound, true
43 case errors.Is(err, domain.ErrAlreadyExists):
44 return connect.CodeAlreadyExists, true
45 case errors.Is(err, domain.ErrUnauthenticated):
46 return connect.CodeUnauthenticated, true
47 case errors.Is(err, domain.ErrForbidden), errors.Is(err, domain.ErrRejected):
48 return connect.CodePermissionDenied, true
49 case errors.Is(err, domain.ErrPreconditionFailed):
50 return connect.CodeFailedPrecondition, true
51 case errors.Is(err, domain.ErrConflict):
52 return connect.CodeAborted, true
53 case errors.Is(err, domain.ErrResourceExhausted):
54 return connect.CodeResourceExhausted, true
55 }
56 for _, invalid := range invalidErrors {
57 if errors.Is(err, invalid) {
58 return connect.CodeInvalidArgument, true
59 }
60 }
61 return 0, false
62}
63
64// invalidErrors are the domain errors that mean the request was malformed.
65var invalidErrors = []error{
66 domain.ErrInvalid, domain.ErrInvalidName, domain.ErrInvalidVisibility,
67 domain.ErrInvalidScope, domain.ErrInvalidToken,
68 domain.ErrInvalidObjectID, domain.ErrInvalidRevision, domain.ErrInvalidPath,
69 domain.ErrInvalidPageToken, domain.ErrInvalidPageSize, domain.ErrInvalidRefKind,
70 domain.ErrNotDirectory, domain.ErrNotFile,
71}