diff --git a/tags.go b/tags.go index 7719e85..41707ed 100644 --- a/tags.go +++ b/tags.go @@ -9,13 +9,34 @@ import ( ) var ( - errTagSyntax = errors.New("bad syntax for struct tag pair") - errTagKeySyntax = errors.New("bad syntax for struct tag key") - errTagValueSyntax = errors.New("bad syntax for struct tag value") + // ErrTagSyntax is returned when a struct tag pair has invalid syntax. + ErrTagSyntax = errors.New("bad syntax for struct tag pair") - errKeyNotSet = errors.New("tag key does not exist") - errTagNotExist = errors.New("tag does not exist") - errTagKeyMismatch = errors.New("mismatch between key and tag.key") + // ErrTagKeySyntax is returned when a struct tag key has invalid syntax. + ErrTagKeySyntax = errors.New("bad syntax for struct tag key") + + // ErrTagValueSyntax is returned when a struct tag value has invalid syntax. + ErrTagValueSyntax = errors.New("bad syntax for struct tag value") + + // ErrKeyNotExist is returned when a tag key is empty or not set. + ErrKeyNotExist = errors.New("key does not exist") + + // ErrKeyNotSet is an alias for ErrKeyNotExist for backward compatibility. + ErrKeyNotSet = ErrKeyNotExist + + // ErrTagNotExist is returned when a requested tag does not exist. + ErrTagNotExist = errors.New("tag does not exist") + + // ErrTagKeyMismatch is returned when there is a mismatch between key and tag.key. + ErrTagKeyMismatch = errors.New("mismatch between key and tag.key") + + errTagSyntax = ErrTagSyntax + errTagKeySyntax = ErrTagKeySyntax + errTagValueSyntax = ErrTagValueSyntax + errKeyNotSet = ErrKeyNotExist + errKeyNotExist = ErrKeyNotExist + errTagNotExist = ErrTagNotExist + errTagKeyMismatch = ErrTagKeyMismatch ) // Tags represent a set of tags from a single struct field @@ -69,13 +90,13 @@ func Parse(tag string) (*Tags, error) { } if i == 0 { - return nil, errTagKeySyntax + return nil, ErrTagKeySyntax } if i+1 >= len(tag) || tag[i] != ':' { - return nil, errTagSyntax + return nil, ErrTagSyntax } if tag[i+1] != '"' { - return nil, errTagValueSyntax + return nil, ErrTagValueSyntax } key := tag[:i] @@ -90,7 +111,7 @@ func Parse(tag string) (*Tags, error) { i++ } if i >= len(tag) { - return nil, errTagValueSyntax + return nil, ErrTagValueSyntax } qvalue := tag[:i+1] @@ -98,7 +119,7 @@ func Parse(tag string) (*Tags, error) { value, err := strconv.Unquote(qvalue) if err != nil { - return nil, errTagValueSyntax + return nil, ErrTagValueSyntax } res := strings.Split(value, ",") @@ -135,13 +156,13 @@ func (t *Tags) Get(key string) (*Tag, error) { } } - return nil, errTagNotExist + return nil, ErrTagNotExist } // Set sets the given tag. If the tag key already exists it'll override it func (t *Tags) Set(tag *Tag) error { if tag.Key == "" { - return errKeyNotSet + return ErrKeyNotExist } added := false diff --git a/tags_test.go b/tags_test.go index 1dc7810..497c10c 100644 --- a/tags_test.go +++ b/tags_test.go @@ -1,6 +1,7 @@ package structtag import ( + "errors" "reflect" "sort" "strings" @@ -422,3 +423,113 @@ func TestTags_Sort(t *testing.T) { t.Errorf("string\n\twant: %#v\n\tgot : %#v", want, tags.String()) } } + +func TestTags_Get_TagNotExist(t *testing.T) { + tag := `json:"foo,omitempty" structs:"bar,omitnested"` + + tags, err := Parse(tag) + if err != nil { + t.Fatal(err) + } + + found, err := tags.Get("nonexistent") + if found != nil { + t.Fatalf("expected nil tag for nonexistent key, got: %v", found) + } + if err == nil { + t.Fatal("expected error for nonexistent key, got nil") + } + + if !errors.Is(err, ErrTagNotExist) { + t.Errorf("errors.Is(err, ErrTagNotExist)\n\twant: true\n\tgot : false (err: %v)", err) + } + + // Verify backward-compatible unexported alias. + if !errors.Is(err, errTagNotExist) { + t.Errorf("errors.Is(err, errTagNotExist)\n\twant: true\n\tgot : false (err: %v)", err) + } + + if errors.Is(err, ErrKeyNotExist) { + t.Errorf("errors.Is(err, ErrKeyNotExist)\n\twant: false\n\tgot : true") + } +} + +func TestTags_Set_KeyNotExist(t *testing.T) { + tag := `json:"foo,omitempty"` + + tags, err := Parse(tag) + if err != nil { + t.Fatal(err) + } + + err = tags.Set(&Tag{ + Key: "", + Name: "bar", + }) + if err == nil { + t.Fatal("setting tag with empty key should error") + } + + if !errors.Is(err, ErrKeyNotExist) { + t.Errorf("errors.Is(err, ErrKeyNotExist)\n\twant: true\n\tgot : false (err: %v)", err) + } + + // Verify backward-compatible aliases. + if !errors.Is(err, ErrKeyNotSet) { + t.Errorf("errors.Is(err, ErrKeyNotSet)\n\twant: true\n\tgot : false (err: %v)", err) + } + if !errors.Is(err, errKeyNotSet) { + t.Errorf("errors.Is(err, errKeyNotSet)\n\twant: true\n\tgot : false (err: %v)", err) + } + if !errors.Is(err, errKeyNotExist) { + t.Errorf("errors.Is(err, errKeyNotExist)\n\twant: true\n\tgot : false (err: %v)", err) + } + + if errors.Is(err, ErrTagNotExist) { + t.Errorf("errors.Is(err, ErrTagNotExist)\n\twant: false\n\tgot : true") + } +} + +func TestSentinelErrors(t *testing.T) { + t.Run("ParseSyntaxErrors", func(t *testing.T) { + testCases := []struct { + name string + tag string + expected error + }{ + { + name: "ErrTagKeySyntax", + tag: `:"foo"`, + expected: ErrTagKeySyntax, + }, + { + name: "ErrTagSyntax", + tag: "json:", + expected: ErrTagSyntax, + }, + { + name: "ErrTagValueSyntax_Unclosed", + tag: `json:"`, + expected: ErrTagValueSyntax, + }, + { + name: "ErrTagValueSyntax_NoQuotes", + tag: "json:foo", + expected: ErrTagValueSyntax, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := Parse(tc.tag) + if err == nil { + t.Fatalf("expected error for tag %q, got nil", tc.tag) + } + if !errors.Is(err, tc.expected) { + t.Errorf("errors.Is(err, %v)\n\twant: true\n\tgot : false (err: %v)", tc.expected, err) + } + }) + } + }) +} +