Skip to content

Commit 3a17f7f

Browse files
committed
Proper gRPC error handling
1 parent 83d71c8 commit 3a17f7f

File tree

13 files changed

+249
-174
lines changed

13 files changed

+249
-174
lines changed

cli/compile/compile.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/arduino/arduino-cli/cli/feedback"
2626
"github.com/arduino/arduino-cli/cli/output"
2727
"github.com/arduino/arduino-cli/configuration"
28+
"google.golang.org/grpc/status"
2829

2930
"github.com/arduino/arduino-cli/cli/errorcodes"
3031
"github.com/arduino/arduino-cli/cli/instance"
@@ -197,7 +198,7 @@ func run(cmd *cobra.Command, args []string) {
197198
ImportDir: buildPath,
198199
Programmer: programmer,
199200
}
200-
var err error
201+
var err *status.Status
201202
if output.OutputFormat == "json" {
202203
// TODO: do not print upload output in json mode
203204
uploadOut := new(bytes.Buffer)

client_example/main.go

+1
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

+3-3
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ func (s *ArduinoCoreServerImpl) Upload(req *rpc.UploadRequest, stream rpc.Arduin
288288
utils.FeedStreamTo(func(data []byte) { stream.Send(&rpc.UploadResponse{ErrStream: data}) }),
289289
)
290290
if err != nil {
291-
return err
291+
return err.Err()
292292
}
293293
return stream.Send(resp)
294294
}
@@ -301,7 +301,7 @@ func (s *ArduinoCoreServerImpl) UploadUsingProgrammer(req *rpc.UploadUsingProgra
301301
utils.FeedStreamTo(func(data []byte) { stream.Send(&rpc.UploadUsingProgrammerResponse{ErrStream: data}) }),
302302
)
303303
if err != nil {
304-
return err
304+
return err.Err()
305305
}
306306
return stream.Send(resp)
307307
}
@@ -314,7 +314,7 @@ func (s *ArduinoCoreServerImpl) BurnBootloader(req *rpc.BurnBootloaderRequest, s
314314
utils.FeedStreamTo(func(data []byte) { stream.Send(&rpc.BurnBootloaderResponse{ErrStream: data}) }),
315315
)
316316
if err != nil {
317-
return err
317+
return err.Err()
318318
}
319319
return stream.Send(resp)
320320
}

commands/upload/burnbootloader.go

+2-1
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

