Merge pull request #10 from fatih/tags
structure: stop parsing nested structures via field tag
This commit is contained in:
commit
76f7c2b740
123
structure.go
123
structure.go
@ -21,6 +21,13 @@ var (
|
|||||||
// // Field is ignored by this package.
|
// // Field is ignored by this package.
|
||||||
// Field bool `structure:"-"`
|
// Field bool `structure:"-"`
|
||||||
//
|
//
|
||||||
|
// A value with the option of "omitnested" stops iterating further if the type
|
||||||
|
// is a struct. Example:
|
||||||
|
//
|
||||||
|
// // Field is not processed further by this package.
|
||||||
|
// Field time.Time `structure:"myName,omitnested"`
|
||||||
|
// Field *http.Request `structure:",omitnested"`
|
||||||
|
//
|
||||||
// Note that only exported fields of a struct can be accessed, non exported
|
// Note that only exported fields of a struct can be accessed, non exported
|
||||||
// fields will be neglected. It panics if s's kind is not struct.
|
// fields will be neglected. It panics if s's kind is not struct.
|
||||||
func Map(s interface{}) map[string]interface{} {
|
func Map(s interface{}) map[string]interface{} {
|
||||||
@ -34,18 +41,18 @@ func Map(s interface{}) map[string]interface{} {
|
|||||||
|
|
||||||
var finalVal interface{}
|
var finalVal interface{}
|
||||||
|
|
||||||
if IsStruct(val.Interface()) {
|
tagName, tagOpts := parseTag(field.Tag.Get(DefaultTagName))
|
||||||
|
if tagName != "" {
|
||||||
|
name = tagName
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||||
// look out for embedded structs, and convert them to a
|
// look out for embedded structs, and convert them to a
|
||||||
// map[string]interface{} too
|
// map[string]interface{} too
|
||||||
finalVal = Map(val.Interface())
|
finalVal = Map(val.Interface())
|
||||||
} else {
|
} else {
|
||||||
finalVal = val.Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
// override if the user passed a structure tag value
|
finalVal = val.Interface()
|
||||||
// ignore if the user passed the "-" value
|
|
||||||
if tag := field.Tag.Get(DefaultTagName); tag != "" {
|
|
||||||
name = tag
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out[name] = finalVal
|
out[name] = finalVal
|
||||||
@ -61,15 +68,26 @@ func Map(s interface{}) map[string]interface{} {
|
|||||||
// // Field is ignored by this package.
|
// // Field is ignored by this package.
|
||||||
// Field int `structure:"-"`
|
// Field int `structure:"-"`
|
||||||
//
|
//
|
||||||
|
// A value with the option of "omitnested" stops iterating further if the type
|
||||||
|
// is a struct. Example:
|
||||||
|
//
|
||||||
|
// // Field is not processed further by this package.
|
||||||
|
// Field time.Time `structure:"myName,omitnested"`
|
||||||
|
// Field *http.Request `structure:",omitnested"`
|
||||||
|
//
|
||||||
// Note that only exported fields of a struct can be accessed, non exported
|
// Note that only exported fields of a struct can be accessed, non exported
|
||||||
// fields will be neglected. It panics if s's kind is not struct.
|
// fields will be neglected. It panics if s's kind is not struct.
|
||||||
func Values(s interface{}) []interface{} {
|
func Values(s interface{}) []interface{} {
|
||||||
v, fields := strctInfo(s)
|
v, fields := strctInfo(s)
|
||||||
|
|
||||||
t := make([]interface{}, 0)
|
t := make([]interface{}, 0)
|
||||||
|
|
||||||
for _, field := range fields {
|
for _, field := range fields {
|
||||||
val := v.FieldByName(field.Name)
|
val := v.FieldByName(field.Name)
|
||||||
if IsStruct(val.Interface()) {
|
|
||||||
|
_, tagOpts := parseTag(field.Tag.Get(DefaultTagName))
|
||||||
|
|
||||||
|
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||||
// look out for embedded structs, and convert them to a
|
// look out for embedded structs, and convert them to a
|
||||||
// []interface{} to be added to the final values slice
|
// []interface{} to be added to the final values slice
|
||||||
for _, embeddedVal := range Values(val.Interface()) {
|
for _, embeddedVal := range Values(val.Interface()) {
|
||||||
@ -84,6 +102,44 @@ func Values(s interface{}) []interface{} {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fields returns a slice of field names. A struct tag with the content of "-"
|
||||||
|
// ignores the checking of that particular field. Example:
|
||||||
|
//
|
||||||
|
// // Field is ignored by this package.
|
||||||
|
// Field bool `structure:"-"`
|
||||||
|
//
|
||||||
|
// A value with the option of "omitnested" stops iterating further if the type
|
||||||
|
// is a struct. Example:
|
||||||
|
//
|
||||||
|
// // Field is not processed further by this package.
|
||||||
|
// Field time.Time `structure:"myName,omitnested"`
|
||||||
|
// Field *http.Request `structure:",omitnested"`
|
||||||
|
//
|
||||||
|
// Note that only exported fields of a struct can be accessed, non exported
|
||||||
|
// fields will be neglected. It panics if s's kind is not struct.
|
||||||
|
func Fields(s interface{}) []string {
|
||||||
|
v, fields := strctInfo(s)
|
||||||
|
|
||||||
|
keys := make([]string, 0)
|
||||||
|
for _, field := range fields {
|
||||||
|
val := v.FieldByName(field.Name)
|
||||||
|
|
||||||
|
_, tagOpts := parseTag(field.Tag.Get(DefaultTagName))
|
||||||
|
|
||||||
|
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||||
|
// look out for embedded structs, and convert them to a
|
||||||
|
// []string to be added to the final values slice
|
||||||
|
for _, embeddedVal := range Fields(val.Interface()) {
|
||||||
|
keys = append(keys, embeddedVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keys = append(keys, field.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
// IsZero returns true if all fields in a struct is a zero value (not
|
// IsZero returns true if all fields in a struct is a zero value (not
|
||||||
// initialized) A struct tag with the content of "-" ignores the checking of
|
// initialized) A struct tag with the content of "-" ignores the checking of
|
||||||
// that particular field. Example:
|
// that particular field. Example:
|
||||||
@ -91,6 +147,13 @@ func Values(s interface{}) []interface{} {
|
|||||||
// // Field is ignored by this package.
|
// // Field is ignored by this package.
|
||||||
// Field bool `structure:"-"`
|
// Field bool `structure:"-"`
|
||||||
//
|
//
|
||||||
|
// A value with the option of "omitnested" stops iterating further if the type
|
||||||
|
// is a struct. Example:
|
||||||
|
//
|
||||||
|
// // Field is not processed further by this package.
|
||||||
|
// Field time.Time `structure:"myName,omitnested"`
|
||||||
|
// Field *http.Request `structure:",omitnested"`
|
||||||
|
//
|
||||||
// Note that only exported fields of a struct can be accessed, non exported
|
// Note that only exported fields of a struct can be accessed, non exported
|
||||||
// fields will be neglected. It panics if s's kind is not struct.
|
// fields will be neglected. It panics if s's kind is not struct.
|
||||||
func IsZero(s interface{}) bool {
|
func IsZero(s interface{}) bool {
|
||||||
@ -99,7 +162,9 @@ func IsZero(s interface{}) bool {
|
|||||||
for _, field := range fields {
|
for _, field := range fields {
|
||||||
val := v.FieldByName(field.Name)
|
val := v.FieldByName(field.Name)
|
||||||
|
|
||||||
if IsStruct(val.Interface()) {
|
_, tagOpts := parseTag(field.Tag.Get(DefaultTagName))
|
||||||
|
|
||||||
|
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||||
ok := IsZero(val.Interface())
|
ok := IsZero(val.Interface())
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
@ -129,6 +194,13 @@ func IsZero(s interface{}) bool {
|
|||||||
// // Field is ignored by this package.
|
// // Field is ignored by this package.
|
||||||
// Field bool `structure:"-"`
|
// Field bool `structure:"-"`
|
||||||
//
|
//
|
||||||
|
// A value with the option of "omitnested" stops iterating further if the type
|
||||||
|
// is a struct. Example:
|
||||||
|
//
|
||||||
|
// // Field is not processed further by this package.
|
||||||
|
// Field time.Time `structure:"myName,omitnested"`
|
||||||
|
// Field *http.Request `structure:",omitnested"`
|
||||||
|
//
|
||||||
// Note that only exported fields of a struct can be accessed, non exported
|
// Note that only exported fields of a struct can be accessed, non exported
|
||||||
// fields will be neglected. It panics if s's kind is not struct.
|
// fields will be neglected. It panics if s's kind is not struct.
|
||||||
func HasZero(s interface{}) bool {
|
func HasZero(s interface{}) bool {
|
||||||
@ -136,7 +208,10 @@ func HasZero(s interface{}) bool {
|
|||||||
|
|
||||||
for _, field := range fields {
|
for _, field := range fields {
|
||||||
val := v.FieldByName(field.Name)
|
val := v.FieldByName(field.Name)
|
||||||
if IsStruct(val.Interface()) {
|
|
||||||
|
_, tagOpts := parseTag(field.Tag.Get(DefaultTagName))
|
||||||
|
|
||||||
|
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||||
ok := HasZero(val.Interface())
|
ok := HasZero(val.Interface())
|
||||||
if ok {
|
if ok {
|
||||||
return true
|
return true
|
||||||
@ -159,34 +234,6 @@ func HasZero(s interface{}) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fields returns a slice of field names. A struct tag with the content of "-"
|
|
||||||
// ignores the checking of that particular field. Example:
|
|
||||||
//
|
|
||||||
// // Field is ignored by this package.
|
|
||||||
// Field bool `structure:"-"`
|
|
||||||
//
|
|
||||||
// Note that only exported fields of a struct can be accessed, non exported
|
|
||||||
// fields will be neglected. It panics if s's kind is not struct.
|
|
||||||
func Fields(s interface{}) []string {
|
|
||||||
v, fields := strctInfo(s)
|
|
||||||
|
|
||||||
keys := make([]string, 0)
|
|
||||||
for _, field := range fields {
|
|
||||||
val := v.FieldByName(field.Name)
|
|
||||||
if IsStruct(val.Interface()) {
|
|
||||||
// look out for embedded structs, and convert them to a
|
|
||||||
// []string to be added to the final values slice
|
|
||||||
for _, embeddedVal := range Fields(val.Interface()) {
|
|
||||||
keys = append(keys, embeddedVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keys = append(keys, field.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsStruct returns true if the given variable is a struct or a pointer to
|
// IsStruct returns true if the given variable is a struct or a pointer to
|
||||||
// struct.
|
// struct.
|
||||||
func IsStruct(s interface{}) bool {
|
func IsStruct(s interface{}) bool {
|
||||||
|
|||||||
@ -56,6 +56,36 @@ func ExampleMap_tags() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExampleMap_nested() {
|
||||||
|
// By default field with struct types are processed too. We can stop
|
||||||
|
// processing them via "omitnested" tag option.
|
||||||
|
type Server struct {
|
||||||
|
Name string `structure:"server_name"`
|
||||||
|
ID int32 `structure:"server_id"`
|
||||||
|
Time time.Time `structure:"time,omitnested"` // do not convert to map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
const shortForm = "2006-Jan-02"
|
||||||
|
t, _ := time.Parse("2006-Jan-02", "2013-Feb-03")
|
||||||
|
|
||||||
|
s := &Server{
|
||||||
|
Name: "Zeynep",
|
||||||
|
ID: 789012,
|
||||||
|
Time: t,
|
||||||
|
}
|
||||||
|
|
||||||
|
m := Map(s)
|
||||||
|
|
||||||
|
// access them by the custom tags defined above
|
||||||
|
fmt.Printf("%v\n", m["server_name"])
|
||||||
|
fmt.Printf("%v\n", m["server_id"])
|
||||||
|
fmt.Printf("%v\n", m["time"].(time.Time))
|
||||||
|
// Output:
|
||||||
|
// Zeynep
|
||||||
|
// 789012
|
||||||
|
// 2013-02-03 00:00:00 +0000 UTC
|
||||||
|
}
|
||||||
|
|
||||||
func ExampleValues() {
|
func ExampleValues() {
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Name string
|
Name string
|
||||||
@ -76,6 +106,35 @@ func ExampleValues() {
|
|||||||
// Values: [Fatih 135790 false]
|
// Values: [Fatih 135790 false]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExampleValues_tags() {
|
||||||
|
type Location struct {
|
||||||
|
City string
|
||||||
|
Country string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
Name string
|
||||||
|
ID int32
|
||||||
|
Enabled bool
|
||||||
|
Location Location `structure:"-"` // values from location are not included anymore
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &Server{
|
||||||
|
Name: "Fatih",
|
||||||
|
ID: 135790,
|
||||||
|
Enabled: false,
|
||||||
|
Location: Location{City: "Ankara", Country: "Turkey"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let get all values from the struct s. Note that we don't include values
|
||||||
|
// from the Location field
|
||||||
|
m := Values(s)
|
||||||
|
|
||||||
|
fmt.Printf("Values: %+v\n", m)
|
||||||
|
// Output:
|
||||||
|
// Values: [Fatih 135790 false]
|
||||||
|
}
|
||||||
|
|
||||||
func ExampleFields() {
|
func ExampleFields() {
|
||||||
type Access struct {
|
type Access struct {
|
||||||
Name string
|
Name string
|
||||||
@ -96,6 +155,33 @@ func ExampleFields() {
|
|||||||
// Fields: [Name LastAccessed Number]
|
// Fields: [Name LastAccessed Number]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExampleFields_nested() {
|
||||||
|
type Person struct {
|
||||||
|
Name string
|
||||||
|
Number int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Access struct {
|
||||||
|
Person Person `structure:",omitnested"`
|
||||||
|
HasPermission bool
|
||||||
|
LastAccessed time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &Access{
|
||||||
|
Person: Person{Name: "fatih", Number: 1234567},
|
||||||
|
LastAccessed: time.Now(),
|
||||||
|
HasPermission: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's get all fields from the struct s. Note that we don't include the
|
||||||
|
// fields from the Person field anymore due to "omitnested" tag option.
|
||||||
|
m := Fields(s)
|
||||||
|
|
||||||
|
fmt.Printf("Fields: %+v\n", m)
|
||||||
|
// Output:
|
||||||
|
// Fields: [Person HasPermission LastAccessed]
|
||||||
|
}
|
||||||
|
|
||||||
func ExampleIsZero() {
|
func ExampleIsZero() {
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Name string
|
Name string
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package structure
|
|||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMapNonStruct(t *testing.T) {
|
func TestMapNonStruct(t *testing.T) {
|
||||||
@ -145,6 +146,37 @@ func TestMap_CustomTag(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMap_OmitNested(t *testing.T) {
|
||||||
|
type A struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
Time time.Time `structure:",omitnested"`
|
||||||
|
}
|
||||||
|
a := A{Time: time.Now()}
|
||||||
|
|
||||||
|
type B struct {
|
||||||
|
Desc string
|
||||||
|
A A
|
||||||
|
}
|
||||||
|
b := &B{A: a}
|
||||||
|
|
||||||
|
m := Map(b)
|
||||||
|
|
||||||
|
in, ok := m["A"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Error("Map nested structs is not available in the map")
|
||||||
|
}
|
||||||
|
|
||||||
|
// should not happen
|
||||||
|
if _, ok := in["Time"].(map[string]interface{}); ok {
|
||||||
|
t.Error("Map nested struct should omit recursiving parsing of Time")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := in["Time"].(time.Time); !ok {
|
||||||
|
t.Error("Map nested struct should stop parsing of Time at is current value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMap_Nested(t *testing.T) {
|
func TestMap_Nested(t *testing.T) {
|
||||||
type A struct {
|
type A struct {
|
||||||
Name string
|
Name string
|
||||||
@ -246,6 +278,45 @@ func TestValues(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValues_OmitNested(t *testing.T) {
|
||||||
|
type A struct {
|
||||||
|
Name string
|
||||||
|
Value int
|
||||||
|
}
|
||||||
|
|
||||||
|
a := A{
|
||||||
|
Name: "example",
|
||||||
|
Value: 123,
|
||||||
|
}
|
||||||
|
|
||||||
|
type B struct {
|
||||||
|
A A `structure:",omitnested"`
|
||||||
|
C int
|
||||||
|
}
|
||||||
|
b := &B{A: a, C: 123}
|
||||||
|
|
||||||
|
s := Values(b)
|
||||||
|
|
||||||
|
if len(s) != 2 {
|
||||||
|
t.Errorf("Values of omitted nested struct should be not counted")
|
||||||
|
}
|
||||||
|
|
||||||
|
inSlice := func(val interface{}) bool {
|
||||||
|
for _, v := range s {
|
||||||
|
if reflect.DeepEqual(v, val) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, val := range []interface{}{123, a} {
|
||||||
|
if !inSlice(val) {
|
||||||
|
t.Errorf("Values should have the value %v", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestValues_Nested(t *testing.T) {
|
func TestValues_Nested(t *testing.T) {
|
||||||
type A struct {
|
type A struct {
|
||||||
Name string
|
Name string
|
||||||
@ -340,6 +411,43 @@ func TestFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFields_OmitNested(t *testing.T) {
|
||||||
|
type A struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
Number int
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
|
a := A{Name: "example"}
|
||||||
|
|
||||||
|
type B struct {
|
||||||
|
A A `structure:",omitnested"`
|
||||||
|
C int
|
||||||
|
}
|
||||||
|
b := &B{A: a, C: 123}
|
||||||
|
|
||||||
|
s := Fields(b)
|
||||||
|
|
||||||
|
if len(s) != 2 {
|
||||||
|
t.Errorf("Fields should omit nested struct. Expecting 2 got: %d", len(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
inSlice := func(val interface{}) bool {
|
||||||
|
for _, v := range s {
|
||||||
|
if reflect.DeepEqual(v, val) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, val := range []interface{}{"A", "C"} {
|
||||||
|
if !inSlice(val) {
|
||||||
|
t.Errorf("Fields should have the value %v", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFields_Nested(t *testing.T) {
|
func TestFields_Nested(t *testing.T) {
|
||||||
type A struct {
|
type A struct {
|
||||||
Name string
|
Name string
|
||||||
@ -440,6 +548,34 @@ func TestIsZero(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsZero_OmitNested(t *testing.T) {
|
||||||
|
type A struct {
|
||||||
|
Name string
|
||||||
|
D string
|
||||||
|
}
|
||||||
|
a := A{Name: "example"}
|
||||||
|
|
||||||
|
type B struct {
|
||||||
|
A A `structure:",omitnested"`
|
||||||
|
C int
|
||||||
|
}
|
||||||
|
b := &B{A: a, C: 123}
|
||||||
|
|
||||||
|
ok := IsZero(b)
|
||||||
|
if ok {
|
||||||
|
t.Error("IsZero should return false because A, B and C are initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
aZero := A{}
|
||||||
|
bZero := &B{A: aZero}
|
||||||
|
|
||||||
|
ok = IsZero(bZero)
|
||||||
|
if !ok {
|
||||||
|
t.Error("IsZero should return true because neither A nor B is initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestIsZero_Nested(t *testing.T) {
|
func TestIsZero_Nested(t *testing.T) {
|
||||||
type A struct {
|
type A struct {
|
||||||
Name string
|
Name string
|
||||||
@ -539,6 +675,27 @@ func TestHasZero(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHasZero_OmitNested(t *testing.T) {
|
||||||
|
type A struct {
|
||||||
|
Name string
|
||||||
|
D string
|
||||||
|
}
|
||||||
|
a := A{Name: "example"}
|
||||||
|
|
||||||
|
type B struct {
|
||||||
|
A A `structure:",omitnested"`
|
||||||
|
C int
|
||||||
|
}
|
||||||
|
b := &B{A: a, C: 123}
|
||||||
|
|
||||||
|
// Because the Field A inside B is omitted HasZero should return false
|
||||||
|
// because it will stop iterating deeper andnot going to lookup for D
|
||||||
|
ok := HasZero(b)
|
||||||
|
if ok {
|
||||||
|
t.Error("HasZero should return false because A and C are initialized")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHasZero_Nested(t *testing.T) {
|
func TestHasZero_Nested(t *testing.T) {
|
||||||
type A struct {
|
type A struct {
|
||||||
Name string
|
Name string
|
||||||
|
|||||||
40
tags.go
Normal file
40
tags.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package structure
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// tagOptions contains a slice of tag options
|
||||||
|
type tagOptions []string
|
||||||
|
|
||||||
|
// Has returns true if the given optiton is available in tagOptions
|
||||||
|
func (t tagOptions) Has(opt string) bool {
|
||||||
|
for _, tagOpt := range t {
|
||||||
|
if tagOpt == opt {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTag splits a struct field's tag into its name and a list of options
|
||||||
|
// which comes after a name. A tag is in the form of: "name,option1,option2".
|
||||||
|
// The name can be neglectected.
|
||||||
|
func parseTag(tag string) (string, tagOptions) {
|
||||||
|
res := strings.Split(tag, ",")
|
||||||
|
|
||||||
|
// tag = ""
|
||||||
|
if len(res) == 0 {
|
||||||
|
return tag, res
|
||||||
|
}
|
||||||
|
|
||||||
|
// tag = "name"
|
||||||
|
if len(res) == 1 {
|
||||||
|
return tag, res[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// tag is one of followings:
|
||||||
|
// "name,opt"
|
||||||
|
// "name,opt,opt2"
|
||||||
|
// ",opt"
|
||||||
|
return res[0], res[1:]
|
||||||
|
}
|
||||||
45
tags_test.go
Normal file
45
tags_test.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package structure
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestParseTag_Name(t *testing.T) {
|
||||||
|
tags := []struct {
|
||||||
|
tag string
|
||||||
|
has bool
|
||||||
|
}{
|
||||||
|
{"name", true},
|
||||||
|
{"name,opt", true},
|
||||||
|
{"name , opt, opt2", false}, // has a single whitespace
|
||||||
|
{", opt, opt2", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range tags {
|
||||||
|
name, _ := parseTag(tag.tag)
|
||||||
|
|
||||||
|
if (name != "name") && tag.has {
|
||||||
|
t.Errorf("Parse tag should return name: %#v", tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseTag_Opts(t *testing.T) {
|
||||||
|
tags := []struct {
|
||||||
|
opts string
|
||||||
|
has bool
|
||||||
|
}{
|
||||||
|
{"name", false},
|
||||||
|
{"name,opt", true},
|
||||||
|
{"name , opt, opt2", false}, // has a single whitespace
|
||||||
|
{",opt, opt2", true},
|
||||||
|
{", opt3, opt4", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
// search for "opt"
|
||||||
|
for _, tag := range tags {
|
||||||
|
_, opts := parseTag(tag.opts)
|
||||||
|
|
||||||
|
if opts.Has("opt") != tag.has {
|
||||||
|
t.Errorf("Tag opts should have opt: %#v", tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user