Browse Source

go-i18n: Prepared i18n errors with go-18n. Need to convert calls.

Frederic G. MARAND 5 years ago
parent
commit
c5b86d67d8

+ 13 - 0
domain/active.en.yaml

@@ -0,0 +1,13 @@
+error.none: No error
+error.target_invalid: Target invalid
+error.unimplemented: Not yet implemented
+short_not_created: Short URL not created for a new target
+short_not_found: Short URL not defined
+storage_read: Storage read error
+storage_unspecified: Storage unspecified error
+storage_write: Storage write error
+target_blocked: Target blocked
+target_censored: Target unavailable for legal reasons
+web.error.format:
+  description: The format for errors
+  one: '{.Kind}: {.Detail}'

+ 6 - 2
domain/domain_api.go

@@ -1,5 +1,9 @@
 package domain
 
+import (
+	"github.com/nicksnyder/go-i18n/v2/i18n"
+)
+
 func GetTargetURL(shortURL string) (target string, err error) {
 	su := ShortURL{URL: URL(shortURL)}
 	tu, err := shortURLRepository.GetTarget(su)
@@ -12,11 +16,11 @@ func GetTargetURL(shortURL string) (target string, err error) {
 	return
 }
 
-func GetShortURL(targetURL string) (short string, isNew bool, err error) {
+func GetShortURL(targetURL string, localizer *i18n.Localizer) (short string, isNew bool, err error) {
 	tu := TargetURL{URL: URL(targetURL)}
 	if tu.IsEmpty() {
 		// Zero values are OK for short and isNew.
-		err = MakeError(TargetInvalidError, "empty URL")
+		err = MakeError(localizer, TargetInvalid.ID, "empty URL")
 		return
 	}
 	su, isNew, err := targetURLRepository.GetShort(tu)

+ 4 - 4
domain/domain_api_test.go

@@ -37,14 +37,14 @@ func TestGetTargetURL(t *testing.T) {
 func TestGetShortURL(t *testing.T) {
 	// No repo should be able to return a short for an empty target.
 	RegisterRepositories(nil, nil)
-	_, _, err := GetShortURL("")
+	_, _, err := GetShortURL("", nil)
 	if err == nil {
 		t.Error("empty repository should fail to get a short for an empty URL")
 	}
 
 	// Empty repos unable to create shorts should not find any short.
 	RegisterRepositories(nil, MakeMockTargetRepo(false))
-	_, isNew, err := GetShortURL("some_target")
+	_, isNew, err := GetShortURL("some_target", nil)
 	if err == nil {
 		t.Error("empty repository should fail to get a short")
 	}
@@ -55,7 +55,7 @@ func TestGetShortURL(t *testing.T) {
 	// Empty repos able to create shorts should return a short for a valid target.
 	RegisterRepositories(nil, MakeMockTargetRepo(true))
 	const targetURL = "some_target"
-	actual, isNew, err := GetShortURL(targetURL)
+	actual, isNew, err := GetShortURL(targetURL, nil)
 	if err != nil {
 		t.Error("repository with create ability should not fail to create a short")
 	}
@@ -71,7 +71,7 @@ func TestGetShortURL(t *testing.T) {
 	}
 
 	// Repos with a matching short for a target should return it as non-new.
-	actual, isNew, err = GetShortURL(targetURL)
+	actual, isNew, err = GetShortURL(targetURL, nil)
 	if err != nil {
 		t.Error("repository with matching short should return it")
 	}

+ 53 - 33
domain/errors.go

@@ -2,54 +2,74 @@ package domain
 
 import (
 	"errors"
-	"fmt"
-)
-
-type ErrorKind int
-
-const (
-	NoError ErrorKind = iota
-	Unimplemented
-
-	ShortNotCreated
-	ShortNotFound
 
-	TargetBlockedError
-	TargetCensoredError
-	TargetInvalidError
-
-	StorageReadError
-	StorageWriteError
-	StorageUnspecifiedError
+	"github.com/nicksnyder/go-i18n/v2/i18n"
 )
 
-var Errors = map[ErrorKind]string{
-	NoError:       "No error",
-	Unimplemented: "Not yet implemented",
-
-	ShortNotCreated: "Short URL not created for a new target",
-	ShortNotFound:   "Short URL not defined",
+type ErrorKind = string
 
-	TargetBlockedError:  "Target blocked",
-	TargetCensoredError: "Target unavailable for legal reasons",
-	TargetInvalidError:  "Target invalid",
+var NoError = i18n.Message{ID: "error.none", Other: "No error"}
+var Unimplemented = i18n.Message{ID: "error.unimplemented", Other: "Not yet implemented"}
+var ShortNotCreated = i18n.Message{ID: "short_not_created", Other: "Short URL not created for a new target"}
+var TargetInvalid = i18n.Message{ID: "error.target_invalid", Other: "Target invalid"}
+var ShortNotFound = i18n.Message{ID: "short_not_found", Other: "Short URL not defined"}
+var TargetBlocked = i18n.Message{ID: "target_blocked", Other: "Target blocked"}
+var TargetCensored = i18n.Message{ID: "target_censored", Other: "Target unavailable for legal reasons"}
+var StorageRead = i18n.Message{ID: "storage_read", Other: "Storage read error"}
+var StorageWrite = i18n.Message{ID: "storage_write", Other: "Storage write error"}
+var StorageUnspecified = i18n.Message{ID: "storage_unspecified", Other: "Storage unspecified error"}
 
-	StorageReadError:        "Storage read error",
-	StorageWriteError:       "Storage write error",
-	StorageUnspecifiedError: "Storage unspecified error",
+var ErrorMessages = map[ErrorKind]i18n.Message{
+	NoError.ID:            NoError,
+	Unimplemented.ID:      Unimplemented,
+	ShortNotCreated.ID:    ShortNotCreated,
+	ShortNotFound.ID:      ShortNotFound,
+	TargetBlocked.ID:      TargetBlocked,
+	TargetCensored.ID:     TargetCensored,
+	StorageRead.ID:        StorageRead,
+	StorageWrite.ID:       StorageWrite,
+	StorageUnspecified.ID: StorageUnspecified,
 }
 
+// Error instances are created localized by MakeError(kind, detail, localizer),
+// so they do not need to support localization themselves.
 type Error struct {
 	error
 	Kind ErrorKind
 }
 
-func MakeError(kind ErrorKind, detail string) Error {
+// MakeError creates a localized error instance, ready to print.
+func MakeError(l *i18n.Localizer, kind ErrorKind, detail string) Error {
+	format := i18n.Message{
+		ID:          "web.error.format",
+		Description: "The format for errors",
+		One:         "{.Kind}: {.Detail}",
+	}
 	var message string
+
 	if len(detail) == 0 {
-		message = Errors[kind]
+		message = l.MustLocalize(&i18n.LocalizeConfig{
+			MessageID: ErrorMessages[kind].ID,
+		})
 	} else {
-		message = fmt.Sprintf("%s: %s", Errors[kind], detail)
+		em, ok := ErrorMessages[kind]
+		if !ok {
+			em = i18n.Message{
+				ID: "error.dynamic",
+				Description: "This is an unplanned error generated on the fly from details. It should be replaced",
+			}
+		}
+		localizedKind := l.MustLocalize(&i18n.LocalizeConfig{
+			DefaultMessage: &em,
+			MessageID:      em.ID,
+		})
+		message = l.MustLocalize(&i18n.LocalizeConfig{
+			DefaultMessage: &format,
+			TemplateData: map[string]string{
+				"Kind":   localizedKind,
+				"Detail": detail,
+			},
+		})
 	}
 
 	return Error{

+ 1 - 1
domain/short_url.go

@@ -20,7 +20,7 @@ MakeShortURL creates a ShortURL instance from a given RedirectURL/target pair.
 func NewUnspecifiedShortURL(target TargetURL) (ShortURL, error) {
 	var su ShortURL
 	if target.IsEmpty() {
-		return su, MakeError(TargetInvalidError, "empty URL")
+		return su, MakeError(TargetInvalid, "empty URL")
 	}
 
 	// FIXME: will cause collisions.

+ 3 - 1
go.mod

@@ -6,6 +6,7 @@ require (
 	github.com/gorilla/mux v1.6.2
 	github.com/gorilla/sessions v1.1.3
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
+	github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.6 // indirect
 	github.com/pressly/goose v2.4.3+incompatible
 	github.com/spf13/cast v1.3.0
 	github.com/spf13/cobra v0.0.3
@@ -13,5 +14,6 @@ require (
 	github.com/spf13/viper v1.2.1
 	golang.org/x/text v0.3.0
 	google.golang.org/appengine v1.3.0 // indirect
-	gopkg.in/yaml.v2 v2.2.1
+	gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
+	gopkg.in/yaml.v2 v2.2.2
 )

+ 6 - 0
go.sum

@@ -1,3 +1,4 @@
+github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -23,6 +24,8 @@ github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDe
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I=
 github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.6 h1:SooTCzUOOs899x/M7gmSS+dgL+AUfSWqAcHLN3auCic=
+github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.6/go.mod h1:4Opqa6/HIv0lhG3WRAkqzO0afezkRhxXI0P8EJkqeRU=
 github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -49,11 +52,14 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 h1:BH3eQWeGbwRU2+wxxuuPOdFBmaiBH81O8BugSjHeTFg=
 golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.0.0-20171214130843-f21a4dfb5e38/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
 google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

+ 3 - 3
infrastructure/memory_test.go

@@ -17,14 +17,14 @@ func TestMemoryEmptyRepo(test *testing.T) {
 		test.Error("Empty repository should not find a target for any URL")
 	}
 
-	s1, isNew, err := domain.GetShortURL(exampleValidHTTPURL)
+	s1, isNew, err := domain.GetShortURL(exampleValidHTTPURL, nil)
 	if err != nil {
 		test.Error("Creating a short URL for a valid URL should not fail")
 	}
 	if !isNew {
 		test.Error("The first short URL in an empty repository should be new")
 	}
-	s2, isNew, err := domain.GetShortURL(exampleValidHTTPURL)
+	s2, isNew, err := domain.GetShortURL(exampleValidHTTPURL, nil)
 	if err != nil {
 		test.Error("Creating a short URL for a valid URL should not fail")
 	}
@@ -51,7 +51,7 @@ func TestMemorySad(test *testing.T) {
 		MemoryTargetURLRepository{},
 	)
 
-	_, _, err := domain.GetShortURL("")
+	_, _, err := domain.GetShortURL("", nil)
 	if err == nil {
 		test.Error("Empty target URL has no valid short URL")
 	}

+ 3 - 3
infrastructure/mysql_test.go

@@ -20,14 +20,14 @@ func TestMySQLEmptyRepo(test *testing.T) {
 			subTest.Error("Empty repository should not find a target for any URL")
 		}
 
-		s1, isNew, err := domain.GetShortURL(exampleValidHTTPURL)
+		s1, isNew, err := domain.GetShortURL(exampleValidHTTPURL, nil)
 		if err != nil {
 			subTest.Error("Creating a short URL for a valid URL should not fail")
 		}
 		if !isNew {
 			subTest.Error("The first short URL in an empty repository should be new")
 		}
-		s2, isNew, err := domain.GetShortURL(exampleValidHTTPURL)
+		s2, isNew, err := domain.GetShortURL(exampleValidHTTPURL, nil)
 		if err != nil {
 			subTest.Error("Creating a short URL for a valid URL should not fail")
 		}
@@ -56,7 +56,7 @@ func TestMySQLSad(test *testing.T) {
 	defer db.Close()
 
 	test.Run("Sad", func(subTest *testing.T) {
-		_, _, err := domain.GetShortURL("")
+		_, _, err := domain.GetShortURL("", nil)
 		if err == nil {
 			test.Error("Empty target URL has no valid short URL")
 		}

+ 2 - 2
web/api/post.go

@@ -36,7 +36,7 @@ func handlePostTarget(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	shortString, isNew, err := domain.GetShortURL(target.Target)
+	shortString, isNew, err := domain.GetShortURL(target.Target, nil)
 
 	w.Header().Set("Content-type", postContentType)
 
@@ -44,7 +44,7 @@ func handlePostTarget(w http.ResponseWriter, r *http.Request) {
 		domainErr, ok := err.(domain.Error)
 		if ok {
 			switch domainErr.Kind {
-			case domain.TargetInvalidError:
+			case domain.TargetInvalid:
 				w.WriteHeader(http.StatusBadRequest)
 				w.Header().Set("Content-Type", JsonTypeHeader)
 				w.Write(jsonFromString(`{ error: "Invalid target requested"}`))

+ 2 - 2
web/ui/post_target.go

@@ -41,7 +41,7 @@ func handlePostTarget(w http.ResponseWriter, r *http.Request, router *mux.Router
 		return
 	}
 
-	short, isNew, err := domain.GetShortURL(target)
+	short, isNew, err := domain.GetShortURL(target, nil)
 	if err != nil {
 		w.WriteHeader(http.StatusInternalServerError)
 		return
@@ -89,7 +89,7 @@ func handlePostTarget(w http.ResponseWriter, r *http.Request, router *mux.Router
 
 func validateTarget(raw string) (string, error) {
 	if raw == "" {
-		return "", domain.MakeError(domain.TargetInvalidError, "empty target")
+		return "", domain.MakeError(domain.TargetInvalid, "empty target")
 	}
 	// BUG(fgm): needs much more validation, starting with XSS.
 	return raw, nil