+27-26
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,25 @@ 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
// Upload FIXMEDOC
42-
func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, errStream io.Writer) (*rpc.UploadResponse, error) {
44+
func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, errStream io.Writer) (*rpc.UploadResponse, *status.Status) {
4345
logrus.Tracef("Upload %s on %s started", req.GetSketchPath(), req.GetFqbn())
4446

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

5355
pm := commands.GetPackageManager(req.GetInstance().GetId())
5456

55-
err = runProgramAction(
57+
return &rpc.UploadResponse{}, runProgramAction(
5658
pm,
5759
sketch,
5860
req.GetImportFile(),
@@ -66,18 +68,14 @@ func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, er
6668
outStream,
6769
errStream,
6870
)
69-
if err != nil {
70-
return nil, err
71-
}
72-
return &rpc.UploadResponse{}, nil
7371
}
7472

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

7977
if req.GetProgrammer() == "" {
80-
return nil, errors.New("programmer not specified")
78+
return nil, status.New(codes.InvalidArgument, "Programmer not specified")
8179
}
8280
_, err := Upload(ctx, &rpc.UploadRequest{
8381
Instance: req.GetInstance(),
@@ -98,17 +96,17 @@ func runProgramAction(pm *packagemanager.PackageManager,
9896
importFile, importDir, fqbnIn, port string,
9997
programmerID string,
10098
verbose, verify, burnBootloader bool,
101-
outStream, errStream io.Writer) error {
99+
outStream, errStream io.Writer) *status.Status {
102100

103101
if burnBootloader && programmerID == "" {
104-
return fmt.Errorf("no programmer specified for burning bootloader")
102+
return status.New(codes.InvalidArgument, "Programmer not specified")
105103
}
106104

107105
// FIXME: make a specification on how a port is specified via command line
108106
if port == "" && sketch != nil && sketch.Metadata != nil {
109107
deviceURI, err := url.Parse(sketch.Metadata.CPU.Port)
110108
if err != nil {
111-
return fmt.Errorf("invalid Device URL format: %s", err)
109+
return status.Newf(codes.InvalidArgument, "Invalid Device URL format: %s", err)
112110
}
113111
if deviceURI.Scheme == "serial" {
114112
port = deviceURI.Host + deviceURI.Path
@@ -120,18 +118,18 @@ func runProgramAction(pm *packagemanager.PackageManager,
120118
fqbnIn = sketch.Metadata.CPU.Fqbn
121119
}
122120
if fqbnIn == "" {
123-
return fmt.Errorf("no Fully Qualified Board Name provided")
121+
return status.New(codes.InvalidArgument, "No FQBN (Fully Qualified Board Name) provided")
124122
}
125123
fqbn, err := cores.ParseFQBN(fqbnIn)
126124
if err != nil {
127-
return fmt.Errorf("incorrect FQBN: %s", err)
125+
return status.Newf(codes.InvalidArgument, fmt.Sprintf("Invalid FQBN: %s", err))
128126
}
129127
logrus.WithField("fqbn", fqbn).Tracef("Detected FQBN")
130128

131129
// Find target board and board properties
132130
_, boardPlatform, board, boardProperties, buildPlatform, err := pm.ResolveFQBN(fqbn)
133131
if err != nil {
134-
return fmt.Errorf("incorrect FQBN: %s", err)
132+
return status.Newf(codes.InvalidArgument, "Could not resolve FQBN: %s", err)
135133
}
136134
logrus.
137135
WithField("boardPlatform", boardPlatform).
@@ -148,7 +146,7 @@ func runProgramAction(pm *packagemanager.PackageManager,
148146
programmer = buildPlatform.Programmers[programmerID]
149147
}
150148
if programmer == nil {
151-
return fmt.Errorf("programmer '%s' not available", programmerID)
149+
return status.Newf(codes.InvalidArgument, "Programmer '%s' not available", programmerID)
152150
}
153151
}
154152

@@ -173,7 +171,7 @@ func runProgramAction(pm *packagemanager.PackageManager,
173171
if t, ok := props.GetOk(toolProperty); ok {
174172
uploadToolID = t
175173
} else {
176-
return fmt.Errorf("cannot get programmer tool: undefined '%s' property", toolProperty)
174+
return status.Newf(codes.FailedPrecondition, "Cannot get programmer tool: undefined '%s' property", toolProperty)
177175
}
178176
}
179177

@@ -189,7 +187,7 @@ func runProgramAction(pm *packagemanager.PackageManager,
189187
Trace("Upload tool")
190188

191189
if split := strings.Split(uploadToolID, ":"); len(split) > 2 {
192-
return fmt.Errorf("invalid 'upload.tool' property: %s", uploadToolID)
190+
return status.Newf(codes.FailedPrecondition, "Invalid 'upload.tool' property: %s", uploadToolID)
193191
} else if len(split) == 2 {
194192
uploadToolID = split[1]
195193
uploadToolPlatform = pm.GetInstalledPlatformRelease(
@@ -228,7 +226,10 @@ func runProgramAction(pm *packagemanager.PackageManager,
228226
}
229227

230228
if !uploadProperties.ContainsKey("upload.protocol") && programmer == nil {
231-
return fmt.Errorf("a programmer is required to upload for this board")
229+
err, _ := status.
230+
Newf(codes.InvalidArgument, "A programmer is required to upload on this board").
231+
WithDetails(&rpc.ProgrammerIsRequiredForUploadError{})
232+
return err
232233
}
233234

234235
// Set properties for verbose upload
@@ -276,13 +277,13 @@ func runProgramAction(pm *packagemanager.PackageManager,
276277
if !burnBootloader {
277278
importPath, sketchName, err := determineBuildPathAndSketchName(importFile, importDir, sketch, fqbn)
278279
if err != nil {
279-
return errors.Errorf("retrieving build artifacts: %s", err)
280+
return status.Newf(codes.Internal, "Error finding build artifacts: %s", err)
280281
}
281282
if !importPath.Exist() {
282-
return fmt.Errorf("compiled sketch not found in %s", importPath)
283+
return status.Newf(codes.Internal, "Compiled sketch not found in %s", importPath)
283284
}
284285
if !importPath.IsDir() {
285-
return fmt.Errorf("expected compiled sketch in directory %s, but is a file instead", importPath)
286+
return status.Newf(codes.Internal, "Expected compiled sketch in directory %s, but is a file instead", importPath)
286287
}
287288
uploadProperties.SetPath("build.path", importPath)
288289
uploadProperties.Set("build.project_name", sketchName)
@@ -359,18 +360,18 @@ func runProgramAction(pm *packagemanager.PackageManager,
359360
// Run recipes for upload
360361
if burnBootloader {
361362
if err := runTool("erase.pattern", uploadProperties, outStream, errStream, verbose); err != nil {
362-
return fmt.Errorf("chip erase error: %s", err)
363+
return status.Newf(codes.Internal, "Failed chip erase: %s", err)
363364
}
364365
if err := runTool("bootloader.pattern", uploadProperties, outStream, errStream, verbose); err != nil {
365-
return fmt.Errorf("burn bootloader error: %s", err)
366+
return status.Newf(codes.Internal, "Failed to burn bootloader: %s", err)
366367
}
367368
} else if programmer != nil {
368369
if err := runTool("program.pattern", uploadProperties, outStream, errStream, verbose); err != nil {
369-
return fmt.Errorf("programming error: %s", err)
370+
return status.Newf(codes.Internal, "Failed programming: %s", err)
370371
}
371372
} else {
372373
if err := runTool("upload.pattern", uploadProperties, outStream, errStream, verbose); err != nil {
373-
return fmt.Errorf("uploading error: %s", err)
374+
return status.Newf(codes.Internal, "Failed uploading: %s", err)
374375
}
375376
}
376377

commands/upload/upload_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ func TestUploadPropertiesComposition(t *testing.T) {
178178
testRunner := func(t *testing.T, test test, verboseVerify bool) {
179179
outStream := &bytes.Buffer{}
180180
errStream := &bytes.Buffer{}
181-
err := runProgramAction(
181+
status := runProgramAction(
182182
pm,
183183
nil, // sketch
184184
"", // 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)