Skip to content

Commit f6967e7

Browse files
committed
Proper gRPC error handling
1 parent e4c54ec commit f6967e7

File tree

8 files changed

+201
-141
lines changed

8 files changed

+201
-141
lines changed

cli/compile/compile.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/arduino/arduino-cli/cli/output"
2828
"github.com/arduino/arduino-cli/configuration"
2929
"github.com/arduino/arduino-cli/i18n"
30+
"google.golang.org/grpc/status"
3031

3132
"github.com/arduino/arduino-cli/cli/errorcodes"
3233
"github.com/arduino/arduino-cli/cli/instance"
@@ -200,7 +201,7 @@ func run(cmd *cobra.Command, args []string) {
200201
ImportDir: buildPath,
201202
Programmer: programmer,
202203
}
203-
var err error
204+
var err *status.Status
204205
if output.OutputFormat == "json" {
205206
// TODO: do not print upload output in json mode
206207
uploadOut := new(bytes.Buffer)

client_example/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
dbg "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/debug/v1"
3333
"github.com/arduino/arduino-cli/rpc/cc/arduino/cli/settings/v1"
3434
"google.golang.org/grpc"
35+
"google.golang.org/grpc/status"
3536
)
3637

3738
var (

commands/daemon/daemon.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ func (s *ArduinoCoreServerImpl) Upload(req *rpc.UploadRequest, stream rpc.Arduin
317317
utils.FeedStreamTo(func(data []byte) { stream.Send(&rpc.UploadResponse{ErrStream: data}) }),
318318
)
319319
if err != nil {
320-
return err
320+
return err.Err()
321321
}
322322
return stream.Send(resp)
323323
}
@@ -330,7 +330,7 @@ func (s *ArduinoCoreServerImpl) UploadUsingProgrammer(req *rpc.UploadUsingProgra
330330
utils.FeedStreamTo(func(data []byte) { stream.Send(&rpc.UploadUsingProgrammerResponse{ErrStream: data}) }),
331331
)
332332
if err != nil {
333-
return err
333+
return err.Err()
334334
}
335335
return stream.Send(resp)
336336
}
@@ -343,7 +343,7 @@ func (s *ArduinoCoreServerImpl) BurnBootloader(req *rpc.BurnBootloaderRequest, s
343343
utils.FeedStreamTo(func(data []byte) { stream.Send(&rpc.BurnBootloaderResponse{ErrStream: data}) }),
344344
)
345345
if err != nil {
346-
return err
346+
return err.Err()
347347
}
348348
return stream.Send(resp)
349349
}

commands/upload/burnbootloader.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ import (
2222
"github.com/arduino/arduino-cli/commands"
2323
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
2424
"github.com/sirupsen/logrus"
25+
"google.golang.org/grpc/status"
2526
)
2627

