Skip to content

Commit 406cb82

Browse files
CLD-694: Catalog client error handling (#514)
## CLD-694: Catalog client error handling - Adds `checkResponseStatus` helper function to convert ResponseStatus to gprc `errors` - Handle errors in client using default grpc error handling - Fix linting issues were creds `ServerName` field is deprecated for grpc Test require running catalog and currently are skipped in CI (functionality will be added in another ticket) Local test execution <img width="453" height="230" alt="image" src="https://github.com/user-attachments/assets/275ba40a-7321-4f62-9c0a-a18f977d221a" /> --------- Co-authored-by: Copilot <[email protected]>
1 parent 5220e9a commit 406cb82

File tree

10 files changed

+288
-74
lines changed

10 files changed

+288
-74
lines changed

.changeset/eight-schools-glow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"chainlink-deployments-framework": minor
3+
---
4+
5+
Handle Catalog ResponseStatus errors as grpc errors

datastore/catalog/remote/address_ref_store.go

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"strings"
87

98
"github.com/Masterminds/semver/v3"
9+
"google.golang.org/grpc/codes"
1010
"google.golang.org/protobuf/types/known/wrapperspb"
1111

1212
"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
@@ -84,12 +84,17 @@ func (s *catalogAddressRefStore) get(
8484
}
8585

8686
// Check for errors in the response
87-
if response.Status != nil && !response.Status.Succeeded {
88-
if strings.Contains(response.Status.GetError(), "No records found") {
89-
return datastore.AddressRef{}, datastore.ErrAddressRefNotFound
87+
if statusErr := parseResponseStatus(response.Status); statusErr != nil {
88+
st, sterr := parseStatusError(statusErr)
89+
if sterr != nil {
90+
return datastore.AddressRef{}, sterr
9091
}
9192

92-
return datastore.AddressRef{}, fmt.Errorf("request failed: %s", response.Status.Error)
93+
if st.Code() == codes.NotFound {
94+
return datastore.AddressRef{}, fmt.Errorf("%w: %s", datastore.ErrAddressRefNotFound, statusErr.Error())
95+
}
96+
97+
return datastore.AddressRef{}, fmt.Errorf("get address ref failed: %w", statusErr)
9398
}
9499

95100
// Extract the address find response
@@ -151,8 +156,8 @@ func (s *catalogAddressRefStore) Fetch(_ context.Context) ([]datastore.AddressRe
151156
}
152157

153158
// Check for errors in the response
154-
if response.Status != nil && !response.Status.Succeeded {
155-
return nil, fmt.Errorf("request failed: %s", response.Status.Error)
159+
if err := parseResponseStatus(response.Status); err != nil {
160+
return nil, fmt.Errorf("fetch address refs failed: %w", err)
156161
}
157162

158163
// Extract the address find response
@@ -231,8 +236,8 @@ func (s *catalogAddressRefStore) Add(_ context.Context, record datastore.Address
231236
}
232237

233238
// Check for errors in the edit response
234-
if editResponse.Status != nil && !editResponse.Status.Succeeded {
235-
return fmt.Errorf("edit request failed: %s", editResponse.Status.Error)
239+
if err := parseResponseStatus(editResponse.Status); err != nil {
240+
return fmt.Errorf("add address ref failed: %w", err)
236241
}
237242

238243
// Extract the edit response to validate it
@@ -278,8 +283,8 @@ func (s *catalogAddressRefStore) Upsert(_ context.Context, record datastore.Addr
278283
}
279284

280285
// Check for errors in the response
281-
if response.Status != nil && !response.Status.Succeeded {
282-
return fmt.Errorf("request failed: %s", response.Status.Error)
286+
if err := parseResponseStatus(response.Status); err != nil {
287+
return fmt.Errorf("upsert address ref failed: %w", err)
283288
}
284289

285290
// Extract the edit response to validate it
@@ -338,8 +343,8 @@ func (s *catalogAddressRefStore) Update(ctx context.Context, record datastore.Ad
338343
}
339344

340345
// Check for errors in the edit response
341-
if editResponse.Status != nil && !editResponse.Status.Succeeded {
342-
return fmt.Errorf("edit request failed: %s", editResponse.Status.Error)
346+
if err := parseResponseStatus(editResponse.Status); err != nil {
347+
return fmt.Errorf("update address ref failed: %w", err)
343348
}
344349

345350
// Extract the edit response to validate it

datastore/catalog/remote/chain_metadata_store.go

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8-
"strings"
98
"sync"
109

10+
"google.golang.org/grpc/codes"
1111
"google.golang.org/protobuf/types/known/wrapperspb"
1212

1313
"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
@@ -145,12 +145,17 @@ func (s *catalogChainMetadataStore) get(ignoreTransaction bool, key datastore.Ch
145145
}
146146

147147
// Check for errors in the response
148-
if resp.Status != nil && !resp.Status.Succeeded {
149-
if strings.Contains(resp.Status.GetError(), "No records found") {
150-
return datastore.ChainMetadata{}, datastore.ErrChainMetadataNotFound
148+
if statusErr := parseResponseStatus(resp.Status); statusErr != nil {
149+
st, sterr := parseStatusError(statusErr)
150+
if sterr != nil {
151+
return datastore.ChainMetadata{}, sterr
151152
}
152153

153-
return datastore.ChainMetadata{}, fmt.Errorf("request failed: %s", resp.Status.Error)
154+
if st.Code() == codes.NotFound {
155+
return datastore.ChainMetadata{}, fmt.Errorf("%w: %s", datastore.ErrChainMetadataNotFound, statusErr.Error())
156+
}
157+
158+
return datastore.ChainMetadata{}, fmt.Errorf("get chain metadata failed: %w", statusErr)
154159
}
155160

156161
findResp := resp.GetChainMetadataFindResponse()
@@ -204,8 +209,8 @@ func (s *catalogChainMetadataStore) Fetch(_ context.Context) ([]datastore.ChainM
204209
}
205210

206211
// Check for errors in the response
207-
if resp.Status != nil && !resp.Status.Succeeded {
208-
return nil, fmt.Errorf("request failed: %s", resp.Status.Error)
212+
if err := parseResponseStatus(resp.Status); err != nil {
213+
return nil, fmt.Errorf("fetch chain metadata failed: %w", err)
209214
}
210215

211216
findResp := resp.GetChainMetadataFindResponse()
@@ -363,19 +368,21 @@ func (s *catalogChainMetadataStore) editRecord(record datastore.ChainMetadata, s
363368
}
364369

365370
// Check for errors in the edit response
366-
if resp.Status != nil && !resp.Status.Succeeded {
367-
errorMsg := resp.Status.GetError()
368-
369-
// Check for specific error conditions
370-
if strings.Contains(errorMsg, "no record found to update for") && semantics == pb.EditSemantics_SEMANTICS_UPDATE {
371-
return datastore.ErrChainMetadataNotFound
372-
} else if strings.Contains(errorMsg, "incorrect row version") && (semantics == pb.EditSemantics_SEMANTICS_UPDATE || semantics == pb.EditSemantics_SEMANTICS_UPSERT) {
373-
return datastore.ErrChainMetadataStale
371+
if statusErr := parseResponseStatus(resp.Status); statusErr != nil {
372+
st, err := parseStatusError(statusErr)
373+
if err != nil {
374+
return err
374375
}
375376

376-
return fmt.Errorf("edit request failed: %s", resp.Status.Error)
377+
switch st.Code() { //nolint:exhaustive // We don't need to handle all codes here
378+
case codes.NotFound:
379+
return fmt.Errorf("%w: %s", datastore.ErrChainMetadataNotFound, statusErr.Error())
380+
case codes.Aborted:
381+
return fmt.Errorf("%w: %s", datastore.ErrChainMetadataStale, statusErr.Error())
382+
default:
383+
return fmt.Errorf("edit request failed: %w", statusErr)
384+
}
377385
}
378-
379386
editResp := resp.GetChainMetadataEditResponse()
380387
if editResp == nil {
381388
return errors.New("unexpected response type")

datastore/catalog/remote/contract_metadata_store.go

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import (
66
"errors"
77
"fmt"
88
"io"
9-
"strings"
109
"sync"
1110

11+
"google.golang.org/grpc/codes"
1212
"google.golang.org/protobuf/types/known/wrapperspb"
1313

1414
"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
@@ -149,12 +149,17 @@ func (s *catalogContractMetadataStore) get(ignoreTransaction bool, key datastore
149149
}
150150

151151
// Check for errors in the response
152-
if resp.Status != nil && !resp.Status.Succeeded {
153-
if strings.Contains(resp.Status.GetError(), "No records found") {
154-
return datastore.ContractMetadata{}, datastore.ErrContractMetadataNotFound
152+
if statusErr := parseResponseStatus(resp.Status); statusErr != nil {
153+
st, sterr := parseStatusError(statusErr)
154+
if sterr != nil {
155+
return datastore.ContractMetadata{}, sterr
155156
}
156157

157-
return datastore.ContractMetadata{}, fmt.Errorf("request failed: %s", resp.Status.Error)
158+
if st.Code() == codes.NotFound {
159+
return datastore.ContractMetadata{}, fmt.Errorf("%w: %s", datastore.ErrContractMetadataNotFound, statusErr.Error())
160+
}
161+
162+
return datastore.ContractMetadata{}, fmt.Errorf("get contract metadata failed: %w", statusErr)
158163
}
159164

160165
findResp := resp.GetContractMetadataFindResponse()
@@ -208,8 +213,8 @@ func (s *catalogContractMetadataStore) Fetch(_ context.Context) ([]datastore.Con
208213
}
209214

210215
// Check for errors in the response
211-
if resp.Status != nil && !resp.Status.Succeeded {
212-
return nil, fmt.Errorf("request failed: %s", resp.Status.Error)
216+
if statusErr := parseResponseStatus(resp.Status); statusErr != nil {
217+
return nil, fmt.Errorf("fetch contract metadata failed: %w", statusErr)
213218
}
214219

215220
findResp := resp.GetContractMetadataFindResponse()
@@ -374,17 +379,20 @@ func (s *catalogContractMetadataStore) editRecord(record datastore.ContractMetad
374379
}
375380

376381
// Check for errors in the edit response
377-
if resp.Status != nil && !resp.Status.Succeeded {
378-
errorMsg := resp.Status.GetError()
379-
380-
// Check for specific error conditions
381-
if strings.Contains(errorMsg, "no record found to update for") && semantics == pb.EditSemantics_SEMANTICS_UPDATE {
382-
return datastore.ErrContractMetadataNotFound
383-
} else if strings.Contains(errorMsg, "incorrect row version") && (semantics == pb.EditSemantics_SEMANTICS_UPDATE || semantics == pb.EditSemantics_SEMANTICS_UPSERT) {
384-
return datastore.ErrContractMetadataStale
382+
if statusErr := parseResponseStatus(resp.Status); statusErr != nil {
383+
st, err := parseStatusError(statusErr)
384+
if err != nil {
385+
return err
385386
}
386387

387-
return fmt.Errorf("edit request failed: %s", resp.Status.Error)
388+
switch st.Code() { //nolint:exhaustive // We don't need to handle all codes here
389+
case codes.NotFound:
390+
return fmt.Errorf("%w: %s", datastore.ErrContractMetadataNotFound, statusErr.Error())
391+
case codes.Aborted:
392+
return fmt.Errorf("%w: %s", datastore.ErrContractMetadataStale, statusErr.Error())
393+
default:
394+
return fmt.Errorf("edit request failed: %w", statusErr)
395+
}
388396
}
389397

390398
editResp := resp.GetContractMetadataEditResponse()

datastore/catalog/remote/env_metadata_store.go

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8-
"strings"
98
"sync"
109

10+
"google.golang.org/grpc/codes"
1111
"google.golang.org/protobuf/types/known/wrapperspb"
1212

1313
"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
@@ -141,12 +141,17 @@ func (s *catalogEnvMetadataStore) get(ignoreTransaction bool) (datastore.EnvMeta
141141
}
142142

143143
// Check for errors in the response
144-
if resp.Status != nil && !resp.Status.Succeeded {
145-
if strings.Contains(resp.Status.GetError(), "No records found") {
146-
return datastore.EnvMetadata{}, datastore.ErrEnvMetadataNotSet
144+
if statusErr := parseResponseStatus(resp.Status); statusErr != nil {
145+
st, sterr := parseStatusError(statusErr)
146+
if sterr != nil {
147+
return datastore.EnvMetadata{}, sterr
147148
}
148149

149-
return datastore.EnvMetadata{}, fmt.Errorf("request failed: %s", resp.Status.Error)
150+
if st.Code() == codes.NotFound {
151+
return datastore.EnvMetadata{}, fmt.Errorf("%w: %s", datastore.ErrEnvMetadataNotSet, statusErr.Error())
152+
}
153+
154+
return datastore.EnvMetadata{}, fmt.Errorf("get environment metadata failed: %w", statusErr)
150155
}
151156

152157
findResp := resp.GetEnvironmentMetadataFindResponse()
@@ -247,14 +252,17 @@ func (s *catalogEnvMetadataStore) editRecord(record datastore.EnvMetadata) error
247252
}
248253

249254
// Check for errors in the edit response
250-
if resp.Status != nil && !resp.Status.Succeeded {
251-
errorMsg := resp.Status.GetError()
252-
// Check for version conflicts
253-
if strings.Contains(errorMsg, "incorrect row version") {
254-
return datastore.ErrEnvMetadataStale
255+
if statusErr := parseResponseStatus(resp.Status); statusErr != nil {
256+
st, err := parseStatusError(statusErr)
257+
if err != nil {
258+
return err
259+
}
260+
261+
if st.Code() == codes.Aborted {
262+
return fmt.Errorf("%w: %s", datastore.ErrEnvMetadataStale, statusErr.Error())
255263
}
256264

257-
return fmt.Errorf("edit request failed: %s", resp.Status.Error)
265+
return fmt.Errorf("edit request failed: %w", statusErr)
258266
}
259267

260268
editResp := resp.GetEnvironmentMetadataEditResponse()
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package remote
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
pb "github.com/smartcontractkit/chainlink-protos/op-catalog/v1/datastore"
8+
rpb "google.golang.org/genproto/googleapis/rpc/status"
9+
"google.golang.org/grpc/codes"
10+
"google.golang.org/grpc/status"
11+
)
12+
13+
// parseResponseStatus converts a ResponseStatus to a standard gRPC error.
14+
// Returns nil if the status indicates success (code == 0).
15+
// Preserves error details for rich error information.
16+
//
17+
// Usage:
18+
//
19+
// if err := parseResponseStatus(resp.Status); err != nil {
20+
// st, _ := status.FromError(err)
21+
// log.Printf("Error: %v (code: %v)", st.Message(), st.Code())
22+
// }
23+
func parseResponseStatus(rs *pb.ResponseStatus) error {
24+
if rs == nil {
25+
return status.Error(codes.Internal, "missing response status")
26+
}
27+
28+
// Success case
29+
if rs.Code == 0 {
30+
return nil
31+
}
32+
33+
// Convert to google.rpc.Status, then to gRPC status
34+
st := status.FromProto(&rpb.Status{
35+
Code: rs.Code,
36+
Message: rs.Message,
37+
Details: rs.Details,
38+
})
39+
40+
return st.Err()
41+
}
42+
43+
// parseStatusError converts a gRPC error to a standard gRPC status.
44+
func parseStatusError(err error) (*status.Status, error) {
45+
if err == nil {
46+
return nil, errors.New("nil error provided")
47+
}
48+
49+
st, ok := status.FromError(err)
50+
if !ok {
51+
return nil, fmt.Errorf("failed to parse error: %s", err.Error())
52+
}
53+
54+
return st, nil
55+
}

0 commit comments

Comments
 (0)