Skip to content

Commit d0d356d

Browse files
author
Luca Bianconi
committed
feat: specialize init errors
1 parent 53a6f25 commit d0d356d

File tree

8 files changed

+1158
-761
lines changed

8 files changed

+1158
-761
lines changed

Diff for: arduino/error_details.go

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to [email protected].
15+
16+
package arduino
17+
18+
import (
19+
"google.golang.org/genproto/googleapis/rpc/errdetails"
20+
"google.golang.org/grpc/codes"
21+
"google.golang.org/grpc/status"
22+
)
23+
24+
const errorInfoDomain = "arduino.cc"
25+
26+
type ErrorDetailReason string
27+
28+
const (
29+
ErrorReasonUnspecified ErrorDetailReason = "UNSPECIFIED"
30+
ErrorReasonInitializationFailure ErrorDetailReason = "INITIALIZATION_FAILURE"
31+
)
32+
33+
type errorInfo struct {
34+
*errdetails.ErrorInfo
35+
}
36+
37+
func NewErrorInfo(reason ErrorDetailReason) *errorInfo {
38+
return NewErrorInfoWithMetadata(reason, nil)
39+
}
40+
41+
func NewErrorInfoWithMetadata(reason ErrorDetailReason, initMetadata map[string]string) *errorInfo {
42+
return &errorInfo{
43+
ErrorInfo: &errdetails.ErrorInfo{
44+
Domain: errorInfoDomain,
45+
Reason: string(reason),
46+
Metadata: initMetadata,
47+
},
48+
}
49+
}
50+
51+
// AddMetadata create a new errorInfo appending additional metadata
52+
// if keys overlap old ones are replaced
53+
func (ei *errorInfo) WithAdditionalMetadata(metadata map[string]string) *errorInfo {
54+
clone := ei.Clone()
55+
if clone.Metadata == nil {
56+
clone.Metadata = map[string]string{}
57+
}
58+
for k, v := range metadata {
59+
clone.Metadata[k] = v
60+
}
61+
return clone
62+
}
63+
64+
func (ei *errorInfo) Clone() *errorInfo {
65+
metadata := ei.Metadata
66+
var clonedMetadata map[string]string = nil
67+
if metadata != nil {
68+
clonedMetadata = map[string]string{}
69+
for k, v := range metadata {
70+
clonedMetadata[k] = v
71+
}
72+
}
73+
74+
clonedInfo := *(ei.ErrorInfo)
75+
76+
clone := errorInfo{
77+
ErrorInfo: &clonedInfo,
78+
}
79+
80+
return &clone
81+
}
82+
83+
var (
84+
ErrorInfoUnspecified = NewErrorInfo(ErrorReasonUnspecified)
85+
ErrorInfoInitializationFailure = NewErrorInfo(ErrorReasonInitializationFailure)
86+
)
87+
88+
type AugError struct{}
89+
90+
func (e *AugError) Error() string {
91+
return "err msg"
92+
}
93+
94+
func (e *AugError) ToRPCStatus() *status.Status {
95+
s, _ := status.New(codes.Aborted, "yes yes").WithDetails(ErrorInfoUnspecified)
96+
return s
97+
}