2728
// BurnBootloader FIXMEDOC
28-
func BurnBootloader(ctx context.Context, req *rpc.BurnBootloaderRequest, outStream io.Writer, errStream io.Writer) (*rpc.BurnBootloaderResponse, error) {
29+
func BurnBootloader(ctx context.Context, req *rpc.BurnBootloaderRequest, outStream io.Writer, errStream io.Writer) (*rpc.BurnBootloaderResponse, *status.Status) {
2930
logrus.
3031
WithField("fqbn", req.GetFqbn()).
3132
WithField("port", req.GetPort()).

commands/upload/upload.go

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,27 @@ import (
3636
properties "github.com/arduino/go-properties-orderedmap"
3737
"github.com/pkg/errors"
3838
"github.com/sirupsen/logrus"
39+
"google.golang.org/grpc/codes"
40+
"google.golang.org/grpc/status"
3941
)
4042

4143
var tr = i18n.Tr
4244

4345
// Upload FIXMEDOC
44-
func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, errStream io.Writer) (*rpc.UploadResponse, error) {
46+
func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, errStream io.Writer) (*rpc.UploadResponse, *status.Status) {
4547
logrus.Tracef("Upload %s on %s started", req.GetSketchPath(), req.GetFqbn())
4648

4749
// TODO: make a generic function to extract sketch from request
4850
// and remove duplication in commands/compile.go
4951
sketchPath := paths.New(req.GetSketchPath())
5052
sk, err := sketch.New(sketchPath)
5153
if err != nil && req.GetImportDir() == "" && req.GetImportFile() == "" {
52-
return nil, fmt.Errorf(tr("opening sketch: %s"), err)
54+
return nil, status.Newf(codes.InvalidArgument, tr("Sketch not found: %s"), err)
5355
}
5456

5557
pm := commands.GetPackageManager(req.GetInstance().GetId())
5658

57-
err = runProgramAction(
59+
return &rpc.UploadResponse{}, runProgramAction(
5860
pm,
5961
sk,
6062
req.GetImportFile(),
@@ -69,18 +71,14 @@ func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, er
6971
errStream,
7072
req.GetDryRun(),
7173
)
72-
if err != nil {
73-
return nil, err
74-
}
75-
return &rpc.UploadResponse{}, nil
7674
}
7775

7876
// UsingProgrammer FIXMEDOC
79-
func UsingProgrammer(ctx context.Context, req *rpc.UploadUsingProgrammerRequest, outStream io.Writer, errStream io.Writer) (*rpc.UploadUsingProgrammerResponse, error) {
77+
func UsingProgrammer(ctx context.Context, req *rpc.UploadUsingProgrammerRequest, outStream io.Writer, errStream io.Writer) (*rpc.UploadUsingProgrammerResponse, *status.Status) {
8078
logrus.Tracef("Upload using programmer %s on %s started", req.GetSketchPath(), req.GetFqbn())
8179

8280
if req.GetProgrammer() == "" {
83-
return nil, errors.New(tr("programmer not specified"))
81+
return nil, status.New(codes.InvalidArgument, tr("Programmer not specified"))
8482
}
8583
_, err := Upload(ctx, &rpc.UploadRequest{
8684
Instance: req.GetInstance(),
@@ -102,17 +100,17 @@ func runProgramAction(pm *packagemanager.PackageManager,
102100
programmerID string,
103101
verbose, verify, burnBootloader bool,
104102
outStream, errStream io.Writer,
105-
dryRun bool) error {
103+
dryRun bool) *status.Status {
106104

107105
if burnBootloader && programmerID == "" {
108-
return fmt.Errorf(tr("no programmer specified for burning bootloader"))
106+
return status.New(codes.InvalidArgument, tr("No programmer specified for burning bootloader"))
109107
}
110108

111109
// FIXME: make a specification on how a port is specified via command line
112110
if port == "" && sk != nil && sk.Metadata != nil {
113111
deviceURI, err := url.Parse(sk.Metadata.CPU.Port)
114112
if err != nil {
115-
return fmt.Errorf(tr("invalid Device URL format: %s"), err)
113+
return status.Newf(codes.InvalidArgument, tr("Invalid Device URL format: %s"), err)
116114
}
117115
if deviceURI.Scheme == "serial" {
118116
port = deviceURI.Host + deviceURI.Path
@@ -124,18 +122,18 @@ func runProgramAction(pm *packagemanager.PackageManager,
124122
fqbnIn = sk.Metadata.CPU.Fqbn
125123
}
126124
if fqbnIn == "" {
127-
return fmt.Errorf(tr("no Fully Qualified Board Name provided"))
125+
return status.New(codes.InvalidArgument, tr("No FQBN (Fully Qualified Board Name) provided"))
128126
}
129127
fqbn, err := cores.ParseFQBN(fqbnIn)
130128
if err != nil {
131-
return fmt.Errorf(tr("incorrect FQBN: %s"), err)
129+
return status.Newf(codes.InvalidArgument, fmt.Sprintf(tr("Invalid FQBN: %s"), err))
132130
}
133131
logrus.WithField("fqbn", fqbn).Tracef("Detected FQBN")
134132

135133
// Find target board and board properties
136134
_, boardPlatform, board, boardProperties, buildPlatform, err := pm.ResolveFQBN(fqbn)
137135
if err != nil {
138-
return fmt.Errorf(tr("incorrect FQBN: %s"), err)
136+
return status.Newf(codes.InvalidArgument, tr("Could not resolve FQBN: %s"), err)
139137
}
140138
logrus.
141139
WithField("boardPlatform", boardPlatform).
@@ -152,7 +150,7 @@ func runProgramAction(pm *packagemanager.PackageManager,
152150
programmer = buildPlatform.Programmers[programmerID]
153151
}
154152
if programmer == nil {
155-
return fmt.Errorf(tr("programmer '%s' not available"), programmerID)
153+
return status.Newf(codes.InvalidArgument, tr("Programmer '%s' not available"), programmerID)
156154
}
157155
}
158156

@@ -177,7 +175,7 @@ func runProgramAction(pm *packagemanager.PackageManager,
177175
if t, ok := props.GetOk(toolProperty); ok {
178176
uploadToolID = t
179177
} else {
180-
return fmt.Errorf(tr("cannot get programmer tool: undefined '%s' property"), toolProperty)
178+
return status.Newf(codes.FailedPrecondition, tr("Cannot get programmer tool: undefined '%s' property"), toolProperty)
181179
}
182180
}
183181

@@ -193,7 +191,7 @@ func runProgramAction(pm *packagemanager.PackageManager,
193191
Trace("Upload tool")
194192

195193
if split := strings.Split(uploadToolID, ":"); len(split) > 2 {
196-
return fmt.Errorf(tr("invalid 'upload.tool' property: %s"), uploadToolID)
194+
return status.Newf(codes.FailedPrecondition, tr("Invalid 'upload.tool' property: %s"), uploadToolID)
197195
} else if len(split) == 2 {
198196
uploadToolID = split[1]
199197
uploadToolPlatform = pm.GetInstalledPlatformRelease(
@@ -232,7 +230,10 @@ func runProgramAction(pm *packagemanager.PackageManager,
232230
}
233231

234232
if !uploadProperties.ContainsKey("upload.protocol") && programmer == nil {
235-
return fmt.Errorf(tr("a programmer is required to upload for this board"))
233+
err, _ := status.
234+
Newf(codes.InvalidArgument, tr("A programmer is required to upload on this board")).
235+
WithDetails(&rpc.ProgrammerIsRequiredForUploadError{})
236+
return err
236237
}
237238

238239
// Set properties for verbose upload
@@ -280,13 +281,13 @@ func runProgramAction(pm *packagemanager.PackageManager,
280281
if !burnBootloader {
281282
importPath, sketchName, err := determineBuildPathAndSketchName(importFile, importDir, sk, fqbn)
282283
if err != nil {
283-
return errors.Errorf(tr("retrieving build artifacts: %s"), err)
284+
return status.Newf(codes.Internal, tr("Error finding build artifacts: %s"), err)
284285
}
285286
if !importPath.Exist() {
286-
return fmt.Errorf(tr("compiled sketch not found in %s"), importPath)
287+
return status.Newf(codes.Internal, tr("Compiled sketch not found in %s"), importPath)
287288
}
288289
if !importPath.IsDir() {
289-
return fmt.Errorf(tr("expected compiled sketch in directory %s, but is a file instead"), importPath)
290+
return status.Newf(codes.Internal, tr("Expected compiled sketch in directory %s, but is a file instead"), importPath)
290291
}
291292
uploadProperties.SetPath("build.path", importPath)
292293
uploadProperties.Set("build.project_name", sketchName)
@@ -370,18 +371,18 @@ func runProgramAction(pm *packagemanager.PackageManager,
370371
// Run recipes for upload
371372
if burnBootloader {
372373
if err := runTool("erase.pattern", uploadProperties, outStream, errStream, verbose, dryRun); err != nil {
373-
return fmt.Errorf(tr("chip erase error: %s"), err)
374+
return status.Newf(codes.Internal, tr("Failed chip erase: %s"), err)
374375
}
375376
if err := runTool("bootloader.pattern", uploadProperties, outStream, errStream, verbose, dryRun); err != nil {
376-
return fmt.Errorf(tr("burn bootloader error: %s"), err)
377+
return status.Newf(codes.Internal, tr("Failed to burn bootloader: %s"), err)
377378
}
378379
} else if programmer != nil {
379380
if err := runTool("program.pattern", uploadProperties, outStream, errStream, verbose, dryRun); err != nil {
380-
return fmt.Errorf(tr("programming error: %s"), err)
381+
return status.Newf(codes.Internal, tr("Failed programming: %s"), err)
381382
}
382383
} else {
383384
if err := runTool("upload.pattern", uploadProperties, outStream, errStream, verbose, dryRun); err != nil {
384-
return fmt.Errorf(tr("uploading error: %s"), err)
385+
return status.Newf(codes.Internal, tr("Failed uploading: %s"), err)
385386
}
386387
}
387388

commands/upload/upload_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ func TestUploadPropertiesComposition(t *testing.T) {
177177
testRunner := func(t *testing.T, test test, verboseVerify bool) {
178178
outStream := &bytes.Buffer{}
179179
errStream := &bytes.Buffer{}
180-
err := runProgramAction(
180+
status := runProgramAction(
181181
pm,
182182
nil, // sketch
183183
"", // importFile
@@ -197,9 +197,9 @@ func TestUploadPropertiesComposition(t *testing.T) {
197197
verboseVerifyOutput = "quiet noverify"
198198
}
199199
if test.expectedOutput == "FAIL" {
200-
require.Error(t, err)
200+
require.NotNil(t, status)
201201
} else {
202-
require.NoError(t, err)
202+
require.Nil(t, status)
203203
outFiltered := strings.ReplaceAll(outStream.String(), "\r", "")
204204
outFiltered = strings.ReplaceAll(outFiltered, "\\", "/")
205205
require.Contains(t, outFiltered, strings.ReplaceAll(test.expectedOutput, "$$VERBOSE-VERIFY$$", verboseVerifyOutput))

0 commit comments

Comments
 (0)