Skip to content

Commit 6b7c627

Browse files
Implement server errors and add validation errors (#112)
1 parent d3db15b commit 6b7c627

File tree

12 files changed

+286
-29
lines changed

12 files changed

+286
-29
lines changed

api/deploy/deploy.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"github.com/hashicorp/hcl/v2"
88
"github.com/multycloud/multy/api/converter"
9+
"github.com/multycloud/multy/api/errors"
910
"github.com/multycloud/multy/api/proto/common"
1011
"github.com/multycloud/multy/api/proto/config"
1112
"github.com/multycloud/multy/api/proto/resources"
@@ -146,7 +147,10 @@ func Translate(c *config.Config, prev *config.Resource, curr *config.Resource) (
146147
Providers: provider,
147148
}
148149

149-
hclOutput := encoder.Encode(&decodedResources)
150+
hclOutput, errs := encoder.Encode(&decodedResources)
151+
if errs != nil {
152+
return hclOutput, errors.ValidationErrors(errs)
153+
}
150154

151155
return hclOutput, nil
152156
}

api/errors/errors.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package errors
2+
3+
import (
4+
"fmt"
5+
pberr "github.com/multycloud/multy/api/proto/errors"
6+
"github.com/multycloud/multy/util"
7+
"github.com/multycloud/multy/validate"
8+
"google.golang.org/genproto/googleapis/rpc/code"
9+
spb "google.golang.org/genproto/googleapis/rpc/status"
10+
"google.golang.org/grpc/status"
11+
"google.golang.org/protobuf/types/known/anypb"
12+
)
13+
14+
func PermissionDenied(msg string) error {
15+
return status.ErrorProto(&spb.Status{
16+
Code: int32(code.Code_PERMISSION_DENIED),
17+
Message: msg,
18+
Details: nil,
19+
})
20+
}
21+
22+
func InternalServerError(err error) error {
23+
if _, ok := status.FromError(err); ok {
24+
return err
25+
}
26+
27+
return status.ErrorProto(&spb.Status{
28+
Code: int32(code.Code_INTERNAL),
29+
Message: err.Error(),
30+
Details: nil,
31+
})
32+
}
33+
34+
func ValidationErrors(errs []validate.ValidationError) error {
35+
return status.ErrorProto(&spb.Status{
36+
Code: int32(code.Code_INVALID_ARGUMENT),
37+
Message: fmt.Sprintf("%d validation errors found", len(errs)),
38+
Details: util.MapSliceValues(errs, func(e validate.ValidationError) *anypb.Any {
39+
a, _ := anypb.New(&pberr.ResourceValidationError{
40+
ResourceId: e.ResourceId,
41+
ErrorMessage: e.ErrorMessage,
42+
FieldName: e.FieldName,
43+
})
44+
return a
45+
}),
46+
})
47+
}

api/proto/errors/errors.pb.go

Lines changed: 170 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/proto/errors/errors.proto

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
syntax = "proto3";
2+
3+
option go_package = "github.com/multycloud/multy/api/proto/errors";
4+
option java_multiple_files = true;
5+
option java_package = "dev.multy.api.errors";
6+
option java_outer_classname = "MultyProto";
7+
8+
package dev.multy.config;
9+
10+
message ResourceValidationError {
11+
string resource_id = 1;
12+
string error_message = 2;
13+
// this is tentative, it might not be populated or might have a different name from the proto request
14+
string field_name = 3;
15+
}

api/services/service.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"github.com/multycloud/multy/api/converter"
77
"github.com/multycloud/multy/api/deploy"
8+
"github.com/multycloud/multy/api/errors"
89
"github.com/multycloud/multy/api/proto/common"
910
"github.com/multycloud/multy/api/proto/config"
1011
"github.com/multycloud/multy/api/proto/resources"
@@ -33,15 +34,28 @@ type Service[Arg proto.Message, OutT proto.Message] struct {
3334
Converters converter.ResourceConverters[Arg, OutT]
3435
}
3536

