Repository: yorkie-team/yorkie
Pull request #519 (2023-04-21 01:58:20)

Remove panic and wrapping informational error from server
<!--  Thanks for sending a pull request! -->

**What this PR does / why we need it**:

Sub PR for https://github.com/yorkie-team/yorkie/pull/501

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
-->
```release-note

```

**Additional documentation**:

<!--
This section can be blank if this pull request does not require a release note.

Please use the following format for linking documentation:
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs

```

**Checklist**:
- [ ] Added relevant tests or not required
- [ ] Didn't break anything


codecov[bot]: ## [Codecov](https://codecov.io/gh/yorkie-team/yorkie/pull/519?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) Report
> Merging [#519](https://codecov.io/gh/yorkie-team/yorkie/pull/519?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) (e4fe946) into [main](https://codecov.io/gh/yorkie-team/yorkie/commit/efe3e87da1e726858408209142476b0a75b7b9d1?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) (efe3e87) will **increase** coverage by `0.39%`.
> The diff coverage is `31.00%`.

```diff
@@            Coverage Diff             @@
##             main     #519      +/-   ##
==========================================
+ Coverage   50.22%   50.61%   +0.39%     
==========================================
  Files          64       64              
  Lines        5671     5751      +80     
==========================================
+ Hits         2848     2911      +63     
+ Misses       2481     2471      -10     
- Partials      342      369      +27     
```


| [Impacted Files](https://codecov.io/gh/yorkie-team/yorkie/pull/519?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) | Coverage Δ | |
|---|---|---|
| [server/backend/config.go](https://codecov.io/gh/yorkie-team/yorkie/pull/519?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-c2VydmVyL2JhY2tlbmQvY29uZmlnLmdv) | `55.55% <0.00%> (-5.43%)` | :arrow_down: |
| [server/backend/database/mongo/config.go](https://codecov.io/gh/yorkie-team/yorkie/pull/519?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-c2VydmVyL2JhY2tlbmQvZGF0YWJhc2UvbW9uZ28vY29uZmlnLmdv) | `73.91% <0.00%> (-7.04%)` | :arrow_down: |
| [internal/validation/validation.go](https://codecov.io/gh/yorkie-team/yorkie/pull/519?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-aW50ZXJuYWwvdmFsaWRhdGlvbi92YWxpZGF0aW9uLmdv) | `44.80% <30.76%> (-15.88%)` | :arrow_down: |
| [api/types/create\_project\_fields.go](https://codecov.io/gh/yorkie-team/yorkie/pull/519?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-YXBpL3R5cGVzL2NyZWF0ZV9wcm9qZWN0X2ZpZWxkcy5nbw==) | `41.66% <45.45%> (-41.67%)` | :arrow_down: |
| [api/types/updatable\_project\_fields.go](https://codecov.io/gh/yorkie-team/yorkie/pull/519?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-YXBpL3R5cGVzL3VwZGF0YWJsZV9wcm9qZWN0X2ZpZWxkcy5nbw==) | `60.00% <50.00%> (-40.00%)` | :arrow_down: |

... and [4 files with indirect coverage changes](https://codecov.io/gh/yorkie-team/yorkie/pull/519/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team)

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team)


Patch:
diff --git a/api/types/create_project_fields.go b/api/types/create_project_fields.go
index 80da201b..98776526 100644
--- a/api/types/create_project_fields.go
+++ b/api/types/create_project_fields.go
@@ -18,6 +18,9 @@
 package types
 
 import (
+	"fmt"
+	"os"
+
 	"github.com/yorkie-team/yorkie/internal/validation"
 )
 
@@ -38,10 +41,19 @@ func (i *CreateProjectFields) Validate() error {
 }
 
 func init() {
-	validation.RegisterValidation("reserved_project_name", func(level validation.FieldLevel) bool {
-		_, ok := reservedProjectNames[level.Field().String()]
-		return !ok
-	})
+	if err := validation.RegisterValidation(
+		"reserved_project_name",
+		func(level validation.FieldLevel) bool {
+			_, ok := reservedProjectNames[level.Field().String()]
+			return !ok
+		},
+	); err != nil {
+		fmt.Fprintln(os.Stderr, "create project fields: ", err)
+		os.Exit(1)
+	}
 
-	validation.RegisterTranslation("reserved_project_name", "given {0} is reserved name")
+	if err := validation.RegisterTranslation("reserved_project_name", "given {0} is reserved name"); err != nil {
+		fmt.Fprintln(os.Stderr, "create project fields: ", err)
+		os.Exit(1)
+	}
 }
diff --git a/api/types/updatable_project_fields.go b/api/types/updatable_project_fields.go
index ec8a89f6..ef389797 100644
--- a/api/types/updatable_project_fields.go
+++ b/api/types/updatable_project_fields.go
@@ -19,6 +19,8 @@ package types
 
 import (
 	"errors"
+	"fmt"
+	"os"
 
 	"github.com/yorkie-team/yorkie/internal/validation"
 )
@@ -51,14 +53,23 @@ func (i *UpdatableProjectFields) Validate() error {
 }
 
 func init() {
-	validation.RegisterValidation("invalid_webhook_method", func(level validation.FieldLevel) bool {
-		methods := level.Field().Interface().([]string)
-		for _, method := range methods {
-			if !IsAuthMethod(method) {
-				return false
+	if err := validation.RegisterValidation(
+		"invalid_webhook_method",
+		func(level validation.FieldLevel) bool {
+			methods := level.Field().Interface().([]string)
+			for _, method := range methods {
+				if !IsAuthMethod(method) {
+					return false
+				}
 			}
-		}
-		return true
-	})
-	validation.RegisterTranslation("invalid_webhook_method", "given {0} is invalid method")
+			return true
+		},
+	); err != nil {
+		fmt.Fprintln(os.Stderr, "updatable project fields: ", err)
+		os.Exit(1)
+	}
+	if err := validation.RegisterTranslation("invalid_webhook_method", "given {0} is invalid method"); err != nil {
+		fmt.Fprintln(os.Stderr, "updatable project fields: ", err)
+		os.Exit(1)
+	}
 }
diff --git a/cmd/yorkie/config/config.go b/cmd/yorkie/config/config.go
index fadff71c..cf9b22b5 100644
--- a/cmd/yorkie/config/config.go
+++ b/cmd/yorkie/config/config.go
@@ -22,23 +22,28 @@ import (
 	"fmt"
 	"os"
 	"path"
+	"path/filepath"
 )
 
 // AdminAddr is the address of the admin server.
 var AdminAddr string
 
 // ensureYorkieDir ensures that the directory of Yorkie exists.
-func ensureYorkieDir() string {
+func ensureYorkieDir() (string, error) {
 	yorkieDir := path.Join(os.Getenv("HOME"), ".yorkie")
 	if err := os.MkdirAll(yorkieDir, 0700); err != nil {
-		panic(err)
+		return "", fmt.Errorf("mkdir: %w", err)
 	}
-	return yorkieDir
+	return yorkieDir, nil
 }
 
 // configPath returns the path of CLI.
-func configPath() string {
-	return path.Join(ensureYorkieDir(), "config.json")
+func configPath() (string, error) {
+	yorkieDir, err := ensureYorkieDir()
+	if err != nil {
+		return "", fmt.Errorf("ensure yorkie dir: %w", err)
+	}
+	return path.Join(yorkieDir, "config.json"), nil
 }
 
 // Config is the configuration of CLI.
@@ -58,15 +63,20 @@ func New() *Config {
 func LoadToken(addr string) (string, error) {
 	config, err := Load()
 	if err != nil {
-		return "", err
+		return "", fmt.Errorf("load token: %w", err)
 	}
-
 	return config.Auths[addr], nil
 }
 
 // Load loads the configuration from the given path.
 func Load() (*Config, error) {
-	file, err := os.Open(configPath())
+	configPathValue, err := configPath()
+	if err != nil {
+		fmt.Fprintln(os.Stderr, "get config path: %w", err)
+		os.Exit(1)
+	}
+
+	file, err := os.Open(filepath.Clean(configPathValue))
 	if err != nil {
 		if os.IsNotExist(err) {
 			return New(), nil
@@ -88,7 +98,13 @@ func Load() (*Config, error) {
 
 // Save saves the configuration to the given path.
 func Save(config *Config) error {
-	file, err := os.Create(configPath())
+	configPathValue, err := configPath()
+	if err != nil {
+		fmt.Fprintln(os.Stderr, "get config path: %w", err)
+		os.Exit(1)
+	}
+
+	file, err := os.Create(filepath.Clean(configPathValue))
 	if err != nil {
 		return fmt.Errorf("create config file: %w", err)
 	}
diff --git a/internal/validation/validation.go b/internal/validation/validation.go
index cdffb7c5..fbf4cbd0 100644
--- a/internal/validation/validation.go
+++ b/internal/validation/validation.go
@@ -19,6 +19,7 @@ package validation
 
 import (
 	"fmt"
+	"os"
 	"regexp"
 	"strings"
 
@@ -137,26 +138,33 @@ func (s StructError) Error() string {
 
 // RegisterValidation is shortcut of defaultValidator.RegisterValidation
 // that register custom validation with given tag, and it can be used in init.
-func RegisterValidation(tag string, fn validator.Func) {
+func RegisterValidation(tag string, fn validator.Func) error {
 	if err := defaultValidator.RegisterValidation(tag, fn); err != nil {
-		panic(err)
+		return fmt.Errorf("register validation: %w", err)
 	}
+	return nil
 }
 
 // RegisterTranslation is shortcut of defaultValidator.RegisterTranslation
 // that registers translations against the provided tag with given msg.
-func RegisterTranslation(tag, msg string) {
-	if err := defaultValidator.RegisterTranslation(tag, trans, func(ut ut.Translator) error {
-		if err := ut.Add(tag, msg, true); err != nil {
-			return fmt.Errorf("add translation: %w", err)
-		}
-		return nil
-	}, func(ut ut.Translator, fe validator.FieldError) string {
-		t, _ := ut.T(tag, fe.Field())
-		return t
-	}); err != nil {
-		panic(err)
+func RegisterTranslation(tag, msg string) error {
+	if err := defaultValidator.RegisterTranslation(
+		tag,
+		trans,
+		func(ut ut.Translator) error {
+			if err := ut.Add(tag, msg, true); err != nil {
+				return fmt.Errorf("register translation: %w", err)
+			}
+			return nil
+		},
+		func(ut ut.Translator, fe validator.FieldError) string {
+			t, _ := ut.T(tag, fe.Field())
+			return t
+		},
+	); err != nil {
+		return fmt.Errorf("register translation: %w", err)
 	}
+	return nil
 }
 
 // ValidateValue validates the value with the tag
@@ -170,7 +178,6 @@ func ValidateValue(v interface{}, tag string) error {
 			}
 		}
 	}
-
 	return nil
 }
 
@@ -193,11 +200,17 @@ func Validate(v string, tagOrRules []interface{}) error {
 			}
 
 			if value.Func != nil {
-				RegisterValidation(tag, value.Func)
+				if err := RegisterValidation(tag, value.Func); err != nil {
+					fmt.Fprintln(os.Stderr, "validation custom rule: %w", err)
+					os.Exit(1)
+				}
 			}
 
 			if value.Err != nil {
-				RegisterTranslation(tag, value.Err.Error())
+				if err := RegisterTranslation(tag, value.Err.Error()); err != nil {
+					fmt.Fprintln(os.Stderr, "validation custom rule: %w", err)
+					os.Exit(1)
+				}
 			}
 
 			sb.WriteString(tag)
@@ -227,38 +240,76 @@ func ValidateStruct(s interface{}) error {
 
 func init() {
 	if err := entranslations.RegisterDefaultTranslations(defaultValidator, trans); err != nil {
-		panic(err)
+		fmt.Fprintln(os.Stderr, "validation register default translations: %w", err)
+		os.Exit(1)
 	}
 
-	RegisterValidation("slug", func(level validator.FieldLevel) bool {
+	if err := RegisterValidation("slug", func(level validator.FieldLevel) bool {
 		val := level.Field().String()
 		return slugRegex.MatchString(val)
-	})
-	RegisterTranslation("slug", "{0} must only contain letters, numbers, hyphen, period, underscore, and tilde")
+	}); err != nil {
+		fmt.Fprintln(os.Stderr, "validation slug: %w", err)
+		os.Exit(1)
+	}
+	if err := RegisterTranslation(
+		"slug",
+		"{0} must only contain letters, numbers, hyphen, period, underscore, and tilde",
+	); err != nil {
+		fmt.Fprintln(os.Stderr, "validation slug: %w", err)
+		os.Exit(1)
+	}
 
-	RegisterValidation("case_sensitive_slug", func(level validator.FieldLevel) bool {
+	if err := RegisterValidation("case_sensitive_slug", func(level validator.FieldLevel) bool {
 		val := level.Field().String()
 		return caseSensitiveSlugRegex.MatchString(val)
-	})
-	RegisterTranslation(
+	}); err != nil {
+		fmt.Fprintln(os.Stderr, "validation case_sensitive_slug: %w", err)
+		os.Exit(1)
+	}
+	if err := RegisterTranslation(
 		"case_sensitive_slug",
-		"{0} must only contain case-sensitive letters, numbers, hyphen, period, underscore, and tilde")
+		"{0} must only contain case-sensitive letters, numbers, hyphen, period, underscore, and tilde",
+	); err != nil {
+		fmt.Fprintln(os.Stderr, "validation case_sensitive_slug: %w", err)
+		os.Exit(1)
+	}
 
-	RegisterValidation("alpha_num_special", func(level validator.FieldLevel) bool {
+	if err := RegisterValidation("alpha_num_special", func(level validator.FieldLevel) bool {
 		val := level.Field().String()
 		return hasAlphaNumSpecial(val)
-	})
-	RegisterTranslation("alpha_num_special", "{0} must include letters, numbers, and special characters")
+	}); err != nil {
+		fmt.Fprintln(os.Stderr, "validation alpha_num_special: %w", err)
+		os.Exit(1)
+	}
+	if err := RegisterTranslation(
+		"alpha_num_special",
+		"{0} must include letters, numbers, and special characters",
+	); err != nil {
+		fmt.Fprintln(os.Stderr, "validation alpha_num_special: %w", err)
+		os.Exit(1)
+	}
 
-	RegisterValidation("emptystring", func(level validator.FieldLevel) bool {
+	if err := RegisterValidation("emptystring", func(level validator.FieldLevel) bool {
 		val := level.Field().String()
 		return val == ""
-	})
-	RegisterTranslation("url|emptystring", "{0} must be a valid URL")
+	}); err != nil {
+		fmt.Fprintln(os.Stderr, "validation emptystring: %w", err)
+		os.Exit(1)
+	}
+	if err := RegisterTranslation("url|emptystring", "{0} must be a valid URL"); err != nil {
+		fmt.Fprintln(os.Stderr, "validation emptystring: %w", err)
+		os.Exit(1)
+	}
 
-	RegisterValidation("duration", func(level validator.FieldLevel) bool {
+	if err := RegisterValidation("duration", func(level validator.FieldLevel) bool {
 		val := level.Field().String()
 		return isValidTimeDurationStringFormat(val)
-	})
-	RegisterTranslation("duration", "{0} must be a valid time duration string format")
+	}); err != nil {
+		fmt.Fprintln(os.Stderr, "validation duration: %w", err)
+		os.Exit(1)
+	}
+	if err := RegisterTranslation("duration", "{0} must be a valid time duration string format"); err != nil {
+		fmt.Fprintln(os.Stderr, "validation duration: %w", err)
+		os.Exit(1)
+	}
 }
diff --git a/server/backend/config.go b/server/backend/config.go
index aa536061..038b861c 100644
--- a/server/backend/config.go
+++ b/server/backend/config.go
@@ -18,6 +18,7 @@ package backend
 
 import (
 	"fmt"
+	"os"
 	"time"
 )
 
@@ -114,7 +115,8 @@ func (c *Config) Validate() error {
 func (c *Config) ParseAdminTokenDuration() time.Duration {
 	result, err := time.ParseDuration(c.AdminTokenDuration)
 	if err != nil {
-		panic(err)
+		fmt.Fprintln(os.Stderr, "parse admin token duration: %w", err)
+		os.Exit(1)
 	}
 
 	return result
@@ -124,7 +126,8 @@ func (c *Config) ParseAdminTokenDuration() time.Duration {
 func (c *Config) ParseAuthWebhookMaxWaitInterval() time.Duration {
 	result, err := time.ParseDuration(c.AuthWebhookMaxWaitInterval)
 	if err != nil {
-		panic(err)
+		fmt.Fprintln(os.Stderr, "parse auth webhook max wait interval: %w", err)
+		os.Exit(1)
 	}
 
 	return result
@@ -134,7 +137,8 @@ func (c *Config) ParseAuthWebhookMaxWaitInterval() time.Duration {
 func (c *Config) ParseAuthWebhookCacheAuthTTL() time.Duration {
 	result, err := time.ParseDuration(c.AuthWebhookCacheAuthTTL)
 	if err != nil {
-		panic(err)
+		fmt.Fprintln(os.Stderr, "parse auth webhook cache auth ttl: %w", err)
+		os.Exit(1)
 	}
 
 	return result
@@ -144,7 +148,8 @@ func (c *Config) ParseAuthWebhookCacheAuthTTL() time.Duration {
 func (c *Config) ParseAuthWebhookCacheUnauthTTL() time.Duration {
 	result, err := time.ParseDuration(c.AuthWebhookCacheUnauthTTL)
 	if err != nil {
-		panic(err)
+		fmt.Fprintln(os.Stderr, "parse auth webhook cache unauth ttl: %w", err)
+		os.Exit(1)
 	}
 
 	return result
diff --git a/server/backend/database/mongo/config.go b/server/backend/database/mongo/config.go
index e975aa8d..58dca11b 100644
--- a/server/backend/database/mongo/config.go
+++ b/server/backend/database/mongo/config.go
@@ -18,6 +18,7 @@ package mongo
 
 import (
 	"fmt"
+	"os"
 	"time"
 )
 
@@ -54,7 +55,8 @@ func (c *Config) Validate() error {
 func (c *Config) ParseConnectionTimeout() time.Duration {
 	result, err := time.ParseDuration(c.ConnectionTimeout)
 	if err != nil {
-		panic(err)
+		fmt.Fprintln(os.Stderr, "parse connection timeout: %w", err)
+		os.Exit(1)
 	}
 
 	return result
@@ -64,7 +66,8 @@ func (c *Config) ParseConnectionTimeout() time.Duration {
 func (c *Config) ParsePingTimeout() time.Duration {
 	result, err := time.ParseDuration(c.PingTimeout)
 	if err != nil {
-		panic(err)
+		fmt.Fprintln(os.Stderr, "parse ping timeout: %w", err)
+		os.Exit(1)
 	}
 
 	return result


Test patch:
diff --git a/internal/validation/validation_test.go b/internal/validation/validation_test.go
index 589bdbd2..be513e21 100644
--- a/internal/validation/validation_test.go
+++ b/internal/validation/validation_test.go
@@ -67,13 +67,13 @@ func TestValidation(t *testing.T) {
 
 	t.Run("custom rule test", func(t *testing.T) {
 		// register custom rule tag and validation function
-		RegisterValidation("custom", func(v FieldLevel) bool {
+		_ = RegisterValidation("custom", func(v FieldLevel) bool {
 			return v.Field().String() == "custom"
 		})
 
 		// custom error message for custom rule
 		myError := errors.New("custom error")
-		RegisterTranslation("custom", myError.Error())
+		_ = RegisterTranslation("custom", myError.Error())
 
 		// validate value
 		err := ValidateValue("custom-invalid-value", "required,custom")