Skip to content

Commit 75b9760

Browse files
cmaglieper1234silvanocerza
authored
[skip-changleog] Better gRPC error handling (#1251)
* Proper gRPC error handling * Update gRPC API of board command to return status.Status instead of error * Update gRPC API of remaining commands to return status.Status instead of error * Replace custom error with protobuf message Previously, a custom error was returned when attempting to upgrade a platform that was already at the latest available version. There is dedicated code for handling this specific error. Now that the function has been changed to return a status.Status instead of error, the previous check for the return being this error is no longer possible. The capability is restored by replacing the error with a protocol buffer message. * Handle details of any type in `core.PlatformUpgrade()` status The status details of the function are used to identify the specific cause of a non-nil status. This is done via a type assertion. Previously, this type assertion was configured such that a details of any type other than the expected would result in a panic. At the moment, that will not occur because we only add details of one type. However, the whole point of the type assertion is to support details of multiple types, and if other types are added a panic will not be the appropriate behavior. A better approach is to check the result of the type assertion, handling the non-nil status as a generic error if its details are of a different type. * Return nil on program action if an error occurred * Refactoring 'upload' commands * Refactoring 'board' commands * Refactoring 'compile' commands * Refactoring 'core' commands * Refactoring 'debug' commands * Refactoring 'lib' commands * Refactoring 'sketch' commands * Refactoring 'commands' commands * updated tests and fixed some error wording * fixed go lint warnings * Apply suggestions from code review Co-authored-by: per1234 <[email protected]> * Apply changes from code review Co-authored-by: Silvano Cerza <[email protected]> * fix i18n Co-authored-by: per1234 <[email protected]> Co-authored-by: Silvano Cerza <[email protected]>
1 parent b8d30dc commit 75b9760

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+2074
-1418
lines changed

Diff for: arduino/cores/fqbn.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func ParseFQBN(fqbnIn string) (*FQBN, error) {
3535
// Split fqbn
3636
fqbnParts := strings.Split(fqbnIn, ":")
3737
if len(fqbnParts) < 3 || len(fqbnParts) > 4 {
38-
return nil, fmt.Errorf("invalid fqbn: %s", fqbnIn)
38+
return nil, fmt.Errorf("not an FQBN: %s", fqbnIn)
3939
}
4040

4141
fqbn := &FQBN{
@@ -45,18 +45,18 @@ func ParseFQBN(fqbnIn string) (*FQBN, error) {
4545
Configs: properties.NewMap(),
4646
}
4747
if fqbn.BoardID == "" {
48-
return nil, fmt.Errorf(tr("invalid fqbn: empty board identifier"))
48+
return nil, fmt.Errorf(tr("empty board identifier"))
4949
}
5050
if len(fqbnParts) > 3 {
5151
for _, pair := range strings.Split(fqbnParts[3], ",") {
5252
parts := strings.SplitN(pair, "=", 2)
5353
if len(parts) != 2 {
54-
return nil, fmt.Errorf(tr("invalid fqbn config: %s"), pair)
54+
return nil, fmt.Errorf(tr("invalid config option: %s"), pair)
5555
}
5656
k := strings.TrimSpace(parts[0])
5757
v := strings.TrimSpace(parts[1])
5858
if k == "" {
59-
return nil, fmt.Errorf(tr("invalid fqbn config: %s"), pair)
59+
return nil, fmt.Errorf(tr("invalid config option: %s"), pair)
6060
}
6161
fqbn.Configs.Set(k, v)
6262
}

Diff for: arduino/libraries/libraries.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func (library *Library) ToRPCLibrary() (*rpc.Library, error) {
111111
var err error
112112
headers, err = library.SourceHeaders()
113113
if err != nil {
114-
return nil, fmt.Errorf(tr("gathering library headers: %w"), err)
114+
return nil, fmt.Errorf(tr("reading library headers: %w"), err)
115115
}
116116
}
117117

Diff for: cli/compile/compile.go

+20-18
Original file line numberDiff line numberDiff line change
@@ -178,20 +178,20 @@ func run(cmd *cobra.Command, args []string) {
178178
SourceOverride: overrides,
179179
Library: library,
180180
}
181-
compileOut := new(bytes.Buffer)
182-
compileErr := new(bytes.Buffer)
181+
compileStdOut := new(bytes.Buffer)
182+
compileStdErr := new(bytes.Buffer)
183183
verboseCompile := configuration.Settings.GetString("logging.level") == "debug"
184184
var compileRes *rpc.CompileResponse
185-
var err error
185+
var compileError error
186186
if output.OutputFormat == "json" {
187-
compileRes, err = compile.Compile(context.Background(), compileRequest, compileOut, compileErr, verboseCompile)
187+
compileRes, compileError = compile.Compile(context.Background(), compileRequest, compileStdOut, compileStdErr, verboseCompile)
188188
} else {
189-
compileRes, err = compile.Compile(context.Background(), compileRequest, os.Stdout, os.Stderr, verboseCompile)
189+
compileRes, compileError = compile.Compile(context.Background(), compileRequest, os.Stdout, os.Stderr, verboseCompile)
190190
}
191191

192-
if err == nil && uploadAfterCompile {
192+
if compileError == nil && uploadAfterCompile {
193193
var sk *sketch.Sketch
194-
sk, err = sketch.New(sketchPath)
194+
sk, err := sketch.New(sketchPath)
195195
if err != nil {
196196
feedback.Errorf(tr("Error during Upload: %v"), err)
197197
os.Exit(errorcodes.ErrGeneric)
@@ -230,28 +230,30 @@ func run(cmd *cobra.Command, args []string) {
230230
Programmer: programmer,
231231
UserFields: fields,
232232
}
233+
234+
var uploadError error
233235
if output.OutputFormat == "json" {
234236
// TODO: do not print upload output in json mode
235-
uploadOut := new(bytes.Buffer)
236-
uploadErr := new(bytes.Buffer)
237-
_, err = upload.Upload(context.Background(), uploadRequest, uploadOut, uploadErr)
237+
uploadStdOut := new(bytes.Buffer)
238+
uploadStdErr := new(bytes.Buffer)
239+
_, uploadError = upload.Upload(context.Background(), uploadRequest, uploadStdOut, uploadStdErr)
238240
} else {
239-
_, err = upload.Upload(context.Background(), uploadRequest, os.Stdout, os.Stderr)
241+
_, uploadError = upload.Upload(context.Background(), uploadRequest, os.Stdout, os.Stderr)
240242
}
241-
if err != nil {
242-
feedback.Errorf(tr("Error during Upload: %v"), err)
243+
if uploadError != nil {
244+
feedback.Errorf(tr("Error during Upload: %v"), uploadError)
243245
os.Exit(errorcodes.ErrGeneric)
244246
}
245247
}
246248

247249
feedback.PrintResult(&compileResult{
248-
CompileOut: compileOut.String(),
249-
CompileErr: compileErr.String(),
250+
CompileOut: compileStdOut.String(),
251+
CompileErr: compileStdErr.String(),
250252
BuilderResult: compileRes,
251-
Success: err == nil,
253+
Success: compileError == nil,
252254
})
253-
if err != nil && output.OutputFormat != "json" {
254-
feedback.Errorf(tr("Error during build: %v"), err)
255+
if compileError != nil && output.OutputFormat != "json" {
256+
feedback.Errorf(tr("Error during build: %v"), compileError)
255257
os.Exit(errorcodes.ErrGeneric)
256258
}
257259
}

Diff for: cli/core/upgrade.go

+9-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package core
1717

1818
import (
1919
"context"
20+
"errors"
2021
"fmt"
2122
"os"
2223

@@ -25,6 +26,7 @@ import (
2526
"github.com/arduino/arduino-cli/cli/feedback"
2627
"github.com/arduino/arduino-cli/cli/instance"
2728
"github.com/arduino/arduino-cli/cli/output"
29+
"github.com/arduino/arduino-cli/commands"
2830
"github.com/arduino/arduino-cli/commands/core"
2931
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
3032
"github.com/sirupsen/logrus"
@@ -94,11 +96,13 @@ func runUpgradeCommand(cmd *cobra.Command, args []string) {
9496
SkipPostInstall: DetectSkipPostInstallValue(),
9597
}
9698

97-
_, err := core.PlatformUpgrade(context.Background(), r, output.ProgressBar(), output.TaskProgress())
98-
if err == core.ErrAlreadyLatest {
99-
feedback.Printf(tr("Platform %s is already at the latest version"), platformRef)
100-
} else if err != nil {
101-
feedback.Errorf(tr("Error during upgrade: %v"), err)
99+
if _, err := core.PlatformUpgrade(context.Background(), r, output.ProgressBar(), output.TaskProgress()); err != nil {
100+
if errors.Is(err, &commands.PlatformAlreadyAtTheLatestVersionError{}) {
101+
feedback.Print(err.Error())
102+
continue
103+
}
104+
105+
feedback.Errorf(tr("Error during upgrade: %v", err))
102106
os.Exit(errorcodes.ErrGeneric)
103107
}
104108
}

Diff for: cli/debug/debug.go

+1-6
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import (
3434
"github.com/arduino/go-properties-orderedmap"
3535
"github.com/fatih/color"
3636
"github.com/spf13/cobra"
37-
"google.golang.org/grpc/status"
3837
)
3938

4039
var (
@@ -101,12 +100,8 @@ func run(command *cobra.Command, args []string) {
101100
if printInfo {
102101

103102
if res, err := debug.GetDebugConfig(context.Background(), debugConfigRequested); err != nil {
104-
if status, ok := status.FromError(err); ok {
105-
feedback.Errorf(tr("Error getting Debug info: %v"), status.Message())
106-
errorcodes.ExitWithGrpcStatus(status)
107-
}
108103
feedback.Errorf(tr("Error getting Debug info: %v"), err)
109-
os.Exit(errorcodes.ErrGeneric)
104+
os.Exit(errorcodes.ErrBadArgument)
110105
} else {
111106
feedback.PrintResult(&debugInfoResult{res})
112107
}

Diff for: cli/errorcodes/errorcodes.go

-18
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,6 @@
1515

1616
package errorcodes
1717

18-
import (
19-
"os"
20-
21-
"google.golang.org/grpc/codes"
22-
"google.golang.org/grpc/status"
23-
)
24-
2518
// Error codes to be used for os.Exit().
2619
const (
2720
_ = iota // 0 is not a valid exit error code
@@ -36,14 +29,3 @@ const (
3629
ErrCoreConfig
3730
ErrBadArgument
3831
)
39-
40-
// ExitWithGrpcStatus will terminate the current process by returing the correct
41-
// error code closest matching the gRPC status.
42-
func ExitWithGrpcStatus(s *status.Status) {
43-
switch s.Code() {
44-
case codes.Unimplemented:
45-
os.Exit(ErrBadArgument)
46-
default:
47-
os.Exit(ErrGeneric)
48-
}
49-
}

Diff for: cli/instance/instance.go

+10-11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package instance
1717

1818
import (
1919
"context"
20+
"errors"
2021
"os"
2122

2223
"github.com/arduino/arduino-cli/cli/errorcodes"
@@ -27,8 +28,6 @@ import (
2728
"github.com/arduino/arduino-cli/i18n"
2829
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
2930
"github.com/arduino/go-paths-helper"
30-
"google.golang.org/grpc/codes"
31-
"google.golang.org/grpc/status"
3231
)
3332

3433
var tr = i18n.Tr
@@ -50,7 +49,7 @@ func CreateAndInit() *rpc.Instance {
5049
}
5150

5251
// Create and return a new Instance.
53-
func Create() (*rpc.Instance, *status.Status) {
52+
func Create() (*rpc.Instance, error) {
5453
res, err := commands.Create(&rpc.CreateRequest{})
5554
if err != nil {
5655
return nil, err
@@ -59,12 +58,12 @@ func Create() (*rpc.Instance, *status.Status) {
5958
}
6059

6160
// Init initializes instance by loading installed libraries and platforms.
62-
// In case of loading failures return a list gRPC Status errors for each
61+
// In case of loading failures return a list of errors for each
6362
// platform or library that we failed to load.
6463
// Package and library indexes files are automatically updated if the
6564
// CLI is run for the first time.
66-
func Init(instance *rpc.Instance) []*status.Status {
67-
errs := []*status.Status{}
65+
func Init(instance *rpc.Instance) []error {
66+
errs := []error{}
6867

6968
// In case the CLI is executed for the first time
7069
if err := FirstUpdate(instance); err != nil {
@@ -77,8 +76,8 @@ func Init(instance *rpc.Instance) []*status.Status {
7776
err := commands.Init(&rpc.InitRequest{
7877
Instance: instance,
7978
}, func(res *rpc.InitResponse) {
80-
if err := res.GetError(); err != nil {
81-
errs = append(errs, status.FromProto(err))
79+
if st := res.GetError(); st != nil {
80+
errs = append(errs, errors.New(st.Message))
8281
}
8382

8483
if progress := res.GetInitProgress(); progress != nil {
@@ -99,7 +98,7 @@ func Init(instance *rpc.Instance) []*status.Status {
9998

10099
// FirstUpdate downloads libraries and packages indexes if they don't exist.
101100
// This ideally is only executed the first time the CLI is run.
102-
func FirstUpdate(instance *rpc.Instance) *status.Status {
101+
func FirstUpdate(instance *rpc.Instance) error {
103102
// Gets the data directory to verify if library_index.json and package_index.json exist
104103
dataDir := paths.New(configuration.Settings.GetString("directories.data"))
105104

@@ -120,7 +119,7 @@ func FirstUpdate(instance *rpc.Instance) *status.Status {
120119
output.ProgressBar(),
121120
)
122121
if err != nil {
123-
return status.Newf(codes.FailedPrecondition, err.Error())
122+
return err
124123
}
125124
}
126125

@@ -135,7 +134,7 @@ func FirstUpdate(instance *rpc.Instance) *status.Status {
135134
output.ProgressBar(),
136135
)
137136
if err != nil {
138-
return status.Newf(codes.FailedPrecondition, err.Error())
137+
return err
139138
}
140139
}
141140

Diff for: cli/lib/args.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func ParseLibraryReferenceArgs(args []string) ([]*LibraryReferenceArg, error) {
7575
// ParseLibraryReferenceArgAndAdjustCase parse a command line argument that reference a
7676
// library and possibly adjust the case of the name to match a library in the index
7777
func ParseLibraryReferenceArgAndAdjustCase(instance *rpc.Instance, arg string) (*LibraryReferenceArg, error) {
78-
libRef, err := ParseLibraryReferenceArg(arg)
78+
libRef, _ := ParseLibraryReferenceArg(arg)
7979
res, err := lib.LibrarySearch(context.Background(), &rpc.LibrarySearchRequest{
8080
Instance: instance,
8181
Query: libRef.Name,

Diff for: cli/lib/check_deps.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func runDepsCommand(cmd *cobra.Command, args []string) {
5757
Version: libRef.Version,
5858
})
5959
if err != nil {
60-
feedback.Errorf(tr("Error resolving dependencies for %[1]s: %[2]s"), libRef, err)
60+
feedback.Errorf(tr("Error resolving dependencies for %[1]s: %[2]s", libRef, err))
6161
}
6262

6363
feedback.PrintResult(&checkDepResult{deps: deps})

Diff for: cli/lib/search.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,11 @@ func runSearchCommand(cmd *cobra.Command, args []string) {
5858
os.Exit(errorcodes.ErrGeneric)
5959
}
6060

61-
err := commands.UpdateLibrariesIndex(context.Background(), &rpc.UpdateLibrariesIndexRequest{
62-
Instance: inst,
63-
}, output.ProgressBar())
64-
if err != nil {
61+
if err := commands.UpdateLibrariesIndex(
62+
context.Background(),
63+
&rpc.UpdateLibrariesIndexRequest{Instance: inst},
64+
output.ProgressBar(),
65+
); err != nil {
6566
feedback.Errorf(tr("Error updating library index: %v"), err)
6667
os.Exit(errorcodes.ErrGeneric)
6768
}

Diff for: commands/board/attach.go

+6-7
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ package board
1717

1818
import (
1919
"context"
20-
"errors"
2120
"fmt"
2221
"net/url"
2322
"strings"
@@ -39,15 +38,15 @@ var tr = i18n.Tr
3938
func Attach(ctx context.Context, req *rpc.BoardAttachRequest, taskCB commands.TaskProgressCB) (*rpc.BoardAttachResponse, error) {
4039
pm := commands.GetPackageManager(req.GetInstance().GetId())
4140
if pm == nil {
42-
return nil, errors.New(tr("invalid instance"))
41+
return nil, &commands.InvalidInstanceError{}
4342
}
4443
var sketchPath *paths.Path
4544
if req.GetSketchPath() != "" {
4645
sketchPath = paths.New(req.GetSketchPath())
4746
}
4847
sk, err := sketch.New(sketchPath)
4948
if err != nil {
50-
return nil, fmt.Errorf(tr("opening sketch: %s"), err)
49+
return nil, &commands.CantOpenSketchError{Cause: err}
5150
}
5251

5352
boardURI := req.GetBoardUri()
@@ -63,7 +62,7 @@ func Attach(ctx context.Context, req *rpc.BoardAttachRequest, taskCB commands.Ta
6362
} else {
6463
deviceURI, err := url.Parse(boardURI)
6564
if err != nil {
66-
return nil, fmt.Errorf(tr("invalid Device URL format: %s"), err)
65+
return nil, &commands.InvalidArgumentError{Message: tr("Invalid Device URL format"), Cause: err}
6766
}
6867

6968
var findBoardFunc func(*packagemanager.PackageManager, *discovery.Monitor, *url.URL) *cores.Board
@@ -73,7 +72,7 @@ func Attach(ctx context.Context, req *rpc.BoardAttachRequest, taskCB commands.Ta
7372
case "http", "https", "tcp", "udp":
7473
findBoardFunc = findNetworkConnectedBoard
7574
default:
76-
return nil, fmt.Errorf(tr("invalid device port type provided"))
75+
return nil, &commands.InvalidArgumentError{Message: tr("Invalid device port type provided")}
7776
}
7877

7978
duration, err := time.ParseDuration(req.GetSearchTimeout())
@@ -89,7 +88,7 @@ func Attach(ctx context.Context, req *rpc.BoardAttachRequest, taskCB commands.Ta
8988
// TODO: Handle the case when no board is found.
9089
board := findBoardFunc(pm, monitor, deviceURI)
9190
if board == nil {
92-
return nil, fmt.Errorf(tr("no supported board found at %s"), deviceURI.String())
91+
return nil, &commands.InvalidArgumentError{Message: tr("No supported board found at %s", deviceURI)}
9392
}
9493
taskCB(&rpc.TaskProgress{Name: fmt.Sprintf(tr("Board found: %s"), board.Name())})
9594

@@ -104,7 +103,7 @@ func Attach(ctx context.Context, req *rpc.BoardAttachRequest, taskCB commands.Ta
104103

105104
err = sk.ExportMetadata()
106105
if err != nil {
107-
return nil, fmt.Errorf(tr("cannot export sketch metadata: %s"), err)
106+
return nil, &commands.PermissionDeniedError{Message: tr("Cannot export sketch metadata"), Cause: err}
108107
}
109108
taskCB(&rpc.TaskProgress{Name: fmt.Sprintf(tr("Selected fqbn: %s"), sk.Metadata.CPU.Fqbn), Completed: true})
110109
return &rpc.BoardAttachResponse{}, nil

0 commit comments

Comments
 (0)