Diff for: arduino/error_details_test.go

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to [email protected].
15+
16+
package arduino
17+
18+
import (
19+
"encoding/json"
20+
"testing"
21+
22+
"github.com/stretchr/testify/require"
23+
"google.golang.org/grpc/codes"
24+
"google.golang.org/grpc/status"
25+
)
26+
27+
func TestJsonMarshaling(t *testing.T) {
28+
{
29+
i := NewErrorInfo(ErrorReasonUnspecified)
30+
encoded, err := json.Marshal(i)
31+
require.NoError(t, err)
32+
expected := `{"domain":"arduino.cc", "reason":"UNSPECIFIED"}`
33+
require.JSONEq(t, expected, string(encoded))
34+
}
35+
36+
{
37+
i := NewErrorInfoWithMetadata(ErrorReasonInitializationFailure, map[string]string{
38+
"component": "instance",
39+
"action": "load package",
40+
})
41+
encoded, err := json.Marshal(i)
42+
require.NoError(t, err)
43+
expected := `{"domain":"arduino.cc", "reason":"INITIALIZATION_FAILURE", "metadata":{"action":"load package","component":"instance"}}`
44+
require.JSONEq(t, expected, string(encoded))
45+
}
46+
}
47+
48+
func TestStatusJsonMarshaling(t *testing.T) {
49+
50+
grpcStatus := status.Newf(codes.FailedPrecondition, "something went wrong")
51+
detail := ErrorInfoInitializationFailure.WithAdditionalMetadata(map[string]string{
52+
"component": "instance",
53+
"action": "load_package",
54+
})
55+
grpcStatus, _ = grpcStatus.WithDetails(detail)
56+
encodedDetail, err := json.Marshal(detail)
57+
require.NoError(t, err)
58+
encodedStatus, err := json.Marshal(grpcStatus.Proto())
59+
require.NoError(t, err)
60+
61+
require.JSONEq(t, `{
62+
"reason": "INITIALIZATION_FAILURE",
63+
"domain": "arduino.cc",
64+
"metadata": { "action": "load_package", "component": "instance" }
65+
}`, string(encodedDetail))
66+
67+
require.JSONEq(t, `{
68+
"code": 9,
69+
"message": "something went wrong",
70+
"details": [
71+
{
72+
"type_url": "type.googleapis.com/google.rpc.ErrorInfo",
73+
"value": "ChZJTklUSUFMSVpBVElPTl9GQUlMVVJFEgphcmR1aW5vLmNjGhUKCWNvbXBvbmVudBIIaW5zdGFuY2UaFgoGYWN0aW9uEgxsb2FkX3BhY2thZ2U="
74+
}
75+
]
76+
}`, string(encodedStatus))
77+
}
78+
79+
func TestAdditionalMetadata(t *testing.T) {
80+
{
81+
// can add to nil initial metadata
82+
i := NewErrorInfo(ErrorReasonUnspecified)
83+
stillI := i.WithAdditionalMetadata(map[string]string{
84+
"a": "1",
85+
"b": "2",
86+
})
87+
require.Equal(t, i, stillI)
88+
require.Equal(t, map[string]string{
89+
"a": "1",
90+
"b": "2",
91+
}, stillI.Metadata)
92+
}
93+
{
94+
initialMetadata := map[string]string{
95+
"a": "1",
96+
"b": "2",
97+
}
98+
i := NewErrorInfoWithMetadata(ErrorReasonUnspecified, initialMetadata)
99+
stillI := i.WithAdditionalMetadata(map[string]string{
100+
"b": "3",
101+
"c": "4",
102+
}).WithAdditionalMetadata(map[string]string{
103+
"d": "5",
104+
})
105+
require.Equal(t, i, stillI)
106+
require.Equal(t, map[string]string{
107+
"a": "1",
108+
"b": "3",
109+
"c": "4",
110+
"d": "5",
111+
}, stillI.Metadata)
112+
}
113+
}

Diff for: arduino/errors.go

+33-1
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,36 @@ func (e *ProgrammerRequiredForUploadError) ToRPCStatus() *status.Status {
308308
return st
309309
}
310310

