mirror of
https://github.com/mgechev/revive.git
synced 2025-11-23 22:04:49 +02:00
feature: support cbor struct tag in struct-tag rule (#1527)
This commit is contained in:
@@ -1293,6 +1293,7 @@ The list of [supported tags](https://go.dev/wiki/Well-known-struct-tags):
|
|||||||
| ------------- | ------------------------------------------------------------------------ |
|
| ------------- | ------------------------------------------------------------------------ |
|
||||||
| `asn1` | <https://pkg.go.dev/encoding/asn1> |
|
| `asn1` | <https://pkg.go.dev/encoding/asn1> |
|
||||||
| `bson` | <https://pkg.go.dev/go.mongodb.org/mongo-driver/bson> |
|
| `bson` | <https://pkg.go.dev/go.mongodb.org/mongo-driver/bson> |
|
||||||
|
| `cbor` | <https://pkg.go.dev/github.com/fxamacker/cbor/v2> |
|
||||||
| `datastore` | <https://pkg.go.dev/cloud.google.com/go/datastore> |
|
| `datastore` | <https://pkg.go.dev/cloud.google.com/go/datastore> |
|
||||||
| `default` | The type of "default" must match the type of the field. |
|
| `default` | The type of "default" must match the type of the field. |
|
||||||
| `json` | <https://pkg.go.dev/encoding/json> |
|
| `json` | <https://pkg.go.dev/encoding/json> |
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type tagKey string
|
|||||||
const (
|
const (
|
||||||
keyASN1 tagKey = "asn1"
|
keyASN1 tagKey = "asn1"
|
||||||
keyBSON tagKey = "bson"
|
keyBSON tagKey = "bson"
|
||||||
|
keyCbor tagKey = "cbor"
|
||||||
keyCodec tagKey = "codec"
|
keyCodec tagKey = "codec"
|
||||||
keyDatastore tagKey = "datastore"
|
keyDatastore tagKey = "datastore"
|
||||||
keyDefault tagKey = "default"
|
keyDefault tagKey = "default"
|
||||||
@@ -45,6 +46,7 @@ type tagChecker func(checkCtx *checkContext, tag *structtag.Tag, field *ast.Fiel
|
|||||||
var tagCheckers = map[tagKey]tagChecker{
|
var tagCheckers = map[tagKey]tagChecker{
|
||||||
keyASN1: checkASN1Tag,
|
keyASN1: checkASN1Tag,
|
||||||
keyBSON: checkBSONTag,
|
keyBSON: checkBSONTag,
|
||||||
|
keyCbor: checkCborTag,
|
||||||
keyCodec: checkCodecTag,
|
keyCodec: checkCodecTag,
|
||||||
keyDatastore: checkDatastoreTag,
|
keyDatastore: checkDatastoreTag,
|
||||||
keyDefault: checkDefaultTag,
|
keyDefault: checkDefaultTag,
|
||||||
@@ -385,6 +387,66 @@ func checkBSONTag(checkCtx *checkContext, tag *structtag.Tag, _ *ast.Field) (mes
|
|||||||
return "", true
|
return "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkCborTag(checkCtx *checkContext, tag *structtag.Tag, _ *ast.Field) (message string, succeeded bool) {
|
||||||
|
hasToArray := false
|
||||||
|
hasOmitEmptyOrZero := false
|
||||||
|
hasKeyAsInt := false
|
||||||
|
|
||||||
|
for _, opt := range tag.Options {
|
||||||
|
switch opt {
|
||||||
|
case "omitempty", "omitzero":
|
||||||
|
hasOmitEmptyOrZero = true
|
||||||
|
case "toarray":
|
||||||
|
if tag.Name != "" {
|
||||||
|
return `tag name for option "toarray" should be empty`, false
|
||||||
|
}
|
||||||
|
hasToArray = true
|
||||||
|
case "keyasint":
|
||||||
|
intKey, err := strconv.Atoi(tag.Name)
|
||||||
|
if err != nil {
|
||||||
|
return `tag name for option "keyasint" should be an integer`, false
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := checkCtx.usedTagNbr[intKey]
|
||||||
|
if ok {
|
||||||
|
return fmt.Sprintf("duplicated integer key %d", intKey), false
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCtx.usedTagNbr[intKey] = true
|
||||||
|
hasKeyAsInt = true
|
||||||
|
continue
|
||||||
|
|
||||||
|
default:
|
||||||
|
if !checkCtx.isUserDefined(keyCbor, opt) {
|
||||||
|
return fmt.Sprintf(msgUnknownOption, opt), false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicated tag names
|
||||||
|
if tag.Name != "" {
|
||||||
|
_, ok := checkCtx.usedTagName[tag.Name]
|
||||||
|
if ok {
|
||||||
|
return fmt.Sprintf("duplicated tag name %s", tag.Name), false
|
||||||
|
}
|
||||||
|
checkCtx.usedTagName[tag.Name] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for integer tag names without keyasint option
|
||||||
|
if !hasKeyAsInt {
|
||||||
|
_, err := strconv.Atoi(tag.Name)
|
||||||
|
if err == nil {
|
||||||
|
return `integer tag names are only allowed in presence of "keyasint" option`, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasToArray && hasOmitEmptyOrZero {
|
||||||
|
return `options "omitempty" and "omitzero" are ignored in presence of "toarray" option`, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
|
||||||
const structTagCodecSpecialField = "_struct"
|
const structTagCodecSpecialField = "_struct"
|
||||||
|
|
||||||
func checkCodecTag(checkCtx *checkContext, tag *structtag.Tag, field *ast.Field) (message string, succeeded bool) {
|
func checkCodecTag(checkCtx *checkContext, tag *structtag.Tag, field *ast.Field) (message string, succeeded bool) {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ func TestStructTagWithUserOptions(t *testing.T) {
|
|||||||
"toml,unknown",
|
"toml,unknown",
|
||||||
"spanner,mySpannerOption",
|
"spanner,mySpannerOption",
|
||||||
"codec,myCodecOption",
|
"codec,myCodecOption",
|
||||||
|
"cbor,myCborOption",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
21
testdata/struct_tag.go
vendored
21
testdata/struct_tag.go
vendored
@@ -215,3 +215,24 @@ type TestDuplicatedCodecTags struct {
|
|||||||
B int `codec:"field_a"` // MATCH /duplicated tag name "field_a" in codec tag/
|
B int `codec:"field_a"` // MATCH /duplicated tag name "field_a" in codec tag/
|
||||||
C int `codec:"field_c"`
|
C int `codec:"field_c"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Cbor struct {
|
||||||
|
RepeatedStr string `cbor:"errors"`
|
||||||
|
Repeated string `cbor:"1,keyasint"`
|
||||||
|
Useless string `cbor:"-,omitempty"` // MATCH /useless option omitempty for ignored field in cbor tag/
|
||||||
|
Inputs string `cbor:",keyasint"` // MATCH /tag name for option "keyasint" should be an integer in cbor tag/
|
||||||
|
Outputs string `cbor:"inputs,keyasint"` // MATCH /tag name for option "keyasint" should be an integer in cbor tag/
|
||||||
|
Errors string `cbor:"errors,optempty,keyasint"` // MATCH /unknown option "optempty" in cbor tag/
|
||||||
|
Inputs2 string `cbor:"-10,omitempty"` // MATCH /integer tag names are only allowed in presence of "keyasint" option in cbor tag/
|
||||||
|
Outputs2 string `cbor:"-12,toarray"` // MATCH /tag name for option "toarray" should be empty in cbor tag/
|
||||||
|
RepeatedInt string `cbor:"1,keyasint"` // MATCH /duplicated integer key 1 in cbor tag/
|
||||||
|
RepeatedStr string `cbor:"errors,omitempty"` // MATCH /duplicated tag name errors in cbor tag/
|
||||||
|
Useless string `cbor:",toarray,omitempty"` // MATCH /options "omitempty" and "omitzero" are ignored in presence of "toarray" option in cbor tag/
|
||||||
|
Useless2 string `cbor:",omitzero,toarray"` // MATCH /options "omitempty" and "omitzero" are ignored in presence of "toarray" option in cbor tag/
|
||||||
|
// OK
|
||||||
|
InputsOk string `cbor:"8,keyasint"`
|
||||||
|
OutputsOk string `cbor:"-100,keyasint"`
|
||||||
|
ErrorsOk string `cbor:"-1,keyasint"`
|
||||||
|
InputsOk2 string `cbor:"inputs,omitempty"`
|
||||||
|
OutputsOk2 string `cbor:",toarray"`
|
||||||
|
}
|
||||||
|
|||||||
8
testdata/struct_tag_user_options.go
vendored
8
testdata/struct_tag_user_options.go
vendored
@@ -102,3 +102,11 @@ type CodecUserOptions struct {
|
|||||||
ID int `codec:"user_id,myCodecOption"`
|
ID int `codec:"user_id,myCodecOption"`
|
||||||
Name string `codec:"full_name,unknownOption"` // MATCH /unknown option "unknownOption" in codec tag/
|
Name string `codec:"full_name,unknownOption"` // MATCH /unknown option "unknownOption" in codec tag/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CborUserOptions struct {
|
||||||
|
InputsOk string `cbor:"8,keyasint,myCborOption"`
|
||||||
|
OutputsOk string `cbor:"-100,keyasint,unknownOption"` // MATCH /unknown option "unknownOption" in cbor tag/
|
||||||
|
ErrorsOk string `cbor:"-1,keyasint"`
|
||||||
|
InputsOk2 string `cbor:"inputs,omitempty"`
|
||||||
|
OutputsOk2 string `cbor:",toarray"`
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user