diff --git a/rule/struct_tag.go b/rule/struct_tag.go index 00a2b96..d35058c 100644 --- a/rule/struct_tag.go +++ b/rule/struct_tag.go @@ -104,6 +104,7 @@ const ( keyJSON = "json" keyProtobuf = "protobuf" keyRequired = "required" + keyURL = "url" keyXML = "xml" keyYAML = "yaml" ) @@ -198,6 +199,11 @@ func (w lintStructTagRule) checkTaggedField(f *ast.Field) { if tag.Name != "true" && tag.Name != "false" { w.addFailure(f.Tag, "required should be 'true' or 'false'") } + case keyURL: + msg, ok := w.checkURLTag(tag.Options) + if !ok { + w.addFailure(f.Tag, msg) + } case keyXML: msg, ok := w.checkXMLTag(tag.Options) if !ok { @@ -329,6 +335,29 @@ func (w lintStructTagRule) checkYAMLTag(options []string) (string, bool) { return "", true } +func (w lintStructTagRule) checkURLTag(options []string) (string, bool) { + var delimiter = "" + for _, opt := range options { + switch opt { + case "int", "omitempty", "numbered", "brackets": + case "unix", "unixmilli", "unixnano": // TODO : check that the field is of type time.Time + case "comma", "semicolon", "space": + if delimiter == "" { + delimiter = opt + continue + } + return fmt.Sprintf("can not set both '%s' and '%s' as delimiters in URL tag", opt, delimiter), false + default: + if w.isUserDefined(keyURL, opt) { + continue + } + return fmt.Sprintf("unknown option '%s' in URL tag", opt), false + } + } + + return "", true +} + func (lintStructTagRule) typeValueMatch(t ast.Expr, val string) bool { tID, ok := t.(*ast.Ident) if !ok { diff --git a/test/struct_tag_test.go b/test/struct_tag_test.go index 5593635..9ce43e6 100644 --- a/test/struct_tag_test.go +++ b/test/struct_tag_test.go @@ -13,7 +13,7 @@ func TestStructTag(t *testing.T) { func TestStructTagWithUserOptions(t *testing.T) { testRule(t, "struct_tag_user_options", &rule.StructTagRule{}, &lint.RuleConfig{ - Arguments: []any{"json,inline,outline", "bson,gnu"}, + Arguments: []any{"json,inline,outline", "bson,gnu", "url,myURLOption"}, }) } diff --git a/testdata/struct_tag.go b/testdata/struct_tag.go index 1c27597..47fb092 100644 --- a/testdata/struct_tag.go +++ b/testdata/struct_tag.go @@ -123,3 +123,16 @@ type Simple struct { XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } + +type RequestQueryOption struct { + Properties []string `url:"properties,comma,omitempty"` + CustomProperties []string `url:"-"` + Associations []string `url:"associations,brackets,omitempty"` + Associations2 []string `url:"associations2,semicolon,omitempty"` + Associations3 []string `url:"associations3,space,brackets,omitempty"` + Associations4 []string `url:"associations4,numbered,omitempty"` + Associations5 []string `url:"associations5,space,semicolon,omitempty"` // MATCH /can not set both 'semicolon' and 'space' as delimiters in URL tag/ + PaginateAssociations bool `url:"paginateAssociations,int,omitempty"` + Archived bool `url:"archived,myURLOption"` // MATCH /unknown option 'myURLOption' in URL tag/ + IDProperty string `url:"idProperty,omitempty"` +} diff --git a/testdata/struct_tag_user_options.go b/testdata/struct_tag_user_options.go index 0b48c4a..31bdbfc 100644 --- a/testdata/struct_tag_user_options.go +++ b/testdata/struct_tag_user_options.go @@ -13,3 +13,9 @@ type RangeAllocation struct { Range string `bson:"range,flow"` // MATCH /unknown option 'flow' in BSON tag/ Data []byte `bson:"data,inline"` } + +type RequestQueryOptions struct { + Properties []string `url:"properties,commmma,omitempty"` // MATCH /unknown option 'commmma' in URL tag/ + CustomProperties []string `url:"-"` + Archived bool `url:"archived,myURLOption"` +}