311+
type FailedInstanceInitReason string
312+
313+
const (
314+
Unspecified FailedInstanceInitReason = "UNSPECIFIED"
315+
InvalidIndexURL FailedInstanceInitReason = "INVALID_INDEX_URL"
316+
ErrorIndexLoad FailedInstanceInitReason = "INDEX_LOAD_ERROR"
317+
ErrorToolLoad FailedInstanceInitReason = "TOOL_LOAD_ERROR"
318+
)
319+
320+
type ErrorInitFailed struct {
321+
Code codes.Code
322+
Cause error
323+
Reason FailedInstanceInitReason
324+
}
325+
326+
func (ife *ErrorInitFailed) Error() string {
327+
return ife.Cause.Error()
328+
}
329+
330+
// ToRPCStatus converts the error into a *status.Status
331+
func (ife *ErrorInitFailed) ToRPCStatus() *status.Status {
332+
st, _ := status.
333+
New(ife.Code, ife.Cause.Error()).
334+
WithDetails(&rpc.FailedInstanceInitError{
335+
Reason: string(ife.Reason),
336+
Message: ife.Cause.Error(),
337+
})
338+
return st
339+
}
340+
311341
// ProgrammerNotFoundError is returned when the programmer is not found
312342
type ProgrammerNotFoundError struct {
313343
Programmer string
@@ -405,7 +435,9 @@ func (e *PlatformLoadingError) Error() string {
405435

406436
// ToRPCStatus converts the error into a *status.Status
407437
func (e *PlatformLoadingError) ToRPCStatus() *status.Status {
408-
return status.New(codes.FailedPrecondition, e.Error())
438+
s, _ := status.New(codes.FailedPrecondition, e.Error()).
439+
WithDetails(&rpc.PlatformLoadingError{})
440+
return s
409441
}
410442

411443
func (e *PlatformLoadingError) Unwrap() error {

Diff for: commands/instances.go

+30-10
Original file line numberDiff line numberDiff line change
@@ -303,23 +303,35 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
303303
for _, u := range urls {
304304
URL, err := utils.URLParse(u)
305305
if err != nil {
306-
s := status.Newf(codes.InvalidArgument, tr("Invalid additional URL: %v"), err)
307-
responseError(s)
306+
e := &arduino.ErrorInitFailed{
307+
Code: codes.InvalidArgument,
308+
Cause: fmt.Errorf(tr("Invalid additional URL: %v", err)),
309+
Reason: arduino.InvalidIndexURL,
310+
}
311+
responseError(e.ToRPCStatus())
308312
continue
309313
}
310314

311315
if URL.Scheme == "file" {
312316
_, err := pmb.LoadPackageIndexFromFile(paths.New(URL.Path))
313317
if err != nil {
314-
s := status.Newf(codes.FailedPrecondition, tr("Loading index file: %v"), err)
315-
responseError(s)
318+
e := &arduino.ErrorInitFailed{
319+
Code: codes.FailedPrecondition,
320+
Cause: fmt.Errorf(tr("Loading index file: %v", err)),
321+
Reason: arduino.ErrorIndexLoad,
322+
}
323+
responseError(e.ToRPCStatus())
316324
}
317325
continue
318326
}
319327

320328
if err := pmb.LoadPackageIndex(URL); err != nil {
321-
s := status.Newf(codes.FailedPrecondition, tr("Loading index file: %v"), err)
322-
responseError(s)
329+
e := &arduino.ErrorInitFailed{
330+
Code: codes.FailedPrecondition,
331+
Cause: fmt.Errorf(tr("Loading index file: %v", err)),
332+
Reason: arduino.ErrorIndexLoad,
333+
}
334+
responseError(e.ToRPCStatus())
323335
}
324336
}
325337

@@ -331,8 +343,12 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
331343
for name, tool := range pmb.GetOrCreatePackage("builtin").Tools {
332344
latest := tool.LatestRelease()
333345
if latest == nil {
334-
s := status.Newf(codes.Internal, tr("can't find latest release of tool %s", name))
335-
responseError(s)
346+
e := &arduino.ErrorInitFailed{
347+
Code: codes.Internal,
348+
Cause: fmt.Errorf(tr("can't find latest release of tool %s", name)),
349+
Reason: arduino.ErrorToolLoad,
350+
}
351+
responseError(e.ToRPCStatus())
336352
} else if !latest.IsInstalled() {
337353
builtinToolsToInstall = append(builtinToolsToInstall, latest)
338354
}
@@ -342,8 +358,12 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
342358
if len(builtinToolsToInstall) > 0 {
343359
for _, toolRelease := range builtinToolsToInstall {
344360
if err := installTool(pmb.Build(), toolRelease, downloadCallback, taskCallback); err != nil {
345-
s := status.Newf(codes.Internal, err.Error())
346-
responseError(s)
361+
e := &arduino.ErrorInitFailed{
362+
Code: codes.Internal,
363+
Cause: err,
364+
Reason: arduino.ErrorToolLoad,
365+
}
366+
responseError(e.ToRPCStatus())
347367
}
348368
}
349369

0 commit comments

Comments
 (0)