37+
func WrappingErrors[InT any, OutT any](f func(context.Context, InT) (OutT, error)) func(context.Context, InT) (OutT, error) {
38+
return func(ctx context.Context, in InT) (OutT, error) {
39+
out, err := f(ctx, in)
40+
if err != nil {
41+
return out, errors.InternalServerError(err)
42+
}
43+
return out, nil
44+
}
45+
}
46+
3647
func (s Service[Arg, OutT]) Create(ctx context.Context, in CreateRequest[Arg]) (OutT, error) {
48+
return WrappingErrors(s.create)(ctx, in)
49+
}
50+
51+
func (s Service[Arg, OutT]) create(ctx context.Context, in CreateRequest[Arg]) (OutT, error) {
3752
fmt.Println("Service create")
3853
userId, err := util.ExtractUserId(ctx)
3954
if err != nil {
4055
return *new(OutT), err
4156
}
4257
c, err := s.Db.LoadUserConfig(userId)
4358
if err != nil {
44-
4559
return *new(OutT), err
4660
}
4761
resource, err := util.InsertIntoConfig(in.GetResources(), c)
@@ -63,6 +77,10 @@ func (s Service[Arg, OutT]) Create(ctx context.Context, in CreateRequest[Arg]) (
6377
}
6478

6579
func (s Service[Arg, OutT]) Read(ctx context.Context, in WithResourceId) (OutT, error) {
80+
return WrappingErrors(s.read)(ctx, in)
81+
}
82+
83+
func (s Service[Arg, OutT]) read(ctx context.Context, in WithResourceId) (OutT, error) {
6684
fmt.Printf("Service read: %s\n", in.GetResourceId())
6785
userId, err := util.ExtractUserId(ctx)
6886
if err != nil {
@@ -95,6 +113,10 @@ func (s Service[Arg, OutT]) Read(ctx context.Context, in WithResourceId) (OutT,
95113
}
96114

97115
func (s Service[Arg, OutT]) Update(ctx context.Context, in UpdateRequest[Arg]) (OutT, error) {
116+
return WrappingErrors(s.update)(ctx, in)
117+
}
118+
119+
func (s Service[Arg, OutT]) update(ctx context.Context, in UpdateRequest[Arg]) (OutT, error) {
98120
fmt.Printf("Service update: %s\n", in.GetResourceId())
99121
userId, err := util.ExtractUserId(ctx)
100122
if err != nil {
@@ -123,6 +145,10 @@ func (s Service[Arg, OutT]) Update(ctx context.Context, in UpdateRequest[Arg]) (
123145
}
124146

125147
func (s Service[Arg, OutT]) Delete(ctx context.Context, in WithResourceId) (*common.Empty, error) {
148+
return WrappingErrors(s.delete)(ctx, in)
149+
}
150+
151+
func (s Service[Arg, OutT]) delete(ctx context.Context, in WithResourceId) (*common.Empty, error) {
126152
fmt.Printf("Service delete: %s\n", in.GetResourceId())
127153
userId, err := util.ExtractUserId(ctx)
128154
if err != nil {

api/util/util.go

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import (
55
"encoding/base64"
66
"fmt"
77
"github.com/google/uuid"
8+
"github.com/multycloud/multy/api/errors"
89
"github.com/multycloud/multy/api/proto/common"
910
"github.com/multycloud/multy/api/proto/config"
1011
"golang.org/x/exp/slices"
1112
"google.golang.org/grpc/metadata"
1213
"google.golang.org/protobuf/proto"
1314
"google.golang.org/protobuf/types/known/anypb"
15+
"strings"
1416
)
1517

1618
func ConvertCommonParams(parameters *common.CloudSpecificResourceCommonArgs) *common.CloudSpecificCommonResourceParameters {
@@ -25,29 +27,15 @@ func ConvertCommonParams(parameters *common.CloudSpecificResourceCommonArgs) *co
2527
func ExtractUserId(ctx context.Context) (string, error) {
2628
md, _ := metadata.FromIncomingContext(ctx)
2729
userIds := md.Get("user_id")
28-
// fix me why
29-
userIds = removeDuplicateStr(userIds)
3030
if len(userIds) == 0 {
31-
return "", fmt.Errorf("user id must be set")
31+
return "", errors.PermissionDenied(fmt.Sprintf("user id must be set"))
3232
}
3333
if len(userIds) > 1 {
34-
return "", fmt.Errorf("only expected 1 user id, found %d", len(userIds))
34+
return "", errors.PermissionDenied(fmt.Sprintf("only expected 1 user id, found %d: %s", len(userIds), strings.Join(userIds, ", ")))
3535
}
3636
return userIds[0], nil
3737
}
3838

39-
func removeDuplicateStr(strSlice []string) []string {
40-
allKeys := make(map[string]bool)
41-
list := []string{}
42-
for _, item := range strSlice {
43-
if _, value := allKeys[item]; !value {
44-
allKeys[item] = true
45-
list = append(list, item)
46-
}
47-
}
48-
return list
49-
}
50-
5139
func InsertIntoConfig[Arg proto.Message](in []Arg, c *config.Config) (*config.Resource, error) {
5240
args, err := convert(in)
5341
if err != nil {

cli/check_command.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/multycloud/multy/encoder"
88
"github.com/multycloud/multy/parser"
99
"github.com/multycloud/multy/resources"
10+
"github.com/multycloud/multy/validate"
1011
flag "github.com/spf13/pflag"
1112
"io/ioutil"
1213
"log"
@@ -54,7 +55,10 @@ func (c *CheckCommand) Execute(ctx context.Context) error {
5455
r := decoder.Decode(parsedConfig)
5556
mctx := resources.MultyContext{Resources: r.Resources, Location: r.GlobalConfig.Location}
5657

57-
_ = encoder.TranslateResources(r, mctx)
58+
_, errs := encoder.TranslateResources(r, mctx)
59+
if errs != nil {
60+
validate.PrintAllAndExit(errs)
61+
}
5862

5963
fmt.Println("no validation errors found")
6064
return nil

0 commit comments

Comments
 (0)