diff --git a/internal/metaerrors/metaerrors.go b/internal/metaerrors/metaerrors.go new file mode 100644 index 000000000..9fce8d7a1 --- /dev/null +++ b/internal/metaerrors/metaerrors.go @@ -0,0 +1,59 @@ +/* + * Copyright 2024 The Yorkie Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Package metaerrors provides a way to attach metadata to errors. +package metaerrors + +import "strings" + +// MetaError is an error that can have metadata attached to it. This can be used +// to send additional information to the SDK or to the user. +type MetaError struct { + // Err is the underlying error. + Err error + + // Metadata is a map of additional information that can be attached to the + // error. + Metadata map[string]string +} + +// New returns a new MetaError with the given error and metadata. +func New(err error, metadata map[string]string) *MetaError { + return &MetaError{ + Err: err, + Metadata: metadata, + } +} + +// Error returns the error message. +func (e MetaError) Error() string { + if len(e.Metadata) == 0 { + return e.Err.Error() + } + + sb := strings.Builder{} + + for key, val := range e.Metadata { + if sb.Len() > 0 { + sb.WriteString(",") + } + sb.WriteString(key) + sb.WriteString("=") + sb.WriteString(val) + } + + return e.Err.Error() + " [" + sb.String() + "]" +} diff --git a/internal/metaerrors/metaerrors_test.go b/internal/metaerrors/metaerrors_test.go new file mode 100644 index 000000000..948d92aa4 --- /dev/null +++ b/internal/metaerrors/metaerrors_test.go @@ -0,0 +1,57 @@ +/* + * Copyright 2024 The Yorkie Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package metaerrors_test + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/yorkie-team/yorkie/internal/metaerrors" +) + +func TestMetaError(t *testing.T) { + t.Run("test meta error", func(t *testing.T) { + err := errors.New("error message") + metaErr := metaerrors.New(err, map[string]string{"key": "value"}) + assert.Equal(t, "error message [key=value]", metaErr.Error()) + + err = errors.New("error message") + metaErr = metaerrors.New(err, map[string]string{"key1": "value1", "key2": "value2"}) + assert.Equal(t, "error message [key1=value1,key2=value2]", metaErr.Error()) + }) + + t.Run("test meta error without metadata", func(t *testing.T) { + err := errors.New("error message") + metaErr := metaerrors.New(err, nil) + assert.Equal(t, "error message", metaErr.Error()) + }) + + t.Run("test meta error with wrapped error", func(t *testing.T) { + err := fmt.Errorf("wrapped error: %w", errors.New("error message")) + metaErr := metaerrors.New(err, map[string]string{"key": "value"}) + assert.Equal(t, "wrapped error: error message [key=value]", metaErr.Error()) + + metaErr = metaerrors.New(errors.New("error message"), map[string]string{"key": "value"}) + assert.Equal(t, "error message [key=value]", metaErr.Error()) + + wrappedErr := fmt.Errorf("wrapped error: %w", metaErr) + assert.Equal(t, "wrapped error: error message [key=value]", wrappedErr.Error()) + }) +} diff --git a/internal/richerror/richerror.go b/internal/richerror/richerror.go deleted file mode 100644 index b8e8f9fe6..000000000 --- a/internal/richerror/richerror.go +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2024 The Yorkie Authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Package richerror provides a rich error type that can be used to wrap errors -package richerror - -// RichError is an error type that can be used to wrap errors with additional metadata -type RichError struct { - Err error - Metadata map[string]string -} - -func (e RichError) Error() string { - return e.Err.Error() -} diff --git a/internal/validation/validation.go b/internal/validation/validation.go index 3c802c7df..e5ad41d6e 100644 --- a/internal/validation/validation.go +++ b/internal/validation/validation.go @@ -14,7 +14,7 @@ * limitations under the License. */ -// Package validation provides the validation functions. +// Package validation provides the validation functions for form and field. package validation import ( diff --git a/server/rpc/auth/webhook.go b/server/rpc/auth/webhook.go index d9f4a198e..a3b2ebd54 100644 --- a/server/rpc/auth/webhook.go +++ b/server/rpc/auth/webhook.go @@ -28,7 +28,7 @@ import ( "time" "github.com/yorkie-team/yorkie/api/types" - "github.com/yorkie-team/yorkie/internal/richerror" + "github.com/yorkie-team/yorkie/internal/metaerrors" "github.com/yorkie-team/yorkie/server/backend" "github.com/yorkie-team/yorkie/server/logging" ) @@ -109,11 +109,10 @@ func verifyAccess( return resp.StatusCode, fmt.Errorf("%s: %w", authResp.Message, ErrPermissionDenied) } if authResp.Code == types.CodeUnauthenticated { - richError := &richerror.RichError{ - Err: ErrUnauthenticated, - Metadata: map[string]string{"message": authResp.Message}, - } - return resp.StatusCode, richError + return resp.StatusCode, metaerrors.New( + ErrUnauthenticated, + map[string]string{"message": authResp.Message}, + ) } return resp.StatusCode, fmt.Errorf("%d: %w", authResp.Code, ErrUnexpectedResponse) diff --git a/server/rpc/connecthelper/status.go b/server/rpc/connecthelper/status.go index 1dbbabd94..1eef3306e 100644 --- a/server/rpc/connecthelper/status.go +++ b/server/rpc/connecthelper/status.go @@ -26,7 +26,7 @@ import ( "github.com/yorkie-team/yorkie/api/converter" "github.com/yorkie-team/yorkie/api/types" - "github.com/yorkie-team/yorkie/internal/richerror" + "github.com/yorkie-team/yorkie/internal/metaerrors" "github.com/yorkie-team/yorkie/internal/validation" "github.com/yorkie-team/yorkie/pkg/document/key" "github.com/yorkie-team/yorkie/pkg/document/time" @@ -186,10 +186,10 @@ func errorToConnectError(err error) (*connect.Error, bool) { return connectErr, true } -// richErrorToConnectError returns connect.Error from the given rich error. -func richErrorToConnectError(err error) (*connect.Error, bool) { - var richError *richerror.RichError - if !errors.As(err, &richError) { +// metaErrorToConnectError returns connect.Error from the given rich error. +func metaErrorToConnectError(err error) (*connect.Error, bool) { + var metaErr *metaerrors.MetaError + if !errors.As(err, &metaErr) { return nil, false } @@ -203,17 +203,17 @@ func richErrorToConnectError(err error) (*connect.Error, bool) { } }() - connectCode, ok = errorToConnectCode[richError.Err] + connectCode, ok = errorToConnectCode[metaErr.Err] if !ok { return nil, false } connectErr := connect.NewError(connectCode, err) - if code, ok := errorToCode[richError.Err]; ok { + if code, ok := errorToCode[metaErr.Err]; ok { errorInfo := &errdetails.ErrorInfo{ Metadata: map[string]string{"code": code}, } - for key, value := range richError.Metadata { + for key, value := range metaErr.Metadata { errorInfo.Metadata[key] = value } if detail, detailErr := connect.NewErrorDetail(errorInfo); detailErr == nil { @@ -270,7 +270,7 @@ func ToStatusError(err error) error { return nil } - if err, ok := richErrorToConnectError(err); ok { + if err, ok := metaErrorToConnectError(err); ok { return err }