mirror of
https://github.com/mgechev/revive.git
synced 2025-11-27 22:18:41 +02:00
struct-tag: change reported messages and refactor implementation (#1292)
This commit is contained in:
@@ -13,7 +13,62 @@ import (
|
|||||||
|
|
||||||
// StructTagRule lints struct tags.
|
// StructTagRule lints struct tags.
|
||||||
type StructTagRule struct {
|
type StructTagRule struct {
|
||||||
userDefined map[string][]string // map: key -> []option
|
userDefined map[tagKey][]string // map: key -> []option
|
||||||
|
}
|
||||||
|
|
||||||
|
type tagKey string
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyASN1 tagKey = "asn1"
|
||||||
|
keyBSON tagKey = "bson"
|
||||||
|
keyDatastore tagKey = "datastore"
|
||||||
|
keyDefault tagKey = "default"
|
||||||
|
keyJSON tagKey = "json"
|
||||||
|
keyMapstructure tagKey = "mapstructure"
|
||||||
|
keyProperties tagKey = "properties"
|
||||||
|
keyProtobuf tagKey = "protobuf"
|
||||||
|
keyRequired tagKey = "required"
|
||||||
|
keyTOML tagKey = "toml"
|
||||||
|
keyURL tagKey = "url"
|
||||||
|
keyValidate tagKey = "validate"
|
||||||
|
keyXML tagKey = "xml"
|
||||||
|
keyYAML tagKey = "yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tagChecker func(checkCtx *checkContext, tag *structtag.Tag, fieldType ast.Expr) (message string, succeeded bool)
|
||||||
|
|
||||||
|
// populate tag checkers map
|
||||||
|
var tagCheckers = map[tagKey]tagChecker{
|
||||||
|
keyASN1: checkASN1Tag,
|
||||||
|
keyBSON: checkBSONTag,
|
||||||
|
keyDatastore: checkDatastoreTag,
|
||||||
|
keyDefault: checkDefaultTag,
|
||||||
|
keyJSON: checkJSONTag,
|
||||||
|
keyMapstructure: checkMapstructureTag,
|
||||||
|
keyProperties: checkPropertiesTag,
|
||||||
|
keyProtobuf: checkProtobufTag,
|
||||||
|
keyRequired: checkRequiredTag,
|
||||||
|
keyTOML: checkTOMLTag,
|
||||||
|
keyURL: checkURLTag,
|
||||||
|
keyValidate: checkValidateTag,
|
||||||
|
keyXML: checkXMLTag,
|
||||||
|
keyYAML: checkYAMLTag,
|
||||||
|
}
|
||||||
|
|
||||||
|
type checkContext struct {
|
||||||
|
userDefined map[tagKey][]string // map: key -> []option
|
||||||
|
usedTagNbr map[int]bool // list of used tag numbers
|
||||||
|
usedTagName map[string]bool // list of used tag keys
|
||||||
|
isAtLeastGo124 bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (checkCtx checkContext) isUserDefined(key tagKey, opt string) bool {
|
||||||
|
if checkCtx.userDefined == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
options := checkCtx.userDefined[key]
|
||||||
|
return slices.Contains(options, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure validates the rule configuration, and configures the rule accordingly.
|
// Configure validates the rule configuration, and configures the rule accordingly.
|
||||||
@@ -28,7 +83,8 @@ func (r *StructTagRule) Configure(arguments lint.Arguments) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.userDefined = make(map[string][]string, len(arguments))
|
|
||||||
|
r.userDefined = make(map[tagKey][]string, len(arguments))
|
||||||
for _, arg := range arguments {
|
for _, arg := range arguments {
|
||||||
item, ok := arg.(string)
|
item, ok := arg.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -38,12 +94,13 @@ func (r *StructTagRule) Configure(arguments lint.Arguments) error {
|
|||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
return fmt.Errorf("invalid argument to the %s rule. Expecting a string of the form key[,option]+, got %s", r.Name(), item)
|
return fmt.Errorf("invalid argument to the %s rule. Expecting a string of the form key[,option]+, got %s", r.Name(), item)
|
||||||
}
|
}
|
||||||
key := strings.TrimSpace(parts[0])
|
key := tagKey(strings.TrimSpace(parts[0]))
|
||||||
for i := 1; i < len(parts); i++ {
|
for i := 1; i < len(parts); i++ {
|
||||||
option := strings.TrimSpace(parts[i])
|
option := strings.TrimSpace(parts[i])
|
||||||
r.userDefined[key] = append(r.userDefined[key], option)
|
r.userDefined[key] = append(r.userDefined[key], option)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +115,7 @@ func (r *StructTagRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure
|
|||||||
onFailure: onFailure,
|
onFailure: onFailure,
|
||||||
userDefined: r.userDefined,
|
userDefined: r.userDefined,
|
||||||
isAtLeastGo124: file.Pkg.IsAtLeastGoVersion(lint.Go124),
|
isAtLeastGo124: file.Pkg.IsAtLeastGoVersion(lint.Go124),
|
||||||
|
tagCheckers: tagCheckers,
|
||||||
}
|
}
|
||||||
|
|
||||||
ast.Walk(w, file.AST)
|
ast.Walk(w, file.AST)
|
||||||
@@ -72,10 +130,9 @@ func (*StructTagRule) Name() string {
|
|||||||
|
|
||||||
type lintStructTagRule struct {
|
type lintStructTagRule struct {
|
||||||
onFailure func(lint.Failure)
|
onFailure func(lint.Failure)
|
||||||
userDefined map[string][]string // map: key -> []option
|
userDefined map[tagKey][]string // map: key -> []option
|
||||||
usedTagNbr map[int]bool // list of used tag numbers
|
|
||||||
usedTagName map[string]bool // list of used tag keys
|
|
||||||
isAtLeastGo124 bool
|
isAtLeastGo124 bool
|
||||||
|
tagCheckers map[tagKey]tagChecker
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor {
|
func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor {
|
||||||
@@ -86,11 +143,16 @@ func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor {
|
|||||||
return nil // skip empty structs
|
return nil // skip empty structs
|
||||||
}
|
}
|
||||||
|
|
||||||
w.usedTagNbr = map[int]bool{}
|
checkCtx := &checkContext{
|
||||||
w.usedTagName = map[string]bool{}
|
userDefined: w.userDefined,
|
||||||
|
usedTagNbr: map[int]bool{},
|
||||||
|
usedTagName: map[string]bool{},
|
||||||
|
isAtLeastGo124: w.isAtLeastGo124,
|
||||||
|
}
|
||||||
|
|
||||||
for _, f := range n.Fields.List {
|
for _, f := range n.Fields.List {
|
||||||
if f.Tag != nil {
|
if f.Tag != nil {
|
||||||
w.checkTaggedField(f)
|
w.checkTaggedField(checkCtx, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,30 +160,44 @@ func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor {
|
|||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
// checkTaggedField checks the tag of the given field.
|
||||||
keyASN1 = "asn1"
|
// precondition: the field has a tag
|
||||||
keyBSON = "bson"
|
func (w lintStructTagRule) checkTaggedField(checkCtx *checkContext, f *ast.Field) {
|
||||||
keyDatastore = "datastore"
|
if len(f.Names) > 0 && !f.Names[0].IsExported() {
|
||||||
keyDefault = "default"
|
w.addFailuref(f, "tag on not-exported field %s", f.Names[0].Name)
|
||||||
keyJSON = "json"
|
}
|
||||||
keyMapstructure = "mapstructure"
|
|
||||||
keyProperties = "properties"
|
|
||||||
keyProtobuf = "protobuf"
|
|
||||||
keyRequired = "required"
|
|
||||||
keyTOML = "toml"
|
|
||||||
keyURL = "url"
|
|
||||||
keyValidate = "validate"
|
|
||||||
keyXML = "xml"
|
|
||||||
keyYAML = "yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (w lintStructTagRule) checkTagNameIfNeed(tag *structtag.Tag) (string, bool) {
|
tags, err := structtag.Parse(strings.Trim(f.Tag.Value, "`"))
|
||||||
|
if err != nil || tags == nil {
|
||||||
|
w.addFailuref(f.Tag, "malformed tag")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range tags.Tags() {
|
||||||
|
if msg, ok := w.checkTagNameIfNeed(checkCtx, tag); !ok {
|
||||||
|
w.addFailureWithTagKey(f.Tag, msg, tag.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
checker, ok := w.tagCheckers[tagKey(tag.Key)]
|
||||||
|
if !ok {
|
||||||
|
continue // we don't have a checker for the tag
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, ok := checker(checkCtx, tag, f.Type)
|
||||||
|
if !ok {
|
||||||
|
w.addFailureWithTagKey(f.Tag, msg, tag.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w lintStructTagRule) checkTagNameIfNeed(checkCtx *checkContext, tag *structtag.Tag) (message string, succeeded bool) {
|
||||||
isUnnamedTag := tag.Name == "" || tag.Name == "-"
|
isUnnamedTag := tag.Name == "" || tag.Name == "-"
|
||||||
if isUnnamedTag {
|
if isUnnamedTag {
|
||||||
return "", true
|
return "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
switch tag.Key {
|
key := tagKey(tag.Key)
|
||||||
|
switch key {
|
||||||
case keyBSON, keyJSON, keyXML, keyYAML, keyProtobuf:
|
case keyBSON, keyJSON, keyXML, keyYAML, keyProtobuf:
|
||||||
default:
|
default:
|
||||||
return "", true
|
return "", true
|
||||||
@@ -134,22 +210,23 @@ func (w lintStructTagRule) checkTagNameIfNeed(tag *structtag.Tag) (string, bool)
|
|||||||
|
|
||||||
// We concat the key and name as the mapping key here
|
// We concat the key and name as the mapping key here
|
||||||
// to allow the same tag name in different tag type.
|
// to allow the same tag name in different tag type.
|
||||||
key := tag.Key + ":" + tagName
|
mapKey := tag.Key + ":" + tagName
|
||||||
if _, ok := w.usedTagName[key]; ok {
|
if _, ok := checkCtx.usedTagName[mapKey]; ok {
|
||||||
return fmt.Sprintf("duplicate tag name: '%s'", tagName), false
|
return fmt.Sprintf("duplicated tag name %q", tagName), false
|
||||||
}
|
}
|
||||||
|
|
||||||
w.usedTagName[key] = true
|
checkCtx.usedTagName[mapKey] = true
|
||||||
|
|
||||||
return "", true
|
return "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lintStructTagRule) getTagName(tag *structtag.Tag) string {
|
func (lintStructTagRule) getTagName(tag *structtag.Tag) string {
|
||||||
switch tag.Key {
|
key := tagKey(tag.Key)
|
||||||
|
switch key {
|
||||||
case keyProtobuf:
|
case keyProtobuf:
|
||||||
for _, option := range tag.Options {
|
for _, option := range tag.Options {
|
||||||
if tagName, found := strings.CutPrefix(option, "name="); found {
|
if tagKey, found := strings.CutPrefix(option, "name="); found {
|
||||||
return tagName
|
return tagKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "" // protobuf tag lacks 'name' option
|
return "" // protobuf tag lacks 'name' option
|
||||||
@@ -158,218 +235,250 @@ func (lintStructTagRule) getTagName(tag *structtag.Tag) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkTaggedField checks the tag of the given field.
|
func checkASN1Tag(checkCtx *checkContext, tag *structtag.Tag, fieldType ast.Expr) (message string, succeeded bool) {
|
||||||
// precondition: the field has a tag
|
|
||||||
func (w lintStructTagRule) checkTaggedField(f *ast.Field) {
|
|
||||||
if len(f.Names) > 0 && !f.Names[0].IsExported() {
|
|
||||||
w.addFailure(f, "tag on not-exported field "+f.Names[0].Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
tags, err := structtag.Parse(strings.Trim(f.Tag.Value, "`"))
|
|
||||||
if err != nil || tags == nil {
|
|
||||||
w.addFailure(f.Tag, "malformed tag")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tag := range tags.Tags() {
|
|
||||||
if msg, ok := w.checkTagNameIfNeed(tag); !ok {
|
|
||||||
w.addFailure(f.Tag, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch key := tag.Key; key {
|
|
||||||
case keyASN1:
|
|
||||||
msg, ok := w.checkASN1Tag(f.Type, tag)
|
|
||||||
if !ok {
|
|
||||||
w.addFailure(f.Tag, msg)
|
|
||||||
}
|
|
||||||
case keyBSON:
|
|
||||||
msg, ok := w.checkBSONTag(tag.Options)
|
|
||||||
if !ok {
|
|
||||||
w.addFailure(f.Tag, msg)
|
|
||||||
}
|
|
||||||
case keyDatastore:
|
|
||||||
msg, ok := w.checkDatastoreTag(tag.Options)
|
|
||||||
if !ok {
|
|
||||||
w.addFailure(f.Tag, msg)
|
|
||||||
}
|
|
||||||
case keyDefault:
|
|
||||||
if !w.typeValueMatch(f.Type, tag.Name) {
|
|
||||||
w.addFailure(f.Tag, "field's type and default value's type mismatch")
|
|
||||||
}
|
|
||||||
case keyJSON:
|
|
||||||
msg, ok := w.checkJSONTag(tag.Name, tag.Options)
|
|
||||||
if !ok {
|
|
||||||
w.addFailure(f.Tag, msg)
|
|
||||||
}
|
|
||||||
case keyMapstructure:
|
|
||||||
msg, ok := w.checkMapstructureTag(tag.Options)
|
|
||||||
if !ok {
|
|
||||||
w.addFailure(f.Tag, msg)
|
|
||||||
}
|
|
||||||
case keyProperties:
|
|
||||||
msg, ok := w.checkPropertiesTag(f.Type, tag.Options)
|
|
||||||
if !ok {
|
|
||||||
w.addFailure(f.Tag, msg)
|
|
||||||
}
|
|
||||||
case keyProtobuf:
|
|
||||||
msg, ok := w.checkProtobufTag(tag)
|
|
||||||
if !ok {
|
|
||||||
w.addFailure(f.Tag, msg)
|
|
||||||
}
|
|
||||||
case keyRequired:
|
|
||||||
if tag.Name != "true" && tag.Name != "false" {
|
|
||||||
w.addFailure(f.Tag, "required should be 'true' or 'false'")
|
|
||||||
}
|
|
||||||
case keyTOML:
|
|
||||||
msg, ok := w.checkTOMLTag(tag.Options)
|
|
||||||
if !ok {
|
|
||||||
w.addFailure(f.Tag, msg)
|
|
||||||
}
|
|
||||||
case keyURL:
|
|
||||||
msg, ok := w.checkURLTag(tag.Options)
|
|
||||||
if !ok {
|
|
||||||
w.addFailure(f.Tag, msg)
|
|
||||||
}
|
|
||||||
case keyValidate:
|
|
||||||
opts := append([]string{tag.Name}, tag.Options...)
|
|
||||||
msg, ok := w.checkValidateTag(opts)
|
|
||||||
if !ok {
|
|
||||||
w.addFailure(f.Tag, msg)
|
|
||||||
}
|
|
||||||
case keyXML:
|
|
||||||
msg, ok := w.checkXMLTag(tag.Options)
|
|
||||||
if !ok {
|
|
||||||
w.addFailure(f.Tag, msg)
|
|
||||||
}
|
|
||||||
case keyYAML:
|
|
||||||
msg, ok := w.checkYAMLTag(tag.Options)
|
|
||||||
if !ok {
|
|
||||||
w.addFailure(f.Tag, msg)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// unknown key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w lintStructTagRule) checkASN1Tag(t ast.Expr, tag *structtag.Tag) (string, bool) {
|
|
||||||
checkList := append(tag.Options, tag.Name)
|
checkList := append(tag.Options, tag.Name)
|
||||||
for _, opt := range checkList {
|
for _, opt := range checkList {
|
||||||
switch opt {
|
switch opt {
|
||||||
case "application", "explicit", "generalized", "ia5", "omitempty", "optional", "set", "utf8":
|
case "application", "explicit", "generalized", "ia5", "omitempty", "optional", "set", "utf8":
|
||||||
|
// do nothing
|
||||||
default:
|
default:
|
||||||
if strings.HasPrefix(opt, "tag:") {
|
msg, ok := checkCompoundANS1Option(checkCtx, opt, fieldType)
|
||||||
parts := strings.Split(opt, ":")
|
if !ok {
|
||||||
tagNumber := parts[1]
|
return msg, false
|
||||||
number, err := strconv.Atoi(tagNumber)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Sprintf("ASN1 tag must be a number, got '%s'", tagNumber), false
|
|
||||||
}
|
}
|
||||||
if w.usedTagNbr[number] {
|
|
||||||
return fmt.Sprintf("duplicated tag number %v", number), false
|
|
||||||
}
|
|
||||||
w.usedTagNbr[number] = true
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(opt, "default:") {
|
|
||||||
parts := strings.Split(opt, ":")
|
|
||||||
if len(parts) < 2 {
|
|
||||||
return "malformed default for ASN1 tag", false
|
|
||||||
}
|
|
||||||
if !w.typeValueMatch(t, parts[1]) {
|
|
||||||
return "field's type and default value's type mismatch", false
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.isUserDefined(keyASN1, opt) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("unknown option '%s' in ASN1 tag", opt), false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", true
|
return "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w lintStructTagRule) checkBSONTag(options []string) (string, bool) {
|
func checkCompoundANS1Option(checkCtx *checkContext, opt string, fieldType ast.Expr) (message string, succeeded bool) {
|
||||||
for _, opt := range options {
|
key, value, _ := strings.Cut(opt, ":")
|
||||||
|
switch key {
|
||||||
|
case "tag":
|
||||||
|
number, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("tag must be a number but is %q", value), false
|
||||||
|
}
|
||||||
|
if checkCtx.usedTagNbr[number] {
|
||||||
|
return fmt.Sprintf(msgDuplicatedTagNumber, number), false
|
||||||
|
}
|
||||||
|
checkCtx.usedTagNbr[number] = true
|
||||||
|
case "default":
|
||||||
|
if !typeValueMatch(fieldType, value) {
|
||||||
|
return msgTypeMismatch, false
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if !checkCtx.isUserDefined(keyASN1, opt) {
|
||||||
|
return fmt.Sprintf(msgUnknownOption, opt), false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDatastoreTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
|
||||||
|
for _, opt := range tag.Options {
|
||||||
|
switch opt {
|
||||||
|
case "flatten", "noindex", "omitempty":
|
||||||
|
default:
|
||||||
|
if checkCtx.isUserDefined(keyDatastore, opt) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(msgUnknownOption, opt), false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDefaultTag(_ *checkContext, tag *structtag.Tag, fieldType ast.Expr) (message string, succeeded bool) {
|
||||||
|
if !typeValueMatch(fieldType, tag.Name) {
|
||||||
|
return msgTypeMismatch, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkBSONTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
|
||||||
|
for _, opt := range tag.Options {
|
||||||
switch opt {
|
switch opt {
|
||||||
case "inline", "minsize", "omitempty":
|
case "inline", "minsize", "omitempty":
|
||||||
default:
|
default:
|
||||||
if w.isUserDefined(keyBSON, opt) {
|
if checkCtx.isUserDefined(keyBSON, opt) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("unknown option '%s' in BSON tag", opt), false
|
return fmt.Sprintf(msgUnknownOption, opt), false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", true
|
return "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w lintStructTagRule) checkJSONTag(name string, options []string) (string, bool) {
|
func checkJSONTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
|
||||||
for _, opt := range options {
|
for _, opt := range tag.Options {
|
||||||
switch opt {
|
switch opt {
|
||||||
case "omitempty", "string":
|
case "omitempty", "string":
|
||||||
case "":
|
case "":
|
||||||
// special case for JSON key "-"
|
// special case for JSON key "-"
|
||||||
if name != "-" {
|
if tag.Name != "-" {
|
||||||
return "option can not be empty in JSON tag", false
|
return "option can not be empty", false
|
||||||
}
|
}
|
||||||
case "omitzero":
|
case "omitzero":
|
||||||
if w.isAtLeastGo124 {
|
if checkCtx.isAtLeastGo124 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fallthrough
|
return `prior Go 1.24, option "omitzero" is unsupported`, false
|
||||||
default:
|
default:
|
||||||
if w.isUserDefined(keyJSON, opt) {
|
if checkCtx.isUserDefined(keyJSON, opt) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("unknown option '%s' in JSON tag", opt), false
|
return fmt.Sprintf(msgUnknownOption, opt), false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", true
|
return "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w lintStructTagRule) checkXMLTag(options []string) (string, bool) {
|
func checkMapstructureTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
|
||||||
for _, opt := range options {
|
for _, opt := range tag.Options {
|
||||||
switch opt {
|
switch opt {
|
||||||
case "any", "attr", "cdata", "chardata", "comment", "innerxml", "omitempty", "typeattr":
|
case "omitempty", "reminder", "squash":
|
||||||
default:
|
default:
|
||||||
if w.isUserDefined(keyXML, opt) {
|
if checkCtx.isUserDefined(keyMapstructure, opt) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("unknown option '%s' in XML tag", opt), false
|
return fmt.Sprintf(msgUnknownOption, opt), false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", true
|
return "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w lintStructTagRule) checkYAMLTag(options []string) (string, bool) {
|
func checkPropertiesTag(_ *checkContext, tag *structtag.Tag, fieldType ast.Expr) (message string, succeeded bool) {
|
||||||
|
options := tag.Options
|
||||||
|
if len(options) == 0 {
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
|
||||||
|
seenOptions := map[string]bool{}
|
||||||
for _, opt := range options {
|
for _, opt := range options {
|
||||||
switch opt {
|
msg, ok := fmt.Sprintf("unknown or malformed option %q", opt), false
|
||||||
case "flow", "inline", "omitempty":
|
if key, value, found := strings.Cut(opt, "="); found {
|
||||||
default:
|
msg, ok = checkCompoundPropertiesOption(key, value, fieldType, seenOptions)
|
||||||
if w.isUserDefined(keyYAML, opt) {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("unknown option '%s' in YAML tag", opt), false
|
|
||||||
|
if !ok {
|
||||||
|
return msg, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", true
|
return "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w lintStructTagRule) checkURLTag(options []string) (string, bool) {
|
func checkCompoundPropertiesOption(key, value string, fieldType ast.Expr, seenOptions map[string]bool) (message string, succeeded bool) {
|
||||||
|
if _, ok := seenOptions[key]; ok {
|
||||||
|
return fmt.Sprintf(msgDuplicatedOption, key), false
|
||||||
|
}
|
||||||
|
seenOptions[key] = true
|
||||||
|
|
||||||
|
if strings.TrimSpace(value) == "" {
|
||||||
|
return fmt.Sprintf("option %q not of the form %s=value", key, key), false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "default":
|
||||||
|
if !typeValueMatch(fieldType, value) {
|
||||||
|
return msgTypeMismatch, false
|
||||||
|
}
|
||||||
|
case "layout":
|
||||||
|
if gofmt(fieldType) != "time.Time" {
|
||||||
|
return "layout option is only applicable to fields of type time.Time", false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkProtobufTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
|
||||||
|
// check name
|
||||||
|
switch tag.Name {
|
||||||
|
case "bytes", "fixed32", "fixed64", "group", "varint", "zigzag32", "zigzag64":
|
||||||
|
// do nothing
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("invalid tag name %q", tag.Name), false
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkProtobufOptions(checkCtx, tag.Options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkProtobufOptions(checkCtx *checkContext, options []string) (message string, succeeded bool) {
|
||||||
|
seenOptions := map[string]bool{}
|
||||||
|
hasName := false
|
||||||
|
for _, opt := range options {
|
||||||
|
opt := strings.Split(opt, "=")[0]
|
||||||
|
|
||||||
|
if number, err := strconv.Atoi(opt); err == nil {
|
||||||
|
_, alreadySeen := checkCtx.usedTagNbr[number]
|
||||||
|
if alreadySeen {
|
||||||
|
return fmt.Sprintf(msgDuplicatedTagNumber, number), false
|
||||||
|
}
|
||||||
|
checkCtx.usedTagNbr[number] = true
|
||||||
|
continue // option is an integer
|
||||||
|
}
|
||||||
|
|
||||||
|
switch opt {
|
||||||
|
case "json", "opt", "proto3", "rep", "req":
|
||||||
|
// do nothing
|
||||||
|
case "name":
|
||||||
|
hasName = true
|
||||||
|
default:
|
||||||
|
if checkCtx.isUserDefined(keyProtobuf, opt) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(msgUnknownOption, opt), false
|
||||||
|
}
|
||||||
|
|
||||||
|
_, alreadySeen := seenOptions[opt]
|
||||||
|
if alreadySeen {
|
||||||
|
return fmt.Sprintf(msgDuplicatedOption, opt), false
|
||||||
|
}
|
||||||
|
seenOptions[opt] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasName {
|
||||||
|
return `mandatory option "name" not found`, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkRequiredTag(_ *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
|
||||||
|
switch tag.Name {
|
||||||
|
case "true", "false":
|
||||||
|
return "", true
|
||||||
|
default:
|
||||||
|
return `required should be "true" or "false"`, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkTOMLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
|
||||||
|
for _, opt := range tag.Options {
|
||||||
|
switch opt {
|
||||||
|
case "omitempty":
|
||||||
|
default:
|
||||||
|
if checkCtx.isUserDefined(keyTOML, opt) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(msgUnknownOption, opt), false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkURLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
|
||||||
var delimiter = ""
|
var delimiter = ""
|
||||||
for _, opt := range options {
|
for _, opt := range tag.Options {
|
||||||
switch opt {
|
switch opt {
|
||||||
case "int", "omitempty", "numbered", "brackets":
|
case "int", "omitempty", "numbered", "brackets":
|
||||||
case "unix", "unixmilli", "unixnano": // TODO : check that the field is of type time.Time
|
case "unix", "unixmilli", "unixnano": // TODO : check that the field is of type time.Time
|
||||||
@@ -378,66 +487,37 @@ func (w lintStructTagRule) checkURLTag(options []string) (string, bool) {
|
|||||||
delimiter = opt
|
delimiter = opt
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("can not set both '%s' and '%s' as delimiters in URL tag", opt, delimiter), false
|
return fmt.Sprintf("can not set both %q and %q as delimiters", opt, delimiter), false
|
||||||
default:
|
default:
|
||||||
if w.isUserDefined(keyURL, opt) {
|
if checkCtx.isUserDefined(keyURL, opt) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("unknown option '%s' in URL tag", opt), false
|
return fmt.Sprintf(msgUnknownOption, opt), false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", true
|
return "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w lintStructTagRule) checkDatastoreTag(options []string) (string, bool) {
|
func checkValidateTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
|
||||||
for _, opt := range options {
|
|
||||||
switch opt {
|
|
||||||
case "flatten", "noindex", "omitempty":
|
|
||||||
default:
|
|
||||||
if w.isUserDefined(keyDatastore, opt) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("unknown option '%s' in Datastore tag", opt), false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w lintStructTagRule) checkMapstructureTag(options []string) (string, bool) {
|
|
||||||
for _, opt := range options {
|
|
||||||
switch opt {
|
|
||||||
case "omitempty", "reminder", "squash":
|
|
||||||
default:
|
|
||||||
if w.isUserDefined(keyMapstructure, opt) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("unknown option '%s' in Mapstructure tag", opt), false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w lintStructTagRule) checkValidateTag(options []string) (string, bool) {
|
|
||||||
previousOption := ""
|
previousOption := ""
|
||||||
seenKeysOption := false
|
seenKeysOption := false
|
||||||
|
options := append([]string{tag.Name}, tag.Options...)
|
||||||
for _, opt := range options {
|
for _, opt := range options {
|
||||||
switch opt {
|
switch opt {
|
||||||
case "keys":
|
case "keys":
|
||||||
if previousOption != "dive" {
|
if previousOption != "dive" {
|
||||||
return "option 'keys' must follow a 'dive' option in validate tag", false
|
return `option "keys" must follow a "dive" option`, false
|
||||||
}
|
}
|
||||||
seenKeysOption = true
|
seenKeysOption = true
|
||||||
case "endkeys":
|
case "endkeys":
|
||||||
if !seenKeysOption {
|
if !seenKeysOption {
|
||||||
return "option 'endkeys' without a previous 'keys' option in validate tag", false
|
return `option "endkeys" without a previous "keys" option`, false
|
||||||
}
|
}
|
||||||
seenKeysOption = false
|
seenKeysOption = false
|
||||||
default:
|
default:
|
||||||
parts := strings.Split(opt, "|")
|
parts := strings.Split(opt, "|")
|
||||||
errMsg, ok := w.checkValidateOptionsAlternatives(parts)
|
errMsg, ok := checkValidateOptionsAlternatives(checkCtx, parts)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errMsg, false
|
return errMsg, false
|
||||||
}
|
}
|
||||||
@@ -448,47 +528,60 @@ func (w lintStructTagRule) checkValidateTag(options []string) (string, bool) {
|
|||||||
return "", true
|
return "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w lintStructTagRule) checkTOMLTag(options []string) (string, bool) {
|
func checkXMLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
|
||||||
for _, opt := range options {
|
for _, opt := range tag.Options {
|
||||||
switch opt {
|
switch opt {
|
||||||
case "omitempty":
|
case "any", "attr", "cdata", "chardata", "comment", "innerxml", "omitempty", "typeattr":
|
||||||
default:
|
default:
|
||||||
if w.isUserDefined(keyTOML, opt) {
|
if checkCtx.isUserDefined(keyXML, opt) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("unknown option '%s' in TOML tag", opt), false
|
return fmt.Sprintf(msgUnknownOption, opt), false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", true
|
return "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w lintStructTagRule) checkValidateOptionsAlternatives(alternatives []string) (string, bool) {
|
func checkYAMLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) {
|
||||||
|
for _, opt := range tag.Options {
|
||||||
|
switch opt {
|
||||||
|
case "flow", "inline", "omitempty":
|
||||||
|
default:
|
||||||
|
if checkCtx.isUserDefined(keyYAML, opt) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(msgUnknownOption, opt), false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkValidateOptionsAlternatives(checkCtx *checkContext, alternatives []string) (message string, succeeded bool) {
|
||||||
for _, alternative := range alternatives {
|
for _, alternative := range alternatives {
|
||||||
alternative := strings.TrimSpace(alternative)
|
alternative := strings.TrimSpace(alternative)
|
||||||
parts := strings.Split(alternative, "=")
|
lhs, _, found := strings.Cut(alternative, "=")
|
||||||
switch len(parts) {
|
if found {
|
||||||
case 1:
|
|
||||||
badOpt, ok := areValidateOpts(parts[0])
|
|
||||||
if ok || w.isUserDefined(keyValidate, badOpt) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("unknown option '%s' in validate tag", badOpt), false
|
|
||||||
case 2:
|
|
||||||
lhs := parts[0]
|
|
||||||
_, ok := validateLHS[lhs]
|
_, ok := validateLHS[lhs]
|
||||||
if ok || w.isUserDefined(keyValidate, lhs) {
|
if ok || checkCtx.isUserDefined(keyValidate, lhs) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("unknown option '%s' in validate tag", lhs), false
|
return fmt.Sprintf(msgUnknownOption, lhs), false
|
||||||
default:
|
|
||||||
return fmt.Sprintf("malformed options '%s' in validate tag, not expected more than one '='", alternative), false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
badOpt, ok := areValidateOpts(alternative)
|
||||||
|
if ok || checkCtx.isUserDefined(keyValidate, badOpt) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(msgUnknownOption, badOpt), false
|
||||||
|
}
|
||||||
|
|
||||||
return "", true
|
return "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lintStructTagRule) typeValueMatch(t ast.Expr, val string) bool {
|
func typeValueMatch(t ast.Expr, val string) bool {
|
||||||
tID, ok := t.(*ast.Ident)
|
tID, ok := t.(*ast.Ident)
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
@@ -512,115 +605,19 @@ func (lintStructTagRule) typeValueMatch(t ast.Expr, val string) bool {
|
|||||||
|
|
||||||
return typeMatches
|
return typeMatches
|
||||||
}
|
}
|
||||||
func (w lintStructTagRule) checkPropertiesTag(t ast.Expr, options []string) (string, bool) {
|
|
||||||
if len(options) == 0 {
|
func (w lintStructTagRule) addFailureWithTagKey(n ast.Node, msg string, tagKey string) {
|
||||||
return "", true
|
w.addFailuref(n, "%s in %s tag", msg, tagKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
hasDefault := false
|
func (w lintStructTagRule) addFailuref(n ast.Node, msg string, args ...any) {
|
||||||
for _, opt := range options {
|
|
||||||
key, val, found := strings.Cut(opt, "=")
|
|
||||||
switch key {
|
|
||||||
case "default":
|
|
||||||
if hasDefault {
|
|
||||||
return "properties tag accepts only one default option", false
|
|
||||||
}
|
|
||||||
hasDefault = true
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
return "malformed default for properties tag", false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !w.typeValueMatch(t, val) {
|
|
||||||
return "field type and default value type mismatch in properties tag", false
|
|
||||||
}
|
|
||||||
case "layout":
|
|
||||||
if !found || strings.TrimSpace(val) == "" {
|
|
||||||
return "malformed layout option for properties tag", false
|
|
||||||
}
|
|
||||||
|
|
||||||
if gofmt(t) != "time.Time" {
|
|
||||||
return "layout option is only applicable to fields of type time.Time in properties tag", false
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("unknown option %q in properties tag", opt), false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w lintStructTagRule) checkProtobufTag(tag *structtag.Tag) (string, bool) {
|
|
||||||
// check name
|
|
||||||
switch tag.Name {
|
|
||||||
case "bytes", "fixed32", "fixed64", "group", "varint", "zigzag32", "zigzag64":
|
|
||||||
// do nothing
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("invalid protobuf tag name '%s'", tag.Name), false
|
|
||||||
}
|
|
||||||
|
|
||||||
// check options
|
|
||||||
seenOptions := map[string]bool{}
|
|
||||||
for _, opt := range tag.Options {
|
|
||||||
if number, err := strconv.Atoi(opt); err == nil {
|
|
||||||
_, alreadySeen := w.usedTagNbr[number]
|
|
||||||
if alreadySeen {
|
|
||||||
return fmt.Sprintf("duplicated tag number %v", number), false
|
|
||||||
}
|
|
||||||
w.usedTagNbr[number] = true
|
|
||||||
continue // option is an integer
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case opt == "opt" || opt == "proto3" || opt == "rep" || opt == "req":
|
|
||||||
// do nothing
|
|
||||||
case strings.Contains(opt, "="):
|
|
||||||
o := strings.Split(opt, "=")[0]
|
|
||||||
_, alreadySeen := seenOptions[o]
|
|
||||||
if alreadySeen {
|
|
||||||
return fmt.Sprintf("protobuf tag has duplicated option '%s'", o), false
|
|
||||||
}
|
|
||||||
seenOptions[o] = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, hasName := seenOptions["name"]
|
|
||||||
if !hasName {
|
|
||||||
return "protobuf tag lacks mandatory option 'name'", false
|
|
||||||
}
|
|
||||||
|
|
||||||
for k := range seenOptions {
|
|
||||||
switch k {
|
|
||||||
case "name", "json":
|
|
||||||
// do nothing
|
|
||||||
default:
|
|
||||||
if w.isUserDefined(keyProtobuf, k) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("unknown option '%s' in protobuf tag", k), false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w lintStructTagRule) addFailure(n ast.Node, msg string) {
|
|
||||||
w.onFailure(lint.Failure{
|
w.onFailure(lint.Failure{
|
||||||
Node: n,
|
Node: n,
|
||||||
Failure: msg,
|
Failure: fmt.Sprintf(msg, args...),
|
||||||
Confidence: 1,
|
Confidence: 1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w lintStructTagRule) isUserDefined(key, opt string) bool {
|
|
||||||
if w.userDefined == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
options := w.userDefined[key]
|
|
||||||
return slices.Contains(options, opt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func areValidateOpts(opts string) (string, bool) {
|
func areValidateOpts(opts string) (string, bool) {
|
||||||
parts := strings.Split(opts, "|")
|
parts := strings.Split(opts, "|")
|
||||||
for _, opt := range parts {
|
for _, opt := range parts {
|
||||||
@@ -633,6 +630,13 @@ func areValidateOpts(opts string) (string, bool) {
|
|||||||
return "", true
|
return "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
msgDuplicatedOption = "duplicated option %q"
|
||||||
|
msgDuplicatedTagNumber = "duplicated tag number %v"
|
||||||
|
msgUnknownOption = "unknown option %q"
|
||||||
|
msgTypeMismatch = "type mismatch between field type and default value type"
|
||||||
|
)
|
||||||
|
|
||||||
var validateSingleOptions = map[string]struct{}{
|
var validateSingleOptions = map[string]struct{}{
|
||||||
"alpha": {},
|
"alpha": {},
|
||||||
"alphanum": {},
|
"alphanum": {},
|
||||||
|
|||||||
113
testdata/go1.24/struct_tag.go
vendored
113
testdata/go1.24/struct_tag.go
vendored
@@ -1,22 +1,20 @@
|
|||||||
package fixtures
|
package fixtures
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type decodeAndValidateRequest struct {
|
type decodeAndValidateRequest struct {
|
||||||
// BEAWRE : the flag of URLParam should match the const string URLParam
|
// BEAWRE : the flag of URLParam should match the const string URLParam
|
||||||
URLParam string `json:"-" path:"url_param" validate:"numeric"`
|
URLParam string `json:"-" path:"url_param" validate:"numeric"`
|
||||||
Text string `json:"text" validate:"max=10"`
|
Text string `json:"text" validate:"max=10"`
|
||||||
DefaultInt int `json:"defaultInt" default:"10.0"` // MATCH /field's type and default value's type mismatch/
|
DefaultInt int `json:"defaultInt" default:"10.0"` // MATCH /type mismatch between field type and default value type in default tag/
|
||||||
DefaultInt2 int `json:"defaultInt2" default:"10"`
|
DefaultInt2 int `json:"defaultInt2" default:"10"`
|
||||||
// MATCH:12 /unknown option 'inline' in JSON tag/
|
// MATCH:10 /unknown option "inline" in json tag/
|
||||||
DefaultInt3 int `json:"defaultInt2,inline" default:"11"` // MATCH /duplicate tag name: 'defaultInt2'/
|
DefaultInt3 int `json:"defaultInt2,inline" default:"11"` // MATCH /duplicated tag name "defaultInt2" in json tag/
|
||||||
DefaultString string `json:"defaultString" default:"foo"`
|
DefaultString string `json:"defaultString" default:"foo"`
|
||||||
DefaultBool bool `json:"defaultBool" default:"trues"` // MATCH /field's type and default value's type mismatch/
|
DefaultBool bool `json:"defaultBool" default:"trues"` // MATCH /type mismatch between field type and default value type in default tag/
|
||||||
DefaultBool2 bool `json:"defaultBool2" default:"true"`
|
DefaultBool2 bool `json:"defaultBool2" default:"true"`
|
||||||
DefaultBool3 bool `json:"defaultBool3" default:"false"`
|
DefaultBool3 bool `json:"defaultBool3" default:"false"`
|
||||||
DefaultFloat float64 `json:"defaultFloat" default:"f10.0"` // MATCH /field's type and default value's type mismatch/
|
DefaultFloat float64 `json:"defaultFloat" default:"f10.0"` // MATCH /type mismatch between field type and default value type in default tag/
|
||||||
DefaultFloat2 float64 `json:"defaultFloat2" default:"10.0"`
|
DefaultFloat2 float64 `json:"defaultFloat2" default:"10.0"`
|
||||||
MandatoryStruct mandatoryStruct `json:"mandatoryStruct" required:"trues"` // MATCH /required should be 'true' or 'false'/
|
MandatoryStruct mandatoryStruct `json:"mandatoryStruct" required:"trues"` // MATCH /required should be "true" or "false" in required tag/
|
||||||
MandatoryStruct2 mandatoryStruct `json:"mandatoryStruct2" required:"true"`
|
MandatoryStruct2 mandatoryStruct `json:"mandatoryStruct2" required:"true"`
|
||||||
MandatoryStruct4 mandatoryStruct `json:"mandatoryStruct4" required:"false"`
|
MandatoryStruct4 mandatoryStruct `json:"mandatoryStruct4" required:"false"`
|
||||||
OptionalStruct *optionalStruct `json:"optionalStruct,omitempty"`
|
OptionalStruct *optionalStruct `json:"optionalStruct,omitempty"`
|
||||||
@@ -24,102 +22,13 @@ type decodeAndValidateRequest struct {
|
|||||||
optionalQuery string `json:"-" querystring:"queryfoo"` // MATCH /tag on not-exported field optionalQuery/
|
optionalQuery string `json:"-" querystring:"queryfoo"` // MATCH /tag on not-exported field optionalQuery/
|
||||||
// No-reg test for bug https://github.com/mgechev/revive/issues/208
|
// No-reg test for bug https://github.com/mgechev/revive/issues/208
|
||||||
Tiret string `json:"-,"`
|
Tiret string `json:"-,"`
|
||||||
BadTiret string `json:"other,"` // MATCH /option can not be empty in JSON tag/
|
BadTiret string `json:"other,"` // MATCH /option can not be empty in json tag/
|
||||||
ForOmitzero string `json:"forOmitZero,omitzero"` // 'omitzero' is valid in go 1.24
|
ForOmitzero string `json:"forOmitZero,omitzero"` // Go 1.24 introduces omitzero
|
||||||
}
|
}
|
||||||
|
|
||||||
type RangeAllocation struct {
|
type RangeAllocation struct {
|
||||||
metav1.TypeMeta `json:",inline"` // MATCH /unknown option 'inline' in JSON tag/
|
metav1.TypeMeta `json:",inline"` // MATCH /unknown option "inline" in json tag/
|
||||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||||
Range string `json:"range,flow"` // MATCH /unknown option 'flow' in JSON tag/
|
Range string `json:"range,flow"` // MATCH /unknown option "flow" in json tag/
|
||||||
Data []byte `json:"data,inline"` // MATCH /unknown option 'inline' in JSON tag/
|
Data []byte `json:"data,inline"` // MATCH /unknown option "inline" in json tag/
|
||||||
}
|
|
||||||
|
|
||||||
type RangeAllocation struct {
|
|
||||||
metav1.TypeMeta `bson:",minsize"`
|
|
||||||
metav1.ObjectMeta `bson:"metadata,omitempty"`
|
|
||||||
Range string `bson:"range,flow"` // MATCH /unknown option 'flow' in BSON tag/
|
|
||||||
Data []byte `bson:"data,inline"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestContextSpecificTags2 struct {
|
|
||||||
A int `asn1:"explicit,tag:1"`
|
|
||||||
B int `asn1:"tag:2"`
|
|
||||||
S string `asn1:"tag:0,utf8"`
|
|
||||||
Ints []int `asn1:"set"`
|
|
||||||
Version int `asn1:"optional,explicit,default:0,tag:000"` // MATCH /duplicated tag number 0/
|
|
||||||
Time time.Time `asn1:"explicit,tag:4,other"` // MATCH /unknown option 'other' in ASN1 tag/
|
|
||||||
X int `asn1:"explicit,tag:invalid"` // MATCH /ASN1 tag must be a number, got 'invalid'/
|
|
||||||
}
|
|
||||||
|
|
||||||
type VirtualMachineRelocateSpecDiskLocator struct {
|
|
||||||
DynamicData
|
|
||||||
|
|
||||||
DiskId int32 `xml:"diskId,attr,cdata"`
|
|
||||||
Datastore ManagedObjectReference `xml:"datastore,chardata,innerxml"`
|
|
||||||
DiskMoveType string `xml:"diskMoveType,omitempty,comment"`
|
|
||||||
DiskBackingInfo BaseVirtualDeviceBackingInfo `xml:"diskBackingInfo,omitempty,any"`
|
|
||||||
Profile []BaseVirtualMachineProfileSpec `xml:"profile,omitempty,other"` // MATCH /unknown option 'other' in XML tag/
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestDuplicatedXMLTags struct {
|
|
||||||
A int `xml:"a"`
|
|
||||||
B int `xml:"a"` // MATCH /duplicate tag name: 'a'/
|
|
||||||
C int `xml:"c"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestDuplicatedBSONTags struct {
|
|
||||||
A int `bson:"b"`
|
|
||||||
B int `bson:"b"` // MATCH /duplicate tag name: 'b'/
|
|
||||||
C int `bson:"c"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestDuplicatedYAMLTags struct {
|
|
||||||
A int `yaml:"b"`
|
|
||||||
B int `yaml:"c"`
|
|
||||||
C int `yaml:"c"` // MATCH /duplicate tag name: 'c'/
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestDuplicatedProtobufTags struct {
|
|
||||||
A int `protobuf:"varint,name=b"`
|
|
||||||
B int `protobuf:"varint,name=c"`
|
|
||||||
C int `protobuf:"varint,name=c"` // MATCH /duplicate tag name: 'c'/
|
|
||||||
}
|
|
||||||
|
|
||||||
// test case from
|
|
||||||
// sigs.k8s.io/kustomize/api/types/helmchartargs.go
|
|
||||||
|
|
||||||
type HelmChartArgs struct {
|
|
||||||
ChartName string `json:"chartName,omitempty" yaml:"chartName,omitempty"`
|
|
||||||
ChartVersion string `json:"chartVersion,omitempty" yaml:"chartVersion,omitempty"`
|
|
||||||
ChartRepoURL string `json:"chartRepoUrl,omitempty" yaml:"chartRepoUrl,omitempty"`
|
|
||||||
ChartHome string `json:"chartHome,omitempty" yaml:"chartHome,omitempty"`
|
|
||||||
ChartRepoName string `json:"chartRepoName,omitempty" yaml:"chartRepoName,omitempty"`
|
|
||||||
HelmBin string `json:"helmBin,omitempty" yaml:"helmBin,omitempty"`
|
|
||||||
HelmHome string `json:"helmHome,omitempty" yaml:"helmHome,omitempty"`
|
|
||||||
Values string `json:"values,omitempty" yaml:"values,omitempty"`
|
|
||||||
ValuesLocal map[string]interface{} `json:"valuesLocal,omitempty" yaml:"valuesLocal,omitempty"`
|
|
||||||
ValuesMerge string `json:"valuesMerge,omitempty" yaml:"valuesMerge,omitempty"`
|
|
||||||
ReleaseName string `json:"releaseName,omitempty" yaml:"releaseName,omitempty"`
|
|
||||||
ReleaseNamespace string `json:"releaseNamespace,omitempty" yaml:"releaseNamespace,omitempty"`
|
|
||||||
ExtraArgs []string `json:"extraArgs,omitempty" yaml:"extraArgs,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test message for holding primitive types.
|
|
||||||
type Simple struct {
|
|
||||||
OBool *bool `protobuf:"varint,1,req,json=oBool"` // MATCH /protobuf tag lacks mandatory option 'name'/
|
|
||||||
OInt32 *int32 `protobuf:"varint,2,opt,name=o_int32,jsonx=oInt32"` // MATCH /unknown option 'jsonx' in protobuf tag/
|
|
||||||
OInt32Str *int32 `protobuf:"varint,3,rep,name=o_int32_str,name=oInt32Str"` // MATCH /protobuf tag has duplicated option 'name'/
|
|
||||||
OInt64 *int64 `protobuf:"varint,4,opt,json=oInt64,name=o_int64,json=oInt64"` // MATCH /protobuf tag has duplicated option 'json'/
|
|
||||||
OSint32Str *int32 `protobuf:"zigzag32,11,opt,name=o_sint32_str,json=oSint32Str"`
|
|
||||||
OSint64Str *int64 `protobuf:"zigzag64,13,opt,name=o_sint32_str,json=oSint64Str"` // MATCH /duplicate tag name: 'o_sint32_str'/
|
|
||||||
OFloat *float32 `protobuf:"fixed32,14,opt,name=o_float,json=oFloat"`
|
|
||||||
ODouble *float64 `protobuf:"fixed64,014,opt,name=o_double,json=oDouble"` // MATCH /duplicated tag number 14/
|
|
||||||
ODoubleStr *float64 `protobuf:"fixed6,17,opt,name=o_double_str,json=oDoubleStr"` // MATCH /invalid protobuf tag name 'fixed6'/
|
|
||||||
OString *string `protobuf:"bytes,18,opt,name=o_string,json=oString"`
|
|
||||||
OString2 *string `protobuf:"bytes,name=ameno"`
|
|
||||||
OString3 *string `protobuf:"bytes,name=ameno"` // MATCH /duplicate tag name: 'ameno'/
|
|
||||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
XXX_sizecache int32 `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
92
testdata/struct_tag.go
vendored
92
testdata/struct_tag.go
vendored
@@ -6,17 +6,17 @@ type decodeAndValidateRequest struct {
|
|||||||
// BEAWRE : the flag of URLParam should match the const string URLParam
|
// BEAWRE : the flag of URLParam should match the const string URLParam
|
||||||
URLParam string `json:"-" path:"url_param" validate:"numeric"`
|
URLParam string `json:"-" path:"url_param" validate:"numeric"`
|
||||||
Text string `json:"text" validate:"max=10"`
|
Text string `json:"text" validate:"max=10"`
|
||||||
DefaultInt int `json:"defaultInt" default:"10.0"` // MATCH /field's type and default value's type mismatch/
|
DefaultInt int `json:"defaultInt" default:"10.0"` // MATCH /type mismatch between field type and default value type in default tag/
|
||||||
DefaultInt2 int `json:"defaultInt2" default:"10"`
|
DefaultInt2 int `json:"defaultInt2" default:"10"`
|
||||||
// MATCH:12 /unknown option 'inline' in JSON tag/
|
// MATCH:12 /unknown option "inline" in json tag/
|
||||||
DefaultInt3 int `json:"defaultInt2,inline" default:"11"` // MATCH /duplicate tag name: 'defaultInt2'/
|
DefaultInt3 int `json:"defaultInt2,inline" default:"11"` // MATCH /duplicated tag name "defaultInt2" in json tag/
|
||||||
DefaultString string `json:"defaultString" default:"foo"`
|
DefaultString string `json:"defaultString" default:"foo"`
|
||||||
DefaultBool bool `json:"defaultBool" default:"trues"` // MATCH /field's type and default value's type mismatch/
|
DefaultBool bool `json:"defaultBool" default:"trues"` // MATCH /type mismatch between field type and default value type in default tag/
|
||||||
DefaultBool2 bool `json:"defaultBool2" default:"true"`
|
DefaultBool2 bool `json:"defaultBool2" default:"true"`
|
||||||
DefaultBool3 bool `json:"defaultBool3" default:"false"`
|
DefaultBool3 bool `json:"defaultBool3" default:"false"`
|
||||||
DefaultFloat float64 `json:"defaultFloat" default:"f10.0"` // MATCH /field's type and default value's type mismatch/
|
DefaultFloat float64 `json:"defaultFloat" default:"f10.0"` // MATCH /type mismatch between field type and default value type in default tag/
|
||||||
DefaultFloat2 float64 `json:"defaultFloat2" default:"10.0"`
|
DefaultFloat2 float64 `json:"defaultFloat2" default:"10.0"`
|
||||||
MandatoryStruct mandatoryStruct `json:"mandatoryStruct" required:"trues"` // MATCH /required should be 'true' or 'false'/
|
MandatoryStruct mandatoryStruct `json:"mandatoryStruct" required:"trues"` // MATCH /required should be "true" or "false" in required tag/
|
||||||
MandatoryStruct2 mandatoryStruct `json:"mandatoryStruct2" required:"true"`
|
MandatoryStruct2 mandatoryStruct `json:"mandatoryStruct2" required:"true"`
|
||||||
MandatoryStruct4 mandatoryStruct `json:"mandatoryStruct4" required:"false"`
|
MandatoryStruct4 mandatoryStruct `json:"mandatoryStruct4" required:"false"`
|
||||||
OptionalStruct *optionalStruct `json:"optionalStruct,omitempty"`
|
OptionalStruct *optionalStruct `json:"optionalStruct,omitempty"`
|
||||||
@@ -24,21 +24,23 @@ type decodeAndValidateRequest struct {
|
|||||||
optionalQuery string `json:"-" querystring:"queryfoo"` // MATCH /tag on not-exported field optionalQuery/
|
optionalQuery string `json:"-" querystring:"queryfoo"` // MATCH /tag on not-exported field optionalQuery/
|
||||||
// No-reg test for bug https://github.com/mgechev/revive/issues/208
|
// No-reg test for bug https://github.com/mgechev/revive/issues/208
|
||||||
Tiret string `json:"-,"`
|
Tiret string `json:"-,"`
|
||||||
BadTiret string `json:"other,"` // MATCH /option can not be empty in JSON tag/
|
BadTiret string `json:"other,"` // MATCH /option can not be empty in json tag/
|
||||||
ForOmitzero string `json:"forOmitZero,omitzero"` // MATCH /unknown option 'omitzero' in JSON tag/
|
ForOmitzero string `json:"forOmitZero,omitzero"` // MATCH /prior Go 1.24, option "omitzero" is unsupported in json tag/
|
||||||
|
// MATCH:30 /option can not be empty in json tag/
|
||||||
|
BadTiret string `json:"other,"` // MATCH /duplicated tag name "other" in json tag/
|
||||||
}
|
}
|
||||||
|
|
||||||
type RangeAllocation struct {
|
type RangeAllocation struct {
|
||||||
metav1.TypeMeta `json:",inline"` // MATCH /unknown option 'inline' in JSON tag/
|
metav1.TypeMeta `json:",inline"` // MATCH /unknown option "inline" in json tag/
|
||||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||||
Range string `json:"range,flow"` // MATCH /unknown option 'flow' in JSON tag/
|
Range string `json:"range,flow"` // MATCH /unknown option "flow" in json tag/
|
||||||
Data []byte `json:"data,inline"` // MATCH /unknown option 'inline' in JSON tag/
|
Data []byte `json:"data,inline"` // MATCH /unknown option "inline" in json tag/
|
||||||
}
|
}
|
||||||
|
|
||||||
type RangeAllocation struct {
|
type RangeAllocation struct {
|
||||||
metav1.TypeMeta `bson:",minsize"`
|
metav1.TypeMeta `bson:",minsize"`
|
||||||
metav1.ObjectMeta `bson:"metadata,omitempty"`
|
metav1.ObjectMeta `bson:"metadata,omitempty"`
|
||||||
Range string `bson:"range,flow"` // MATCH /unknown option 'flow' in BSON tag/
|
Range string `bson:"range,flow"` // MATCH /unknown option "flow" in bson tag/
|
||||||
Data []byte `bson:"data,inline"`
|
Data []byte `bson:"data,inline"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,9 +49,9 @@ type TestContextSpecificTags2 struct {
|
|||||||
B int `asn1:"tag:2"`
|
B int `asn1:"tag:2"`
|
||||||
S string `asn1:"tag:0,utf8"`
|
S string `asn1:"tag:0,utf8"`
|
||||||
Ints []int `asn1:"set"`
|
Ints []int `asn1:"set"`
|
||||||
Version int `asn1:"optional,explicit,default:0,tag:000"` // MATCH /duplicated tag number 0/
|
Version int `asn1:"optional,explicit,default:0,tag:000"` // MATCH /duplicated tag number 0 in asn1 tag/
|
||||||
Time time.Time `asn1:"explicit,tag:4,other"` // MATCH /unknown option 'other' in ASN1 tag/
|
Time time.Time `asn1:"explicit,tag:4,other"` // MATCH /unknown option "other" in asn1 tag/
|
||||||
X int `asn1:"explicit,tag:invalid"` // MATCH /ASN1 tag must be a number, got 'invalid'/
|
X int `asn1:"explicit,tag:invalid"` // MATCH /tag must be a number but is "invalid" in asn1 tag/
|
||||||
}
|
}
|
||||||
|
|
||||||
type VirtualMachineRelocateSpecDiskLocator struct {
|
type VirtualMachineRelocateSpecDiskLocator struct {
|
||||||
@@ -59,31 +61,31 @@ type VirtualMachineRelocateSpecDiskLocator struct {
|
|||||||
Datastore ManagedObjectReference `xml:"datastore,chardata,innerxml"`
|
Datastore ManagedObjectReference `xml:"datastore,chardata,innerxml"`
|
||||||
DiskMoveType string `xml:"diskMoveType,omitempty,comment"`
|
DiskMoveType string `xml:"diskMoveType,omitempty,comment"`
|
||||||
DiskBackingInfo BaseVirtualDeviceBackingInfo `xml:"diskBackingInfo,omitempty,any"`
|
DiskBackingInfo BaseVirtualDeviceBackingInfo `xml:"diskBackingInfo,omitempty,any"`
|
||||||
Profile []BaseVirtualMachineProfileSpec `xml:"profile,omitempty,other"` // MATCH /unknown option 'other' in XML tag/
|
Profile []BaseVirtualMachineProfileSpec `xml:"profile,omitempty,other"` // MATCH /unknown option "other" in xml tag/
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestDuplicatedXMLTags struct {
|
type TestDuplicatedxmlTags struct {
|
||||||
A int `xml:"a"`
|
A int `xml:"a"`
|
||||||
B int `xml:"a"` // MATCH /duplicate tag name: 'a'/
|
B int `xml:"a"` // MATCH /duplicated tag name "a" in xml tag/
|
||||||
C int `xml:"c"`
|
C int `xml:"c"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestDuplicatedBSONTags struct {
|
type TestDuplicatedbsonTags struct {
|
||||||
A int `bson:"b"`
|
A int `bson:"b"`
|
||||||
B int `bson:"b"` // MATCH /duplicate tag name: 'b'/
|
B int `bson:"b"` // MATCH /duplicated tag name "b" in bson tag/
|
||||||
C int `bson:"c"`
|
C int `bson:"c"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestDuplicatedYAMLTags struct {
|
type TestDuplicatedYAMLTags struct {
|
||||||
A int `yaml:"b"`
|
A int `yaml:"b"`
|
||||||
B int `yaml:"c"`
|
B int `yaml:"c"`
|
||||||
C int `yaml:"c"` // MATCH /duplicate tag name: 'c'/
|
C int `yaml:"c"` // MATCH /duplicated tag name "c" in yaml tag/
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestDuplicatedProtobufTags struct {
|
type TestDuplicatedProtobufTags struct {
|
||||||
A int `protobuf:"varint,name=b"`
|
A int `protobuf:"varint,name=b"`
|
||||||
B int `protobuf:"varint,name=c"`
|
B int `protobuf:"varint,name=c"`
|
||||||
C int `protobuf:"varint,name=c"` // MATCH /duplicate tag name: 'c'/
|
C int `protobuf:"varint,name=c"` // MATCH /duplicated tag name "c" in protobuf tag/
|
||||||
}
|
}
|
||||||
|
|
||||||
// test case from
|
// test case from
|
||||||
@@ -107,18 +109,18 @@ type HelmChartArgs struct {
|
|||||||
|
|
||||||
// Test message for holding primitive types.
|
// Test message for holding primitive types.
|
||||||
type Simple struct {
|
type Simple struct {
|
||||||
OBool *bool `protobuf:"varint,1,req,json=oBool"` // MATCH /protobuf tag lacks mandatory option 'name'/
|
OBool *bool `protobuf:"varint,1,req,json=oBool"` // MATCH /mandatory option "name" not found in protobuf tag/
|
||||||
OInt32 *int32 `protobuf:"varint,2,opt,name=o_int32,jsonx=oInt32"` // MATCH /unknown option 'jsonx' in protobuf tag/
|
OInt32 *int32 `protobuf:"varint,2,opt,name=o_int32,jsonx=oInt32"` // MATCH /unknown option "jsonx" in protobuf tag/
|
||||||
OInt32Str *int32 `protobuf:"varint,3,rep,name=o_int32_str,name=oInt32Str"` // MATCH /protobuf tag has duplicated option 'name'/
|
OInt32Str *int32 `protobuf:"varint,3,rep,name=o_int32_str,name=oInt32Str"` // MATCH /duplicated option "name" in protobuf tag/
|
||||||
OInt64 *int64 `protobuf:"varint,4,opt,json=oInt64,name=o_int64,json=oInt64"` // MATCH /protobuf tag has duplicated option 'json'/
|
OInt64 *int64 `protobuf:"varint,4,opt,json=oInt64,name=o_int64,json=oInt64"` // MATCH /duplicated option "json" in protobuf tag/
|
||||||
OSint32Str *int32 `protobuf:"zigzag32,11,opt,name=o_sint32_str,json=oSint32Str"`
|
OSint32Str *int32 `protobuf:"zigzag32,11,opt,name=o_sint32_str,json=oSint32Str"`
|
||||||
OSint64Str *int64 `protobuf:"zigzag64,13,opt,name=o_sint32_str,json=oSint64Str"` // MATCH /duplicate tag name: 'o_sint32_str'/
|
OSint64Str *int64 `protobuf:"zigzag64,13,opt,name=o_sint32_str,json=oSint64Str"` // MATCH /duplicated tag name "o_sint32_str" in protobuf tag/
|
||||||
OFloat *float32 `protobuf:"fixed32,14,opt,name=o_float,json=oFloat"`
|
OFloat *float32 `protobuf:"fixed32,14,opt,name=o_float,json=oFloat"`
|
||||||
ODouble *float64 `protobuf:"fixed64,014,opt,name=o_double,json=oDouble"` // MATCH /duplicated tag number 14/
|
ODouble *float64 `protobuf:"fixed64,014,opt,name=o_double,json=oDouble"` // MATCH /duplicated tag number 14 in protobuf tag/
|
||||||
ODoubleStr *float64 `protobuf:"fixed6,17,opt,name=o_double_str,json=oDoubleStr"` // MATCH /invalid protobuf tag name 'fixed6'/
|
ODoubleStr *float64 `protobuf:"fixed6,17,opt,name=o_double_str,json=oDoubleStr"` // MATCH /invalid tag name "fixed6" in protobuf tag/
|
||||||
OString *string `protobuf:"bytes,18,opt,name=o_string,json=oString"`
|
OString *string `protobuf:"bytes,18,opt,name=o_string,json=oString"`
|
||||||
OString2 *string `protobuf:"bytes,name=ameno"`
|
OString2 *string `protobuf:"bytes,name=ameno"`
|
||||||
OString3 *string `protobuf:"bytes,name=ameno"` // MATCH /duplicate tag name: 'ameno'/
|
OString3 *string `protobuf:"bytes,name=ameno"` // MATCH /duplicated tag name "ameno" in protobuf tag/
|
||||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
XXX_unrecognized []byte `json:"-"`
|
XXX_unrecognized []byte `json:"-"`
|
||||||
XXX_sizecache int32 `json:"-"`
|
XXX_sizecache int32 `json:"-"`
|
||||||
@@ -131,20 +133,20 @@ type RequestQueryOption struct {
|
|||||||
Associations2 []string `url:"associations2,semicolon,omitempty"`
|
Associations2 []string `url:"associations2,semicolon,omitempty"`
|
||||||
Associations3 []string `url:"associations3,space,brackets,omitempty"`
|
Associations3 []string `url:"associations3,space,brackets,omitempty"`
|
||||||
Associations4 []string `url:"associations4,numbered,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/
|
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"`
|
PaginateAssociations bool `url:"paginateAssociations,int,omitempty"`
|
||||||
Archived bool `url:"archived,myURLOption"` // MATCH /unknown option 'myURLOption' in URL tag/
|
Archived bool `url:"archived,myURLOption"` // MATCH /unknown option "myURLOption" in url tag/
|
||||||
IDProperty string `url:"idProperty,omitempty"`
|
IDProperty string `url:"idProperty,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Fields struct {
|
type Fields struct {
|
||||||
Field string `datastore:",noindex,flatten,omitempty"`
|
Field string `datastore:",noindex,flatten,omitempty"`
|
||||||
OtherField string `datastore:",unknownOption"` // MATCH /unknown option 'unknownOption' in Datastore tag/
|
OtherField string `datastore:",unknownOption"` // MATCH /unknown option "unknownOption" in datastore tag/
|
||||||
}
|
}
|
||||||
|
|
||||||
type MapStruct struct {
|
type MapStruct struct {
|
||||||
Field1 string `mapstructure:",squash,reminder,omitempty"`
|
Field1 string `mapstructure:",squash,reminder,omitempty"`
|
||||||
OtherField string `mapstructure:",unknownOption"` // MATCH /unknown option 'unknownOption' in Mapstructure tag/
|
OtherField string `mapstructure:",unknownOption"` // MATCH /unknown option "unknownOption" in mapstructure tag/
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValidateUser struct {
|
type ValidateUser struct {
|
||||||
@@ -152,30 +154,30 @@ type ValidateUser struct {
|
|||||||
Email string `validate:"required,email"`
|
Email string `validate:"required,email"`
|
||||||
Password string `validate:"required,min=8,max=32"`
|
Password string `validate:"required,min=8,max=32"`
|
||||||
Biography string `validate:"min=0,max=1000"`
|
Biography string `validate:"min=0,max=1000"`
|
||||||
DisplayName string `validate:"displayName,min=3,max=32"` // MATCH /unknown option 'displayName' in validate tag/
|
DisplayName string `validate:"displayName,min=3,max=32"` // MATCH /unknown option "displayName" in validate tag/
|
||||||
Complex string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,required"`
|
Complex string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,required"`
|
||||||
BadComplex string `validate:"gt=0,keys,eq=1|eq=2,endkeys,required"` // MATCH /option 'keys' must follow a 'dive' option in validate tag/
|
BadComplex string `validate:"gt=0,keys,eq=1|eq=2,endkeys,required"` // MATCH /option "keys" must follow a "dive" option in validate tag/
|
||||||
BadComplex2 string `validate:"gt=0,dive,eq=1|eq=2,endkeys,required"` // MATCH /option 'endkeys' without a previous 'keys' option in validate tag/
|
BadComplex2 string `validate:"gt=0,dive,eq=1|eq=2,endkeys,required"` // MATCH /option "endkeys" without a previous "keys" option in validate tag/
|
||||||
BadComplex3 string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,endkeys,required"` // MATCH /option 'endkeys' without a previous 'keys' option in validate tag/
|
BadComplex3 string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,endkeys,required"` // MATCH /option "endkeys" without a previous "keys" option in validate tag/
|
||||||
}
|
}
|
||||||
|
|
||||||
type TomlUser struct {
|
type TomlUser struct {
|
||||||
Username string `toml:"username,omitempty"`
|
Username string `toml:"username,omitempty"`
|
||||||
Location string `toml:"location,unknown"` // MATCH /unknown option 'unknown' in TOML tag/
|
Location string `toml:"location,unknown"` // MATCH /unknown option "unknown" in toml tag/
|
||||||
}
|
}
|
||||||
|
|
||||||
type PropertiesTags struct {
|
type PropertiesTags struct {
|
||||||
Field int `properties:"-"`
|
Field int `properties:"-"`
|
||||||
Field int `properties:"myName"`
|
Field int `properties:"myName"`
|
||||||
Field int `properties:"myName,default=15"`
|
Field int `properties:"myName,default=15"`
|
||||||
Field int `properties:"myName,default=sString"` // MATCH /field type and default value type mismatch in properties tag/
|
Field int `properties:"myName,default=sString"` // MATCH /type mismatch between field type and default value type in properties tag/
|
||||||
Field int `properties:",default:15"` // MATCH /unknown option "default:15" in properties tag/
|
Field int `properties:",default:15"` // MATCH /unknown or malformed option "default:15" in properties tag/
|
||||||
Field int `properties:",default=15,default=2"` // MATCH /properties tag accepts only one default option/
|
Field int `properties:",default=15,default=2"` // MATCH /duplicated option "default" in properties tag/
|
||||||
Field time.Time `properties:"date,layout=2006-01-02"`
|
Field time.Time `properties:"date,layout=2006-01-02"`
|
||||||
Field time.Time `properties:",layout=2006-01-02"`
|
Field time.Time `properties:",layout=2006-01-02"`
|
||||||
Field time.Time `properties:"date,layout"` // MATCH /malformed layout option for properties tag/
|
Field time.Time `properties:"date,layout"` // MATCH /unknown or malformed option "layout" in properties tag/
|
||||||
Field time.Time `properties:"date,layout= "` // MATCH /malformed layout option for properties tag/
|
Field time.Time `properties:"date,layout= "` // MATCH /option "layout" not of the form layout=value in properties tag/
|
||||||
Field string `properties:"date,layout=2006-01-02"` // MATCH /layout option is only applicable to fields of type time.Time in properties tag/
|
Field string `properties:"date,layout=2006-01-02"` // MATCH /layout option is only applicable to fields of type time.Time in properties tag/
|
||||||
Field []string `properties:",default=a;b;c"`
|
Field []string `properties:",default=a;b;c"`
|
||||||
Field map[string]string `properties:"myName,omitempty"` // MATCH /unknown option "omitempty" in properties tag/
|
Field map[string]string `properties:"myName,omitempty"` // MATCH /unknown or malformed option "omitempty" in properties tag/
|
||||||
}
|
}
|
||||||
|
|||||||
16
testdata/struct_tag_user_options.go
vendored
16
testdata/struct_tag_user_options.go
vendored
@@ -4,30 +4,30 @@ type RangeAllocation struct {
|
|||||||
metav1.TypeMeta `json:",inline"`
|
metav1.TypeMeta `json:",inline"`
|
||||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||||
Range string `json:"range,outline"`
|
Range string `json:"range,outline"`
|
||||||
Data []byte `json:"data,flow"` // MATCH /unknown option 'flow' in JSON tag/
|
Data []byte `json:"data,flow"` // MATCH /unknown option "flow" in json tag/
|
||||||
}
|
}
|
||||||
|
|
||||||
type RangeAllocation struct {
|
type RangeAllocation struct {
|
||||||
metav1.TypeMeta `bson:",minsize,gnu"`
|
metav1.TypeMeta `bson:",minsize,gnu"`
|
||||||
metav1.ObjectMeta `bson:"metadata,omitempty"`
|
metav1.ObjectMeta `bson:"metadata,omitempty"`
|
||||||
Range string `bson:"range,flow"` // MATCH /unknown option 'flow' in BSON tag/
|
Range string `bson:"range,flow"` // MATCH /unknown option "flow" in bson tag/
|
||||||
Data []byte `bson:"data,inline"`
|
Data []byte `bson:"data,inline"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestQueryOptions struct {
|
type RequestQueryOptions struct {
|
||||||
Properties []string `url:"properties,commmma,omitempty"` // MATCH /unknown option 'commmma' in URL tag/
|
Properties []string `url:"properties,commmma,omitempty"` // MATCH /unknown option "commmma" in url tag/
|
||||||
CustomProperties []string `url:"-"`
|
CustomProperties []string `url:"-"`
|
||||||
Archived bool `url:"archived,myURLOption"`
|
Archived bool `url:"archived,myURLOption"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Fields struct {
|
type Fields struct {
|
||||||
Field string `datastore:",noindex,flatten,omitempty,myDatastoreOption"`
|
Field string `datastore:",noindex,flatten,omitempty,myDatastoreOption"`
|
||||||
OtherField string `datastore:",unknownOption"` // MATCH /unknown option 'unknownOption' in Datastore tag/
|
OtherField string `datastore:",unknownOption"` // MATCH /unknown option "unknownOption" in datastore tag/
|
||||||
}
|
}
|
||||||
|
|
||||||
type MapStruct struct {
|
type MapStruct struct {
|
||||||
Field1 string `mapstructure:",squash,reminder,omitempty,myMapstructureOption"`
|
Field1 string `mapstructure:",squash,reminder,omitempty,myMapstructureOption"`
|
||||||
OtherField string `mapstructure:",unknownOption"` // MATCH /unknown option 'unknownOption' in Mapstructure tag/
|
OtherField string `mapstructure:",unknownOption"` // MATCH /unknown option "unknownOption" in mapstructure tag/
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValidateUser struct {
|
type ValidateUser struct {
|
||||||
@@ -37,9 +37,9 @@ type ValidateUser struct {
|
|||||||
Biography string `validate:"min=0,max=1000"`
|
Biography string `validate:"min=0,max=1000"`
|
||||||
DisplayName string `validate:"displayName,min=3,max=32"`
|
DisplayName string `validate:"displayName,min=3,max=32"`
|
||||||
Complex string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,required"`
|
Complex string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,required"`
|
||||||
BadComplex string `validate:"gt=0,keys,eq=1|eq=2,endkeys,required"` // MATCH /option 'keys' must follow a 'dive' option in validate tag/
|
BadComplex string `validate:"gt=0,keys,eq=1|eq=2,endkeys,required"` // MATCH /option "keys" must follow a "dive" option in validate tag/
|
||||||
BadComplex2 string `validate:"gt=0,dive,eq=1|eq=2,endkeys,required"` // MATCH /option 'endkeys' without a previous 'keys' option in validate tag/
|
BadComplex2 string `validate:"gt=0,dive,eq=1|eq=2,endkeys,required"` // MATCH /option "endkeys" without a previous "keys" option in validate tag/
|
||||||
BadComplex3 string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,endkeys,required"` // MATCH /option 'endkeys' without a previous 'keys' option in validate tag/
|
BadComplex3 string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,endkeys,required"` // MATCH /option "endkeys" without a previous "keys" option in validate tag/
|
||||||
}
|
}
|
||||||
|
|
||||||
type TomlUser struct {
|
type TomlUser struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user