Skip to content

Commit

Permalink
fix it
Browse files Browse the repository at this point in the history
  • Loading branch information
nicksnyder committed Feb 4, 2024
1 parent 15b26d9 commit 97a55c3
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 40 deletions.
122 changes: 93 additions & 29 deletions i18n/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,47 +175,111 @@ func stringSubmap(k string, v interface{}, strdata map[string]string) error {
}
}

// isMessage tells whether the given data is a message, or a map containing
// nested messages.
// A map is assumed to be a message if it contains any of the "reserved" keys:
// "id", "description", "hash", "leftdelim", "rightdelim", "zero", "one", "two", "few", "many", "other"
// with a string value.
// e.g.,
var reservedKeys = map[string]struct{}{
"id": {},
"description": {},
"hash": {},
"leftdelim": {},
"rightdelim": {},
"zero": {},
"one": {},
"two": {},
"few": {},
"many": {},
"other": {},
"translation": {},
}

func isReserved(key string, val any) bool {
if _, ok := reservedKeys[key]; ok {
if key == "translation" {
return true
}
if _, ok := val.(string); ok {
return true
}
}
return false
}

// isMessage returns true if v contains only message keys and false if it contains no message keys.
// It returns an error if v contains both message and non-message keys.
// - {"message": {"description": "world"}} is a message
// - {"message": {"description": "world", "foo": "bar"}} is a message ("foo" key is ignored)
// - {"notmessage": {"description": {"hello": "world"}}} is not
// - {"notmessage": {"foo": "bar"}} is not
func isMessage(v interface{}) bool {
reservedKeys := []string{"id", "description", "hash", "leftdelim", "rightdelim", "zero", "one", "two", "few", "many", "other"}
// - {"error": {"description": "world", "foo": "bar"}} is an error
// - {"notmessage": {"description": {"hello": "world"}}} is not a message
// - {"notmessage": {"foo": "bar"}} is not a message
func isMessage(v interface{}) (bool, error) {
switch data := v.(type) {
case string:
return true
return true, nil
case map[string]interface{}:
for _, key := range reservedKeys {
reservedKeyCount := 0
for key := range reservedKeys {
val, ok := data[key]
if !ok {
continue
if ok && isReserved(key, val) {
reservedKeyCount++
}
_, ok = val.(string)
if !ok {
continue
}
if reservedKeyCount == 0 {
return false, nil
}
if len(data) > reservedKeyCount {
reservedKeys := make([]string, 0, reservedKeyCount)
unreservedKeys := make([]string, 0, len(data)-reservedKeyCount)
for k, v := range data {
if isReserved(k, v) {
reservedKeys = append(reservedKeys, k)
} else {
unreservedKeys = append(unreservedKeys, k)
}
}
// return false, fmt.Errorf("reserved keys %v mixed with unreserved keys %v", reservedKeys, unreservedKeys)
return false, &mixedKeysError{
reservedKeys: reservedKeys,
unreservedKeys: unreservedKeys,
}
// v is a message if it contains a "reserved" key holding a string value
return true
}
return true, nil
case map[interface{}]interface{}:
for _, key := range reservedKeys {
reservedKeyCount := 0
for key := range reservedKeys {
val, ok := data[key]
if !ok {
continue
if ok && isReserved(key, val) {
reservedKeyCount++
}
_, ok = val.(string)
if !ok {
continue
}
if reservedKeyCount == 0 {
return false, nil
}
if len(data) > reservedKeyCount {
reservedKeys := make([]string, 0, reservedKeyCount)
unreservedKeys := make([]string, 0, len(data)-reservedKeyCount)
for key, v := range data {
k, ok := key.(string)
if !ok {
unreservedKeys = append(unreservedKeys, fmt.Sprintf("%+v", k))
} else if isReserved(k, v) {
reservedKeys = append(reservedKeys, k)
} else {
unreservedKeys = append(unreservedKeys, k)
}

Check warning on line 265 in i18n/message.go

View check run for this annotation

Codecov / codecov/patch

i18n/message.go#L255-L265

Added lines #L255 - L265 were not covered by tests
}
// return false, fmt.Errorf("reserved keys %v mixed with unreserved keys %v", reservedKeys, unreservedKeys)
return false, &mixedKeysError{
reservedKeys: reservedKeys,
unreservedKeys: unreservedKeys,

Check warning on line 270 in i18n/message.go

View check run for this annotation

Codecov / codecov/patch

i18n/message.go#L268-L270

Added lines #L268 - L270 were not covered by tests
}
// v is a message if it contains a "reserved" key holding a string value
return true
}
return true, nil
}
return false
return false, nil
}

type mixedKeysError struct {
reservedKeys []string
unreservedKeys []string
}

func (e *mixedKeysError) Error() string {
return fmt.Sprintf("reserved keys %v mixed with unreserved keys %v", e.reservedKeys, e.unreservedKeys)
}
18 changes: 15 additions & 3 deletions i18n/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ func ParseMessageFileBytes(buf []byte, path string, unmarshalFuncs map[string]Un
return nil, err
}

if messageFile.Messages, err = recGetMessages(raw, isMessage(raw), true); err != nil {
m, err := isMessage(raw)
if err != nil {
return nil, err
}

Check warning on line 49 in i18n/parse.go

View check run for this annotation

Codecov / codecov/patch

i18n/parse.go#L48-L49

Added lines #L48 - L49 were not covered by tests

if messageFile.Messages, err = recGetMessages(raw, m, true); err != nil {
return nil, err
}

Expand Down Expand Up @@ -105,7 +110,11 @@ func recGetMessages(raw interface{}, isMapMessage, isInitialCall bool) ([]*Messa
messages = make([]*Message, 0, len(data))
for _, data := range data {
// recursively scan slice items
childMessages, err := recGetMessages(data, isMessage(data), false)
m, err := isMessage(data)
if err != nil {
return nil, err
}

Check warning on line 116 in i18n/parse.go

View check run for this annotation

Codecov / codecov/patch

i18n/parse.go#L115-L116

Added lines #L115 - L116 were not covered by tests
childMessages, err := recGetMessages(data, m, false)
if err != nil {
return nil, err
}
Expand All @@ -120,7 +129,10 @@ func recGetMessages(raw interface{}, isMapMessage, isInitialCall bool) ([]*Messa
}

func addChildMessages(id string, data interface{}, messages []*Message) ([]*Message, error) {
isChildMessage := isMessage(data)
isChildMessage, err := isMessage(data)
if err != nil {
return nil, err
}
childMessages, err := recGetMessages(data, isChildMessage, false)
if err != nil {
return nil, err
Expand Down
40 changes: 32 additions & 8 deletions i18n/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,35 @@ func TestParseMessageFileBytes(t *testing.T) {
}},
},
},
// {
// name: "basic test",
// file: `{"id": "hello", "other": "world", "foo": "bar"}`,
// path: "en.json",
// messageFile: &MessageFile{
// Path: "en.json",
// Tag: language.English,
// Format: "json",
// Messages: []*Message{{
// ID: "hello",
// Other: "world",
// }},
// },
// },
// {
// name: "basic test reserved key top level",
// file: `{"other": "world"}`,
// path: "en.json",
// messageFile: &MessageFile{
// Path: "en.json",
// Tag: language.English,
// Format: "json",
// Messages: []*Message{{
// ID: "other",
// Other: "world",
// }},
// },
// },

{
name: "basic test with dot separator in key",
file: `{"prepended.hello": "world"}`,
Expand Down Expand Up @@ -111,14 +140,9 @@ func TestParseMessageFileBytes(t *testing.T) {
name: "basic test with description and dummy",
file: `{"notnested": {"description": "world", "dummy": "nothing"}}`,
path: "en.json",
messageFile: &MessageFile{
Path: "en.json",
Tag: language.English,
Format: "json",
Messages: []*Message{{
ID: "notnested",
Description: "world",
}},
err: &mixedKeysError{
reservedKeys: []string{"description"},
unreservedKeys: []string{"dummy"},
},
},
{
Expand Down

0 comments on commit 97a55c3

Please sign in to comment.