Merge pull request #675 from harshavardhana/pr_out_import_quick_key_value_store_from_minio_client_for_persistent_state_files_primarily_for_donut
Import quick key value store from Minio Client for persistent state files, primarily for donutmaster
commit
0cb3f76a91
@ -0,0 +1,23 @@ |
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects) |
||||
*.o |
||||
*.a |
||||
*.so |
||||
|
||||
# Folders |
||||
_obj |
||||
_test |
||||
|
||||
# Architecture specific extensions/prefixes |
||||
*.[568vq] |
||||
[568vq].out |
||||
|
||||
*.cgo1.go |
||||
*.cgo2.c |
||||
_cgo_defun.c |
||||
_cgo_gotypes.go |
||||
_cgo_export.* |
||||
|
||||
_testmain.go |
||||
|
||||
*.exe |
||||
*.test |
@ -0,0 +1,11 @@ |
||||
language: go |
||||
go: 1.3 |
||||
before_install: |
||||
- go get github.com/axw/gocov/gocov |
||||
- go get github.com/mattn/goveralls |
||||
- go get code.google.com/p/go.tools/cmd/cover |
||||
script: |
||||
- $HOME/gopath/bin/goveralls -repotoken $COVERALLS_TOKEN |
||||
env: |
||||
global: |
||||
- secure: hkc+92KPmMFqIH9n4yWdnH1JpZjahmOyDJwpTh8Yl0JieJNG0XEXpOqNao27eA0cLF+UHdyjFeGcPUJKNmgE46AoQjtovt+ICjCXKR2yF6S2kKJcUOz/Vd6boZF7qHV06jjxyxOebpID5iSoW6UfFr001bFxpd3jaSLFTzSHWRQ= |
@ -0,0 +1,21 @@ |
||||
The MIT License (MIT) |
||||
|
||||
Copyright (c) 2014 Fatih Arslan |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -0,0 +1,159 @@ |
||||
# Structs [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/structs) [![Build Status](http://img.shields.io/travis/fatih/structs.svg?style=flat-square)](https://travis-ci.org/fatih/structs) [![Coverage Status](http://img.shields.io/coveralls/fatih/structs.svg?style=flat-square)](https://coveralls.io/r/fatih/structs) |
||||
|
||||
Structs contains various utilities to work with Go (Golang) structs. It was |
||||
initially used by me to convert a struct into a `map[string]interface{}`. With |
||||
time I've added other utilities for structs. It's basically a high level |
||||
package based on primitives from the reflect package. Feel free to add new |
||||
functions or improve the existing code. |
||||
|
||||
## Install |
||||
|
||||
```bash |
||||
go get github.com/fatih/structs |
||||
``` |
||||
|
||||
## Usage and Examples |
||||
|
||||
Just like the standard lib `strings`, `bytes` and co packages, `structs` has |
||||
many global functions to manipulate or organize your struct data. Lets define |
||||
and declare a struct: |
||||
|
||||
```go |
||||
type Server struct { |
||||
Name string `json:"name,omitempty"` |
||||
ID int |
||||
Enabled bool |
||||
users []string // not exported |
||||
http.Server // embedded |
||||
} |
||||
|
||||
server := &Server{ |
||||
Name: "gopher", |
||||
ID: 123456, |
||||
Enabled: true, |
||||
} |
||||
``` |
||||
|
||||
```go |
||||
// Convert a struct to a map[string]interface{} |
||||
// => {"Name":"gopher", "ID":123456, "Enabled":true} |
||||
m := structs.Map(server) |
||||
|
||||
// Convert the values of a struct to a []interface{} |
||||
// => ["gopher", 123456, true] |
||||
v := structs.Values(server) |
||||
|
||||
// Convert the values of a struct to a []*Field |
||||
// (see "Field methods" for more info about fields) |
||||
f := structs.Fields(server) |
||||
|
||||
// Return the struct name => "Server" |
||||
n := structs.Name(server) |
||||
|
||||
// Check if any field of a struct is initialized or not. |
||||
h := structs.HasZero(server) |
||||
|
||||
// Check if all fields of a struct is initialized or not. |
||||
z := structs.IsZero(server) |
||||
|
||||
// Check if server is a struct or a pointer to struct |
||||
i := structs.IsStruct(server) |
||||
``` |
||||
|
||||
### Struct methods |
||||
|
||||
The structs functions can be also used as independent methods by creating a new |
||||
`*structs.Struct`. This is handy if you want to have more control over the |
||||
structs (such as retrieving a single Field). |
||||
|
||||
```go |
||||
// Create a new struct type: |
||||
s := structs.New(server) |
||||
|
||||
m := s.Map() // Get a map[string]interface{} |
||||
v := s.Values() // Get a []interface{} |
||||
f := s.Fields() // Get a []*Field |
||||
f := s.Field(name) // Get a *Field based on the given field name |
||||
f, ok := s.FieldsOk(name) // Get a *Field based on the given field name |
||||
n := s.Name() // Get the struct name |
||||
h := s.HasZero() // Check if any field is initialized |
||||
z := s.IsZero() // Check if all fields are initialized |
||||
``` |
||||
|
||||
### Field methods |
||||
|
||||
We can easily examine a single Field for more detail. Below you can see how we |
||||
get and interact with various field methods: |
||||
|
||||
|
||||
```go |
||||
s := structs.New(server) |
||||
|
||||
// Get the Field struct for the "Name" field |
||||
name := s.Field("Name") |
||||
|
||||
// Get the underlying value, value => "gopher" |
||||
value := name.Value().(string) |
||||
|
||||
// Set the field's value |
||||
name.Set("another gopher") |
||||
|
||||
// Get the field's kind, kind => "string" |
||||
name.Kind() |
||||
|
||||
// Check if the field is exported or not |
||||
if name.IsExported() { |
||||
fmt.Println("Name field is exported") |
||||
} |
||||
|
||||
// Check if the value is a zero value, such as "" for string, 0 for int |
||||
if !name.IsZero() { |
||||
fmt.Println("Name is initialized") |
||||
} |
||||
|
||||
// Check if the field is an anonymous (embedded) field |
||||
if !name.IsEmbedded() { |
||||
fmt.Println("Name is not an embedded field") |
||||
} |
||||
|
||||
// Get the Field's tag value for tag name "json", tag value => "name,omitempty" |
||||
tagValue := name.Tag("json") |
||||
``` |
||||
|
||||
Nested structs are supported too: |
||||
|
||||
```go |
||||
addrField := s.Field("Server").Field("Addr") |
||||
|
||||
// Get the value for addr |
||||
a := addrField.Value().(string) |
||||
|
||||
// Or get all fields |
||||
httpServer := s.Field("Server").Fields() |
||||
``` |
||||
|
||||
We can also get a slice of Fields from the Struct type to iterate over all |
||||
fields. This is handy if you wish to examine all fields: |
||||
|
||||
```go |
||||
// Convert the fields of a struct to a []*Field |
||||
fields := s.Fields() |
||||
|
||||
for _, f := range fields { |
||||
fmt.Printf("field name: %+v\n", f.Name()) |
||||
|
||||
if f.IsExported() { |
||||
fmt.Printf("value : %+v\n", f.Value()) |
||||
fmt.Printf("is zero : %+v\n", f.IsZero()) |
||||
} |
||||
} |
||||
``` |
||||
|
||||
## Credits |
||||
|
||||
* [Fatih Arslan](https://github.com/fatih) |
||||
* [Cihangir Savas](https://github.com/cihangir) |
||||
|
||||
## License |
||||
|
||||
The MIT License (MIT) - see LICENSE.md for more details |
@ -0,0 +1,126 @@ |
||||
package structs |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"reflect" |
||||
) |
||||
|
||||
var ( |
||||
errNotExported = errors.New("field is not exported") |
||||
errNotSettable = errors.New("field is not settable") |
||||
) |
||||
|
||||
// Field represents a single struct field that encapsulates high level
|
||||
// functions around the field.
|
||||
type Field struct { |
||||
value reflect.Value |
||||
field reflect.StructField |
||||
defaultTag string |
||||
} |
||||
|
||||
// Tag returns the value associated with key in the tag string. If there is no
|
||||
// such key in the tag, Tag returns the empty string.
|
||||
func (f *Field) Tag(key string) string { |
||||
return f.field.Tag.Get(key) |
||||
} |
||||
|
||||
// Value returns the underlying value of of the field. It panics if the field
|
||||
// is not exported.
|
||||
func (f *Field) Value() interface{} { |
||||
return f.value.Interface() |
||||
} |
||||
|
||||
// IsEmbedded returns true if the given field is an anonymous field (embedded)
|
||||
func (f *Field) IsEmbedded() bool { |
||||
return f.field.Anonymous |
||||
} |
||||
|
||||
// IsExported returns true if the given field is exported.
|
||||
func (f *Field) IsExported() bool { |
||||
return f.field.PkgPath == "" |
||||
} |
||||
|
||||
// IsZero returns true if the given field is not initalized (has a zero value).
|
||||
// It panics if the field is not exported.
|
||||
func (f *Field) IsZero() bool { |
||||
zero := reflect.Zero(f.value.Type()).Interface() |
||||
current := f.Value() |
||||
|
||||
return reflect.DeepEqual(current, zero) |
||||
} |
||||
|
||||
// Name returns the name of the given field
|
||||
func (f *Field) Name() string { |
||||
return f.field.Name |
||||
} |
||||
|
||||
// Kind returns the fields kind, such as "string", "map", "bool", etc ..
|
||||
func (f *Field) Kind() reflect.Kind { |
||||
return f.value.Kind() |
||||
} |
||||
|
||||
// Set sets the field to given value v. It retuns an error if the field is not
|
||||
// settable (not addresable or not exported) or if the given value's type
|
||||
// doesn't match the fields type.
|
||||
func (f *Field) Set(val interface{}) error { |
||||
// we can't set unexported fields, so be sure this field is exported
|
||||
if !f.IsExported() { |
||||
return errNotExported |
||||
} |
||||
|
||||
// do we get here? not sure...
|
||||
if !f.value.CanSet() { |
||||
return errNotSettable |
||||
} |
||||
|
||||
given := reflect.ValueOf(val) |
||||
|
||||
if f.value.Kind() != given.Kind() { |
||||
return fmt.Errorf("wrong kind. got: %s want: %s", given.Kind(), f.value.Kind()) |
||||
} |
||||
|
||||
f.value.Set(given) |
||||
return nil |
||||
} |
||||
|
||||
// Fields returns a slice of Fields. This is particular handy to get the fields
|
||||
// of a nested struct . A struct tag with the content of "-" ignores the
|
||||
// checking of that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field *http.Request `structs:"-"`
|
||||
//
|
||||
// It panics if field is not exported or if field's kind is not struct
|
||||
func (f *Field) Fields() []*Field { |
||||
return getFields(f.value, f.defaultTag) |
||||
} |
||||
|
||||
// Field returns the field from a nested struct. It panics if the nested struct
|
||||
// is not exported or if the field was not found.
|
||||
func (f *Field) Field(name string) *Field { |
||||
field, ok := f.FieldOk(name) |
||||
if !ok { |
||||
panic("field not found") |
||||
} |
||||
|
||||
return field |
||||
} |
||||
|
||||
// Field returns the field from a nested struct. The boolean returns true if
|
||||
// the field was found. It panics if the nested struct is not exported or if
|
||||
// the field was not found.
|
||||
func (f *Field) FieldOk(name string) (*Field, bool) { |
||||
v := strctVal(f.value.Interface()) |
||||
t := v.Type() |
||||
|
||||
field, ok := t.FieldByName(name) |
||||
if !ok { |
||||
return nil, false |
||||
} |
||||
|
||||
return &Field{ |
||||
field: field, |
||||
value: v.FieldByName(name), |
||||
}, true |
||||
} |
@ -0,0 +1,324 @@ |
||||
package structs |
||||
|
||||
import ( |
||||
"reflect" |
||||
"testing" |
||||
) |
||||
|
||||
// A test struct that defines all cases
|
||||
type Foo struct { |
||||
A string |
||||
B int `structs:"y"` |
||||
C bool `json:"c"` |
||||
d string // not exported
|
||||
E *Baz |
||||
x string `xml:"x"` // not exported, with tag
|
||||
Y []string |
||||
Z map[string]interface{} |
||||
*Bar // embedded
|
||||
} |
||||
|
||||
type Baz struct { |
||||
A string |
||||
B int |
||||
} |
||||
|
||||
type Bar struct { |
||||
E string |
||||
F int |
||||
g []string |
||||
} |
||||
|
||||
func newStruct() *Struct { |
||||
b := &Bar{ |
||||
E: "example", |
||||
F: 2, |
||||
g: []string{"zeynep", "fatih"}, |
||||
} |
||||
|
||||
// B and x is not initialized for testing
|
||||
f := &Foo{ |
||||
A: "gopher", |
||||
C: true, |
||||
d: "small", |
||||
E: nil, |
||||
Y: []string{"example"}, |
||||
Z: nil, |
||||
} |
||||
f.Bar = b |
||||
|
||||
return New(f) |
||||
} |
||||
|
||||
func TestField_Set(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
f := s.Field("A") |
||||
err := f.Set("fatih") |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
if f.Value().(string) != "fatih" { |
||||
t.Errorf("Setted value is wrong: %s want: %s", f.Value().(string), "fatih") |
||||
} |
||||
|
||||
f = s.Field("Y") |
||||
err = f.Set([]string{"override", "with", "this"}) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
sliceLen := len(f.Value().([]string)) |
||||
if sliceLen != 3 { |
||||
t.Errorf("Setted values slice length is wrong: %d, want: %d", sliceLen, 3) |
||||
} |
||||
|
||||
f = s.Field("C") |
||||
err = f.Set(false) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
if f.Value().(bool) { |
||||
t.Errorf("Setted value is wrong: %s want: %s", f.Value().(bool), false) |
||||
} |
||||
|
||||
// let's pass a different type
|
||||
f = s.Field("A") |
||||
err = f.Set(123) // Field A is of type string, but we are going to pass an integer
|
||||
if err == nil { |
||||
t.Error("Setting a field's value with a different type than the field's type should return an error") |
||||
} |
||||
|
||||
// old value should be still there :)
|
||||
if f.Value().(string) != "fatih" { |
||||
t.Errorf("Setted value is wrong: %s want: %s", f.Value().(string), "fatih") |
||||
} |
||||
|
||||
// let's access an unexported field, which should give an error
|
||||
f = s.Field("d") |
||||
err = f.Set("large") |
||||
if err != errNotExported { |
||||
t.Error(err) |
||||
} |
||||
|
||||
// let's set a pointer to struct
|
||||
b := &Bar{ |
||||
E: "gopher", |
||||
F: 2, |
||||
} |
||||
|
||||
f = s.Field("Bar") |
||||
err = f.Set(b) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
baz := &Baz{ |
||||
A: "helloWorld", |
||||
B: 42, |
||||
} |
||||
|
||||
f = s.Field("E") |
||||
err = f.Set(baz) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
ba := s.Field("E").Value().(*Baz) |
||||
|
||||
if ba.A != "helloWorld" { |
||||
t.Errorf("could not set baz. Got: %s Want: helloWorld", ba.A) |
||||
} |
||||
} |
||||
|
||||
func TestField(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
defer func() { |
||||
err := recover() |
||||
if err == nil { |
||||
t.Error("Retrieveing a non existing field from the struct should panic") |
||||
} |
||||
}() |
||||
|
||||
_ = s.Field("no-field") |
||||
} |
||||
|
||||
func TestField_Kind(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
f := s.Field("A") |
||||
if f.Kind() != reflect.String { |
||||
t.Errorf("Field A has wrong kind: %s want: %s", f.Kind(), reflect.String) |
||||
} |
||||
|
||||
f = s.Field("B") |
||||
if f.Kind() != reflect.Int { |
||||
t.Errorf("Field B has wrong kind: %s want: %s", f.Kind(), reflect.Int) |
||||
} |
||||
|
||||
// unexported
|
||||
f = s.Field("d") |
||||
if f.Kind() != reflect.String { |
||||
t.Errorf("Field d has wrong kind: %s want: %s", f.Kind(), reflect.String) |
||||
} |
||||
} |
||||
|
||||
func TestField_Tag(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
v := s.Field("B").Tag("json") |
||||
if v != "" { |
||||
t.Errorf("Field's tag value of a non existing tag should return empty, got: %s", v) |
||||
} |
||||
|
||||
v = s.Field("C").Tag("json") |
||||
if v != "c" { |
||||
t.Errorf("Field's tag value of the existing field C should return 'c', got: %s", v) |
||||
} |
||||
|
||||
v = s.Field("d").Tag("json") |
||||
if v != "" { |
||||
t.Errorf("Field's tag value of a non exported field should return empty, got: %s", v) |
||||
} |
||||
|
||||
v = s.Field("x").Tag("xml") |
||||
if v != "x" { |
||||
t.Errorf("Field's tag value of a non exported field with a tag should return 'x', got: %s", v) |
||||
} |
||||
|
||||
v = s.Field("A").Tag("json") |
||||
if v != "" { |
||||
t.Errorf("Field's tag value of a existing field without a tag should return empty, got: %s", v) |
||||
} |
||||
} |
||||
|
||||
func TestField_Value(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
v := s.Field("A").Value() |
||||
val, ok := v.(string) |
||||
if !ok { |
||||
t.Errorf("Field's value of a A should be string") |
||||
} |
||||
|
||||
if val != "gopher" { |
||||
t.Errorf("Field's value of a existing tag should return 'gopher', got: %s", val) |
||||
} |
||||
|
||||
defer func() { |
||||
err := recover() |
||||
if err == nil { |
||||
t.Error("Value of a non exported field from the field should panic") |
||||
} |
||||
}() |
||||
|
||||
// should panic
|
||||
_ = s.Field("d").Value() |
||||
} |
||||
|
||||
func TestField_IsEmbedded(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
if !s.Field("Bar").IsEmbedded() { |
||||
t.Errorf("Fields 'Bar' field is an embedded field") |
||||
} |
||||
|
||||
if s.Field("d").IsEmbedded() { |
||||
t.Errorf("Fields 'd' field is not an embedded field") |
||||
} |
||||
} |
||||
|
||||
func TestField_IsExported(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
if !s.Field("Bar").IsExported() { |
||||
t.Errorf("Fields 'Bar' field is an exported field") |
||||
} |
||||
|
||||
if !s.Field("A").IsExported() { |
||||
t.Errorf("Fields 'A' field is an exported field") |
||||
} |
||||
|
||||
if s.Field("d").IsExported() { |
||||
t.Errorf("Fields 'd' field is not an exported field") |
||||
} |
||||
} |
||||
|
||||
func TestField_IsZero(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
if s.Field("A").IsZero() { |
||||
t.Errorf("Fields 'A' field is an initialized field") |
||||
} |
||||
|
||||
if !s.Field("B").IsZero() { |
||||
t.Errorf("Fields 'B' field is not an initialized field") |
||||
} |
||||
} |
||||
|
||||
func TestField_Name(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
if s.Field("A").Name() != "A" { |
||||
t.Errorf("Fields 'A' field should have the name 'A'") |
||||
} |
||||
} |
||||
|
||||
func TestField_Field(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
e := s.Field("Bar").Field("E") |
||||
|
||||
val, ok := e.Value().(string) |
||||
if !ok { |
||||
t.Error("The value of the field 'e' inside 'Bar' struct should be string") |
||||
} |
||||
|
||||
if val != "example" { |
||||
t.Errorf("The value of 'e' should be 'example, got: %s", val) |
||||
} |
||||
|
||||
defer func() { |
||||
err := recover() |
||||
if err == nil { |
||||
t.Error("Field of a non existing nested struct should panic") |
||||
} |
||||
}() |
||||
|
||||
_ = s.Field("Bar").Field("e") |
||||
} |
||||
|
||||
func TestField_Fields(t *testing.T) { |
||||
s := newStruct() |
||||
fields := s.Field("Bar").Fields() |
||||
|
||||
if len(fields) != 3 { |
||||
t.Errorf("We expect 3 fields in embedded struct, was: %d", len(fields)) |
||||
} |
||||
} |
||||
|
||||
func TestField_FieldOk(t *testing.T) { |
||||
s := newStruct() |
||||
|
||||
b, ok := s.FieldOk("Bar") |
||||
if !ok { |
||||
t.Error("The field 'Bar' should exists.") |
||||
} |
||||
|
||||
e, ok := b.FieldOk("E") |
||||
if !ok { |
||||
t.Error("The field 'E' should exists.") |
||||
} |
||||
|
||||
val, ok := e.Value().(string) |
||||
if !ok { |
||||
t.Error("The value of the field 'e' inside 'Bar' struct should be string") |
||||
} |
||||
|
||||
if val != "example" { |
||||
t.Errorf("The value of 'e' should be 'example, got: %s", val) |
||||
} |
||||
} |
@ -0,0 +1,422 @@ |
||||
// Package structs contains various utilities functions to work with structs.
|
||||
package structs |
||||
|
||||
import "reflect" |
||||
|
||||
var ( |
||||
// DefaultTagName is the default tag name for struct fields which provides
|
||||
// a more granular to tweak certain structs. Lookup the necessary functions
|
||||
// for more info.
|
||||
DefaultTagName = "structs" // struct's field default tag name
|
||||
) |
||||
|
||||
// Struct encapsulates a struct type to provide several high level functions
|
||||
// around the struct.
|
||||
type Struct struct { |
||||
raw interface{} |
||||
value reflect.Value |
||||
TagName string |
||||
} |
||||
|
||||
// New returns a new *Struct with the struct s. It panics if the s's kind is
|
||||
// not struct.
|
||||
func New(s interface{}) *Struct { |
||||
return &Struct{ |
||||
raw: s, |
||||
value: strctVal(s), |
||||
TagName: DefaultTagName, |
||||
} |
||||
} |
||||
|
||||
// Map converts the given struct to a map[string]interface{}, where the keys
|
||||
// of the map are the field names and the values of the map the associated
|
||||
// values of the fields. The default key string is the struct field name but
|
||||
// can be changed in the struct field's tag value. The "structs" key in the
|
||||
// struct's field tag value is the key name. Example:
|
||||
//
|
||||
// // Field appears in map as key "myName".
|
||||
// Name string `structs:"myName"`
|
||||
//
|
||||
// A tag value with the content of "-" ignores that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// A tag 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 `structs:"myName,omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// A tag value with the option of "omitempty" ignores that particular field if
|
||||
// the field value is empty. Example:
|
||||
//
|
||||
// // Field appears in map as key "myName", but the field is
|
||||
// // skipped if empty.
|
||||
// Field string `structs:"myName,omitempty"`
|
||||
//
|
||||
// // Field appears in map as key "Field" (the default), but
|
||||
// // the field is skipped if empty.
|
||||
// Field string `structs:",omitempty"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected.
|
||||
func (s *Struct) Map() map[string]interface{} { |
||||
out := make(map[string]interface{}) |
||||
|
||||
fields := s.structFields() |
||||
|
||||
for _, field := range fields { |
||||
name := field.Name |
||||
val := s.value.FieldByName(name) |
||||
|
||||
var finalVal interface{} |
||||
|
||||
tagName, tagOpts := parseTag(field.Tag.Get(s.TagName)) |
||||
if tagName != "" { |
||||
name = tagName |
||||
} |
||||
|
||||
// if the value is a zero value and the field is marked as omitempty do
|
||||
// not include
|
||||
if tagOpts.Has("omitempty") { |
||||
zero := reflect.Zero(val.Type()).Interface() |
||||
current := val.Interface() |
||||
|
||||
if reflect.DeepEqual(current, zero) { |
||||
continue |
||||
} |
||||
} |
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") { |
||||
// look out for embedded structs, and convert them to a
|
||||
// map[string]interface{} too
|
||||
finalVal = Map(val.Interface()) |
||||
} else { |
||||
finalVal = val.Interface() |
||||
} |
||||
|
||||
out[name] = finalVal |
||||
} |
||||
|
||||
return out |
||||
} |
||||
|
||||
// Values converts the given s struct's field values to a []interface{}. A
|
||||
// struct tag with the content of "-" ignores the that particular field.
|
||||
// Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field int `structs:"-"`
|
||||
//
|
||||
// A value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Fields is not processed further by this package.
|
||||
// Field time.Time `structs:",omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// A tag value with the option of "omitempty" ignores that particular field and
|
||||
// is not added to the values if the field value is empty. Example:
|
||||
//
|
||||
// // Field is skipped if empty
|
||||
// Field string `structs:",omitempty"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected.
|
||||
func (s *Struct) Values() []interface{} { |
||||
fields := s.structFields() |
||||
|
||||
var t []interface{} |
||||
|
||||
for _, field := range fields { |
||||
val := s.value.FieldByName(field.Name) |
||||
|
||||
_, tagOpts := parseTag(field.Tag.Get(s.TagName)) |
||||
|
||||
// if the value is a zero value and the field is marked as omitempty do
|
||||
// not include
|
||||
if tagOpts.Has("omitempty") { |
||||
zero := reflect.Zero(val.Type()).Interface() |
||||
current := val.Interface() |
||||
|
||||
if reflect.DeepEqual(current, zero) { |
||||
continue |
||||
} |
||||
} |
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") { |
||||
// look out for embedded structs, and convert them to a
|
||||
// []interface{} to be added to the final values slice
|
||||
for _, embeddedVal := range Values(val.Interface()) { |
||||
t = append(t, embeddedVal) |
||||
} |
||||
} else { |
||||
t = append(t, val.Interface()) |
||||
} |
||||
} |
||||
|
||||
return t |
||||
} |
||||
|
||||
// Fields returns a slice of Fields. A struct tag with the content of "-"
|
||||
// ignores the checking of that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// It panics if s's kind is not struct.
|
||||
func (s *Struct) Fields() []*Field { |
||||
return getFields(s.value, s.TagName) |
||||
} |
||||
|
||||
func getFields(v reflect.Value, tagName string) []*Field { |
||||
if v.Kind() == reflect.Ptr { |
||||
v = v.Elem() |
||||
} |
||||
|
||||
t := v.Type() |
||||
|
||||
var fields []*Field |
||||
|
||||
for i := 0; i < t.NumField(); i++ { |
||||
field := t.Field(i) |
||||
|
||||
if tag := field.Tag.Get(tagName); tag == "-" { |
||||
continue |
||||
} |
||||
|
||||
f := &Field{ |
||||
field: field, |
||||
value: v.FieldByName(field.Name), |
||||
} |
||||
|
||||
fields = append(fields, f) |
||||
|
||||
} |
||||
|
||||
return fields |
||||
} |
||||
|
||||
// Field returns a new Field struct that provides several high level functions
|
||||
// around a single struct field entity. It panics if the field is not found.
|
||||
func (s *Struct) Field(name string) *Field { |
||||
f, ok := s.FieldOk(name) |
||||
if !ok { |
||||
panic("field not found") |
||||
} |
||||
|
||||
return f |
||||
} |
||||
|
||||
// Field returns a new Field struct that provides several high level functions
|
||||
// around a single struct field entity. The boolean returns true if the field
|
||||
// was found.
|
||||
func (s *Struct) FieldOk(name string) (*Field, bool) { |
||||
t := s.value.Type() |
||||
|
||||
field, ok := t.FieldByName(name) |
||||
if !ok { |
||||
return nil, false |
||||
} |
||||
|
||||
return &Field{ |
||||
field: field, |
||||
value: s.value.FieldByName(name), |
||||
defaultTag: s.TagName, |
||||
}, true |
||||
} |
||||
|
||||
// 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
|
||||
// that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// 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 `structs:"myName,omitnested"`
|
||||
// Field *http.Request `structs:",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 (s *Struct) IsZero() bool { |
||||
fields := s.structFields() |
||||
|
||||
for _, field := range fields { |
||||
val := s.value.FieldByName(field.Name) |
||||
|
||||
_, tagOpts := parseTag(field.Tag.Get(s.TagName)) |
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") { |
||||
ok := IsZero(val.Interface()) |
||||
if !ok { |
||||
return false |
||||
} |
||||
|
||||
continue |
||||
} |
||||
|
||||
// zero value of the given field, such as "" for string, 0 for int
|
||||
zero := reflect.Zero(val.Type()).Interface() |
||||
|
||||
// current value of the given field
|
||||
current := val.Interface() |
||||
|
||||
if !reflect.DeepEqual(current, zero) { |
||||
return false |
||||
} |
||||
} |
||||
|
||||
return true |
||||
} |
||||
|
||||
// HasZero returns true if a field in a struct is not initialized (zero value).
|
||||
// A struct tag with the content of "-" ignores the checking of that particular
|
||||
// field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// 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 `structs:"myName,omitnested"`
|
||||
// Field *http.Request `structs:",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 (s *Struct) HasZero() bool { |
||||
fields := s.structFields() |
||||
|
||||
for _, field := range fields { |
||||
val := s.value.FieldByName(field.Name) |
||||
|
||||
_, tagOpts := parseTag(field.Tag.Get(s.TagName)) |
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") { |
||||
ok := HasZero(val.Interface()) |
||||
if ok { |
||||
return true |
||||
} |
||||
|
||||
continue |
||||
} |
||||
|
||||
// zero value of the given field, such as "" for string, 0 for int
|
||||
zero := reflect.Zero(val.Type()).Interface() |
||||
|
||||
// current value of the given field
|
||||
current := val.Interface() |
||||
|
||||
if reflect.DeepEqual(current, zero) { |
||||
return true |
||||
} |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
// Name returns the structs's type name within its package. For more info refer
|
||||
// to Name() function.
|
||||
func (s *Struct) Name() string { |
||||
return s.value.Type().Name() |
||||
} |
||||
|
||||
// structFields returns the exported struct fields for a given s struct. This
|
||||
// is a convenient helper method to avoid duplicate code in some of the
|
||||
// functions.
|
||||
func (s *Struct) structFields() []reflect.StructField { |
||||
t := s.value.Type() |
||||
|
||||
var f []reflect.StructField |
||||
|
||||
for i := 0; i < t.NumField(); i++ { |
||||
field := t.Field(i) |
||||
// we can't access the value of unexported fields
|
||||
if field.PkgPath != "" { |
||||
continue |
||||
} |
||||
|
||||
// don't check if it's omitted
|
||||
if tag := field.Tag.Get(s.TagName); tag == "-" { |
||||
continue |
||||
} |
||||
|
||||
f = append(f, field) |
||||
} |
||||
|
||||
return f |
||||
} |
||||
|
||||
func strctVal(s interface{}) reflect.Value { |
||||
v := reflect.ValueOf(s) |
||||
|
||||
// if pointer get the underlying element≤
|
||||
if v.Kind() == reflect.Ptr { |
||||
v = v.Elem() |
||||
} |
||||
|
||||
if v.Kind() != reflect.Struct { |
||||
panic("not struct") |
||||
} |
||||
|
||||
return v |
||||
} |
||||
|
||||
// Map converts the given struct to a map[string]interface{}. For more info
|
||||
// refer to Struct types Map() method. It panics if s's kind is not struct.
|
||||
func Map(s interface{}) map[string]interface{} { |
||||
return New(s).Map() |
||||
} |
||||
|
||||
// Values converts the given struct to a []interface{}. For more info refer to
|
||||
// Struct types Values() method. It panics if s's kind is not struct.
|
||||
func Values(s interface{}) []interface{} { |
||||
return New(s).Values() |
||||
} |
||||
|
||||
// Fields returns a slice of *Field. For more info refer to Struct types
|
||||
// Fields() method. It panics if s's kind is not struct.
|
||||
func Fields(s interface{}) []*Field { |
||||
return New(s).Fields() |
||||
} |
||||
|
||||
// IsZero returns true if all fields is equal to a zero value. For more info
|
||||
// refer to Struct types IsZero() method. It panics if s's kind is not struct.
|
||||
func IsZero(s interface{}) bool { |
||||
return New(s).IsZero() |
||||
} |
||||
|
||||
// HasZero returns true if any field is equal to a zero value. For more info
|
||||
// refer to Struct types HasZero() method. It panics if s's kind is not struct.
|
||||
func HasZero(s interface{}) bool { |
||||
return New(s).HasZero() |
||||
} |
||||
|
||||
// IsStruct returns true if the given variable is a struct or a pointer to
|
||||
// struct.
|
||||
func IsStruct(s interface{}) bool { |
||||
v := reflect.ValueOf(s) |
||||
if v.Kind() == reflect.Ptr { |
||||
v = v.Elem() |
||||
} |
||||
|
||||
// uninitialized zero value of a struct
|
||||
if v.Kind() == reflect.Invalid { |
||||
return false |
||||
} |
||||
|
||||
return v.Kind() == reflect.Struct |
||||
} |
||||
|
||||
// Name returns the structs's type name within its package. It returns an
|
||||
// empty string for unnamed types. It panics if s's kind is not struct.
|
||||
func Name(s interface{}) string { |
||||
return New(s).Name() |
||||
} |
@ -0,0 +1,351 @@ |
||||
package structs |
||||
|
||||
import ( |
||||
"fmt" |
||||
"time" |
||||
) |
||||
|
||||
func ExampleNew() { |
||||
type Server struct { |
||||
Name string |
||||
ID int32 |
||||
Enabled bool |
||||
} |
||||
|
||||
server := &Server{ |
||||
Name: "Arslan", |
||||
ID: 123456, |
||||
Enabled: true, |
||||
} |
||||
|
||||
s := New(server) |
||||
|
||||
fmt.Printf("Name : %v\n", s.Name()) |
||||
fmt.Printf("Values : %v\n", s.Values()) |
||||
fmt.Printf("Value of ID : %v\n", s.Field("ID").Value()) |
||||
// Output:
|
||||
// Name : Server
|
||||
// Values : [Arslan 123456 true]
|
||||
// Value of ID : 123456
|
||||
|
||||
} |
||||
|
||||
func ExampleMap() { |
||||
type Server struct { |
||||
Name string |
||||
ID int32 |
||||
Enabled bool |
||||
} |
||||
|
||||
s := &Server{ |
||||
Name: "Arslan", |
||||
ID: 123456, |
||||
Enabled: true, |
||||
} |
||||
|
||||
m := Map(s) |
||||
|
||||
fmt.Printf("%#v\n", m["Name"]) |
||||
fmt.Printf("%#v\n", m["ID"]) |
||||
fmt.Printf("%#v\n", m["Enabled"]) |
||||
// Output:
|
||||
// "Arslan"
|
||||
// 123456
|
||||
// true
|
||||
|
||||
} |
||||
|
||||
func ExampleMap_tags() { |
||||
// Custom tags can change the map keys instead of using the fields name
|
||||
type Server struct { |
||||
Name string `structs:"server_name"` |
||||
ID int32 `structs:"server_id"` |
||||
Enabled bool `structs:"enabled"` |
||||
} |
||||
|
||||
s := &Server{ |
||||
Name: "Zeynep", |
||||
ID: 789012, |
||||
} |
||||
|
||||
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["enabled"]) |
||||
// Output:
|
||||
// "Zeynep"
|
||||
// 789012
|
||||
// false
|
||||
|
||||
} |
||||
|
||||
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 `structs:"server_name"` |
||||
ID int32 `structs:"server_id"` |
||||
Time time.Time `structs:"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 ExampleMap_omitEmpty() { |
||||
// By default field with struct types of zero values are processed too. We
|
||||
// can stop processing them via "omitempty" tag option.
|
||||
type Server struct { |
||||
Name string `structs:",omitempty"` |
||||
ID int32 `structs:"server_id,omitempty"` |
||||
Location string |
||||
} |
||||
|
||||
// Only add location
|
||||
s := &Server{ |
||||
Location: "Tokyo", |
||||
} |
||||
|
||||
m := Map(s) |
||||
|
||||
// map contains only the Location field
|
||||
fmt.Printf("%v\n", m) |
||||
// Output:
|
||||
// map[Location:Tokyo]
|
||||
} |
||||
|
||||
func ExampleValues() { |
||||
type Server struct { |
||||
Name string |
||||
ID int32 |
||||
Enabled bool |
||||
} |
||||
|
||||
s := &Server{ |
||||
Name: "Fatih", |
||||
ID: 135790, |
||||
Enabled: false, |
||||
} |
||||
|
||||
m := Values(s) |
||||
|
||||
fmt.Printf("Values: %+v\n", m) |
||||
// Output:
|
||||
// Values: [Fatih 135790 false]
|
||||
} |
||||
|
||||
func ExampleValues_omitEmpty() { |
||||
// By default field with struct types of zero values are processed too. We
|
||||
// can stop processing them via "omitempty" tag option.
|
||||
type Server struct { |
||||
Name string `structs:",omitempty"` |
||||
ID int32 `structs:"server_id,omitempty"` |
||||
Location string |
||||
} |
||||
|
||||
// Only add location
|
||||
s := &Server{ |
||||
Location: "Ankara", |
||||
} |
||||
|
||||
m := Values(s) |
||||
|
||||
// values contains only the Location field
|
||||
fmt.Printf("Values: %+v\n", m) |
||||
// Output:
|
||||
// Values: [Ankara]
|
||||
} |
||||
|
||||
func ExampleValues_tags() { |
||||
type Location struct { |
||||
City string |
||||
Country string |
||||
} |
||||
|
||||
type Server struct { |
||||
Name string |
||||
ID int32 |
||||
Enabled bool |
||||
Location Location `structs:"-"` // 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() { |
||||
type Access struct { |
||||
Name string |
||||
LastAccessed time.Time |
||||
Number int |
||||
} |
||||
|
||||
s := &Access{ |
||||
Name: "Fatih", |
||||
LastAccessed: time.Now(), |
||||
Number: 1234567, |
||||
} |
||||
|
||||
fields := Fields(s) |
||||
|
||||
for i, field := range fields { |
||||
fmt.Printf("[%d] %+v\n", i, field.Name()) |
||||
} |
||||
|
||||
// Output:
|
||||
// [0] Name
|
||||
// [1] LastAccessed
|
||||
// [2] Number
|
||||
} |
||||
|
||||
func ExampleFields_nested() { |
||||
type Person struct { |
||||
Name string |
||||
Number int |
||||
} |
||||
|
||||
type Access struct { |
||||
Person Person |
||||
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.
|
||||
fields := Fields(s) |
||||
|
||||
for _, field := range fields { |
||||
if field.Name() == "Person" { |
||||
fmt.Printf("Access.Person.Name: %+v\n", field.Field("Name").Value()) |
||||
} |
||||
} |
||||
|
||||
// Output:
|
||||
// Access.Person.Name: fatih
|
||||
} |
||||
|
||||
func ExampleField() { |
||||
type Person struct { |
||||
Name string |
||||
Number int |
||||
} |
||||
|
||||
type Access struct { |
||||
Person Person |
||||
HasPermission bool |
||||
LastAccessed time.Time |
||||
} |
||||
|
||||
access := &Access{ |
||||
Person: Person{Name: "fatih", Number: 1234567}, |
||||
LastAccessed: time.Now(), |
||||
HasPermission: true, |
||||
} |
||||
|
||||
// Create a new Struct type
|
||||
s := New(access) |
||||
|
||||
// Get the Field type for "Person" field
|
||||
p := s.Field("Person") |
||||
|
||||
// Get the underlying "Name field" and print the value of it
|
||||
name := p.Field("Name") |
||||
|
||||
fmt.Printf("Value of Person.Access.Name: %+v\n", name.Value()) |
||||
|
||||
// Output:
|
||||
// Value of Person.Access.Name: fatih
|
||||
|
||||
} |
||||
|
||||
func ExampleIsZero() { |
||||
type Server struct { |
||||
Name string |
||||
ID int32 |
||||
Enabled bool |
||||
} |
||||
|
||||
// Nothing is initalized
|
||||
a := &Server{} |
||||
isZeroA := IsZero(a) |
||||
|
||||
// Name and Enabled is initialized, but not ID
|
||||
b := &Server{ |
||||
Name: "Golang", |
||||
Enabled: true, |
||||
} |
||||
isZeroB := IsZero(b) |
||||
|
||||
fmt.Printf("%#v\n", isZeroA) |
||||
fmt.Printf("%#v\n", isZeroB) |
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
} |
||||
|
||||
func ExampleHasZero() { |
||||
// Let's define an Access struct. Note that the "Enabled" field is not
|
||||
// going to be checked because we added the "structs" tag to the field.
|
||||
type Access struct { |
||||
Name string |
||||
LastAccessed time.Time |
||||
Number int |
||||
Enabled bool `structs:"-"` |
||||
} |
||||
|
||||
// Name and Number is not initialized.
|
||||
a := &Access{ |
||||
LastAccessed: time.Now(), |
||||
} |
||||
hasZeroA := HasZero(a) |
||||
|
||||
// Name and Number is initialized.
|
||||
b := &Access{ |
||||
Name: "Fatih", |
||||
LastAccessed: time.Now(), |
||||
Number: 12345, |
||||
} |
||||
hasZeroB := HasZero(b) |
||||
|
||||
fmt.Printf("%#v\n", hasZeroA) |
||||
fmt.Printf("%#v\n", hasZeroB) |
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
} |
@ -0,0 +1,847 @@ |
||||
package structs |
||||
|
||||
import ( |
||||
"fmt" |
||||
"reflect" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
func TestMapNonStruct(t *testing.T) { |
||||
foo := []string{"foo"} |
||||
|
||||
defer func() { |
||||
err := recover() |
||||
if err == nil { |
||||
t.Error("Passing a non struct into Map should panic") |
||||
} |
||||
}() |
||||
|
||||
// this should panic. We are going to recover and and test it
|
||||
_ = Map(foo) |
||||
} |
||||
|
||||
func TestStructIndexes(t *testing.T) { |
||||
type C struct { |
||||
something int |
||||
Props map[string]interface{} |
||||
} |
||||
|
||||
defer func() { |
||||
err := recover() |
||||
if err != nil { |
||||
fmt.Printf("err %+v\n", err) |
||||
t.Error("Using mixed indexes should not panic") |
||||
} |
||||
}() |
||||
|
||||
// They should not panic
|
||||
_ = Map(&C{}) |
||||
_ = Fields(&C{}) |
||||
_ = Values(&C{}) |
||||
_ = IsZero(&C{}) |
||||
_ = HasZero(&C{}) |
||||
} |
||||
|
||||
func TestMap(t *testing.T) { |
||||
var T = struct { |
||||
A string |
||||
B int |
||||
C bool |
||||
}{ |
||||
A: "a-value", |
||||
B: 2, |
||||
C: true, |
||||
} |
||||
|
||||
a := Map(T) |
||||
|
||||
if typ := reflect.TypeOf(a).Kind(); typ != reflect.Map { |
||||
t.Errorf("Map should return a map type, got: %v", typ) |
||||
} |
||||
|
||||
// we have three fields
|
||||
if len(a) != 3 { |
||||
t.Errorf("Map should return a map of len 3, got: %d", len(a)) |
||||
} |
||||
|
||||
inMap := func(val interface{}) bool { |
||||
for _, v := range a { |
||||
if reflect.DeepEqual(v, val) { |
||||
return true |
||||
} |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
for _, val := range []interface{}{"a-value", 2, true} { |
||||
if !inMap(val) { |
||||
t.Errorf("Map should have the value %v", val) |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
func TestMap_Tag(t *testing.T) { |
||||
var T = struct { |
||||
A string `structs:"x"` |
||||
B int `structs:"y"` |
||||
C bool `structs:"z"` |
||||
}{ |
||||
A: "a-value", |
||||
B: 2, |
||||
C: true, |
||||
} |
||||
|
||||
a := Map(T) |
||||
|
||||
inMap := func(key interface{}) bool { |
||||
for k := range a { |
||||
if reflect.DeepEqual(k, key) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, key := range []string{"x", "y", "z"} { |
||||
if !inMap(key) { |
||||
t.Errorf("Map should have the key %v", key) |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
func TestMap_CustomTag(t *testing.T) { |
||||
var T = struct { |
||||
A string `dd:"x"` |
||||
B int `dd:"y"` |
||||
C bool `dd:"z"` |
||||
}{ |
||||
A: "a-value", |
||||
B: 2, |
||||
C: true, |
||||
} |
||||
|
||||
s := New(T) |
||||
s.TagName = "dd" |
||||
|
||||
a := s.Map() |
||||
|
||||
inMap := func(key interface{}) bool { |
||||
for k := range a { |
||||
if reflect.DeepEqual(k, key) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, key := range []string{"x", "y", "z"} { |
||||
if !inMap(key) { |
||||
t.Errorf("Map should have the key %v", key) |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
func TestMap_MultipleCustomTag(t *testing.T) { |
||||
var A = struct { |
||||
X string `aa:"ax"` |
||||
}{"a_value"} |
||||
|
||||
aStruct := New(A) |
||||
aStruct.TagName = "aa" |
||||
|
||||
var B = struct { |
||||
X string `bb:"bx"` |
||||
}{"b_value"} |
||||
|
||||
bStruct := New(B) |
||||
bStruct.TagName = "bb" |
||||
|
||||
a, b := aStruct.Map(), bStruct.Map() |
||||
if !reflect.DeepEqual(a, map[string]interface{}{"ax": "a_value"}) { |
||||
t.Error("Map should have field ax with value a_value") |
||||
} |
||||
|
||||
if !reflect.DeepEqual(b, map[string]interface{}{"bx": "b_value"}) { |
||||
t.Error("Map should have field bx with value b_value") |
||||
} |
||||
} |
||||
|
||||
func TestMap_OmitEmpty(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
Value string `structs:",omitempty"` |
||||
Time time.Time `structs:",omitempty"` |
||||
} |
||||
a := A{} |
||||
|
||||
m := Map(a) |
||||
|
||||
_, ok := m["Value"].(map[string]interface{}) |
||||
if ok { |
||||
t.Error("Map should not contain the Value field that is tagged as omitempty") |
||||
} |
||||
|
||||
_, ok = m["Time"].(map[string]interface{}) |
||||
if ok { |
||||
t.Error("Map should not contain the Time field that is tagged as omitempty") |
||||
} |
||||
} |
||||
|
||||
func TestMap_OmitNested(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
Value string |
||||
Time time.Time `structs:",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) { |
||||
type A struct { |
||||
Name string |
||||
} |
||||
a := &A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A *A |
||||
} |
||||
b := &B{A: a} |
||||
|
||||
m := Map(b) |
||||
|
||||
if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map { |
||||
t.Errorf("Map should return a map type, got: %v", typ) |
||||
} |
||||
|
||||
in, ok := m["A"].(map[string]interface{}) |
||||
if !ok { |
||||
t.Error("Map nested structs is not available in the map") |
||||
} |
||||
|
||||
if name := in["Name"].(string); name != "example" { |
||||
t.Errorf("Map nested struct's name field should give example, got: %s", name) |
||||
} |
||||
} |
||||
|
||||
func TestMap_Anonymous(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
} |
||||
a := &A{Name: "example"} |
||||
|
||||
type B struct { |
||||
*A |
||||
} |
||||
b := &B{} |
||||
b.A = a |
||||
|
||||
m := Map(b) |
||||
|
||||
if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map { |
||||
t.Errorf("Map should return a map type, got: %v", typ) |
||||
} |
||||
|
||||
in, ok := m["A"].(map[string]interface{}) |
||||
if !ok { |
||||
t.Error("Embedded structs is not available in the map") |
||||
} |
||||
|
||||
if name := in["Name"].(string); name != "example" { |
||||
t.Errorf("Embedded A struct's Name field should give example, got: %s", name) |
||||
} |
||||
} |
||||
|
||||
func TestStruct(t *testing.T) { |
||||
var T = struct{}{} |
||||
|
||||
if !IsStruct(T) { |
||||
t.Errorf("T should be a struct, got: %T", T) |
||||
} |
||||
|
||||
if !IsStruct(&T) { |
||||
t.Errorf("T should be a struct, got: %T", T) |
||||
} |
||||
|
||||
} |
||||
|
||||
func TestValues(t *testing.T) { |
||||
var T = struct { |
||||
A string |
||||
B int |
||||
C bool |
||||
}{ |
||||
A: "a-value", |
||||
B: 2, |
||||
C: true, |
||||
} |
||||
|
||||
s := Values(T) |
||||
|
||||
if typ := reflect.TypeOf(s).Kind(); typ != reflect.Slice { |
||||
t.Errorf("Values should return a slice type, got: %v", typ) |
||||
} |
||||
|
||||
inSlice := func(val interface{}) bool { |
||||
for _, v := range s { |
||||
if reflect.DeepEqual(v, val) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, val := range []interface{}{"a-value", 2, true} { |
||||
if !inSlice(val) { |
||||
t.Errorf("Values should have the value %v", val) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestValues_OmitEmpty(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
Value int `structs:",omitempty"` |
||||
} |
||||
|
||||
a := A{Name: "example"} |
||||
s := Values(a) |
||||
|
||||
if len(s) != 1 { |
||||
t.Errorf("Values of omitted empty fields should be not counted") |
||||
} |
||||
|
||||
if s[0].(string) != "example" { |
||||
t.Errorf("Values of omitted empty fields should left the value example") |
||||
} |
||||
} |
||||
|
||||
func TestValues_OmitNested(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
Value int |
||||
} |
||||
|
||||
a := A{ |
||||
Name: "example", |
||||
Value: 123, |
||||
} |
||||
|
||||
type B struct { |
||||
A A `structs:",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) { |
||||
type A struct { |
||||
Name string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A A |
||||
C int |
||||
} |
||||
b := &B{A: a, C: 123} |
||||
|
||||
s := Values(b) |
||||
|
||||
inSlice := func(val interface{}) bool { |
||||
for _, v := range s { |
||||
if reflect.DeepEqual(v, val) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, val := range []interface{}{"example", 123} { |
||||
if !inSlice(val) { |
||||
t.Errorf("Values should have the value %v", val) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestValues_Anonymous(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A |
||||
C int |
||||
} |
||||
b := &B{C: 123} |
||||
b.A = a |
||||
|
||||
s := Values(b) |
||||
|
||||
inSlice := func(val interface{}) bool { |
||||
for _, v := range s { |
||||
if reflect.DeepEqual(v, val) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, val := range []interface{}{"example", 123} { |
||||
if !inSlice(val) { |
||||
t.Errorf("Values should have the value %v", val) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestFields(t *testing.T) { |
||||
var T = struct { |
||||
A string |
||||
B int |
||||
C bool |
||||
}{ |
||||
A: "a-value", |
||||
B: 2, |
||||
C: true, |
||||
} |
||||
|
||||
s := Fields(T) |
||||
|
||||
if len(s) != 3 { |
||||
t.Errorf("Fields should return a slice of len 3, got: %d", len(s)) |
||||
} |
||||
|
||||
inSlice := func(val string) bool { |
||||
for _, v := range s { |
||||
if reflect.DeepEqual(v.Name(), val) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, val := range []string{"A", "B", "C"} { |
||||
if !inSlice(val) { |
||||
t.Errorf("Fields should have the value %v", val) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestFields_OmitNested(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
Enabled bool |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A A |
||||
C int |
||||
Value string `structs:"-"` |
||||
Number int |
||||
} |
||||
b := &B{A: a, C: 123} |
||||
|
||||
s := Fields(b) |
||||
|
||||
if len(s) != 3 { |
||||
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.Name(), 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_Anonymous(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A |
||||
C int |
||||
} |
||||
b := &B{C: 123} |
||||
b.A = a |
||||
|
||||
s := Fields(b) |
||||
|
||||
inSlice := func(val interface{}) bool { |
||||
for _, v := range s { |
||||
if reflect.DeepEqual(v.Name(), val) { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
for _, val := range []interface{}{"A", "C"} { |
||||
if !inSlice(val) { |
||||
t.Errorf("Fields should have the value %v", val) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestIsZero(t *testing.T) { |
||||
var T = struct { |
||||
A string |
||||
B int |
||||
C bool `structs:"-"` |
||||
D []string |
||||
}{} |
||||
|
||||
ok := IsZero(T) |
||||
if !ok { |
||||
t.Error("IsZero should return true because none of the fields are initialized.") |
||||
} |
||||
|
||||
var X = struct { |
||||
A string |
||||
F *bool |
||||
}{ |
||||
A: "a-value", |
||||
} |
||||
|
||||
ok = IsZero(X) |
||||
if ok { |
||||
t.Error("IsZero should return false because A is initialized") |
||||
} |
||||
|
||||
var Y = struct { |
||||
A string |
||||
B int |
||||
}{ |
||||
A: "a-value", |
||||
B: 123, |
||||
} |
||||
|
||||
ok = IsZero(Y) |
||||
if ok { |
||||
t.Error("IsZero should return false because A and B is initialized") |
||||
} |
||||
} |
||||
|
||||
func TestIsZero_OmitNested(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
D string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A A `structs:",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) { |
||||
type A struct { |
||||
Name string |
||||
D string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A A |
||||
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_Anonymous(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
D string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A |
||||
C int |
||||
} |
||||
b := &B{C: 123} |
||||
b.A = a |
||||
|
||||
ok := IsZero(b) |
||||
if ok { |
||||
t.Error("IsZero should return false because A, B and C are initialized") |
||||
} |
||||
|
||||
aZero := A{} |
||||
bZero := &B{} |
||||
bZero.A = aZero |
||||
|
||||
ok = IsZero(bZero) |
||||
if !ok { |
||||
t.Error("IsZero should return true because neither A nor B is initialized") |
||||
} |
||||
} |
||||
|
||||
func TestHasZero(t *testing.T) { |
||||
var T = struct { |
||||
A string |
||||
B int |
||||
C bool `structs:"-"` |
||||
D []string |
||||
}{ |
||||
A: "a-value", |
||||
B: 2, |
||||
} |
||||
|
||||
ok := HasZero(T) |
||||
if !ok { |
||||
t.Error("HasZero should return true because A and B are initialized.") |
||||
} |
||||
|
||||
var X = struct { |
||||
A string |
||||
F *bool |
||||
}{ |
||||
A: "a-value", |
||||
} |
||||
|
||||
ok = HasZero(X) |
||||
if !ok { |
||||
t.Error("HasZero should return true because A is initialized") |
||||
} |
||||
|
||||
var Y = struct { |
||||
A string |
||||
B int |
||||
}{ |
||||
A: "a-value", |
||||
B: 123, |
||||
} |
||||
|
||||
ok = HasZero(Y) |
||||
if ok { |
||||
t.Error("HasZero should return false because A and B is initialized") |
||||
} |
||||
} |
||||
|
||||
func TestHasZero_OmitNested(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
D string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A A `structs:",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) { |
||||
type A struct { |
||||
Name string |
||||
D string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A A |
||||
C int |
||||
} |
||||
b := &B{A: a, C: 123} |
||||
|
||||
ok := HasZero(b) |
||||
if !ok { |
||||
t.Error("HasZero should return true because D is not initialized") |
||||
} |
||||
} |
||||
|
||||
func TestHasZero_Anonymous(t *testing.T) { |
||||
type A struct { |
||||
Name string |
||||
D string |
||||
} |
||||
a := A{Name: "example"} |
||||
|
||||
type B struct { |
||||
A |
||||
C int |
||||
} |
||||
b := &B{C: 123} |
||||
b.A = a |
||||
|
||||
ok := HasZero(b) |
||||
if !ok { |
||||
t.Error("HasZero should return false because D is not initialized") |
||||
} |
||||
} |
||||
|
||||
func TestName(t *testing.T) { |
||||
type Foo struct { |
||||
A string |
||||
B bool |
||||
} |
||||
f := &Foo{} |
||||
|
||||
n := Name(f) |
||||
if n != "Foo" { |
||||
t.Errorf("Name should return Foo, got: %s", n) |
||||
} |
||||
|
||||
unnamed := struct{ Name string }{Name: "Cihangir"} |
||||
m := Name(unnamed) |
||||
if m != "" { |
||||
t.Errorf("Name should return empty string for unnamed struct, got: %s", n) |
||||
} |
||||
|
||||
defer func() { |
||||
err := recover() |
||||
if err == nil { |
||||
t.Error("Name should panic if a non struct is passed") |
||||
} |
||||
}() |
||||
|
||||
Name([]string{}) |
||||
} |
||||
|
||||
func TestNestedNilPointer(t *testing.T) { |
||||
type Collar struct { |
||||
Engraving string |
||||
} |
||||
|
||||
type Dog struct { |
||||
Name string |
||||
Collar *Collar |
||||
} |
||||
|
||||
type Person struct { |
||||
Name string |
||||
Dog *Dog |
||||
} |
||||
|
||||
person := &Person{ |
||||
Name: "John", |
||||
} |
||||
|
||||
personWithDog := &Person{ |
||||
Name: "Ron", |
||||
Dog: &Dog{ |
||||
Name: "Rover", |
||||
}, |
||||
} |
||||
|
||||
personWithDogWithCollar := &Person{ |
||||
Name: "Kon", |
||||
Dog: &Dog{ |
||||
Name: "Ruffles", |
||||
Collar: &Collar{ |
||||
Engraving: "If lost, call Kon", |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
defer func() { |
||||
err := recover() |
||||
if err != nil { |
||||
fmt.Printf("err %+v\n", err) |
||||
t.Error("Internal nil pointer should not panic") |
||||
} |
||||
}() |
||||
|
||||
_ = Map(person) // Panics
|
||||
_ = Map(personWithDog) // Panics
|
||||
_ = Map(personWithDogWithCollar) // Doesn't panic
|
||||
} |
@ -0,0 +1,32 @@ |
||||
package structs |
||||
|
||||
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) { |
||||
// tag is one of followings:
|
||||
// ""
|
||||
// "name"
|
||||
// "name,opt"
|
||||
// "name,opt,opt2"
|
||||
// ",opt"
|
||||
|
||||
res := strings.Split(tag, ",") |
||||
return res[0], res[1:] |
||||
} |
@ -0,0 +1,46 @@ |
||||
package structs |
||||
|
||||
import "testing" |
||||
|
||||
func TestParseTag_Name(t *testing.T) { |
||||
tags := []struct { |
||||
tag string |
||||
has bool |
||||
}{ |
||||
{"", false}, |
||||
{"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) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,229 @@ |
||||
/* |
||||
* Quick - Quick key value store for config files and persistent state files |
||||
* |
||||
* Minio Client (C) 2015 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package quick |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"errors" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"os" |
||||
"reflect" |
||||
"runtime" |
||||
"strings" |
||||
"sync" |
||||
|
||||
"github.com/fatih/structs" |
||||
"github.com/minio/minio/pkg/iodine" |
||||
) |
||||
|
||||
// Config - generic config interface functions
|
||||
type Config interface { |
||||
String() string |
||||
Version() string |
||||
Save(string) error |
||||
Load(string) error |
||||
Data() interface{} |
||||
Diff(Config) ([]structs.Field, error) |
||||
DeepDiff(Config) ([]structs.Field, error) |
||||
} |
||||
|
||||
// config - implements quick.Config interface
|
||||
type config struct { |
||||
data *interface{} |
||||
lock *sync.RWMutex |
||||
} |
||||
|
||||
// CheckData - checks the validity of config data. Data sould be of type struct and contain a string type field called "Version"
|
||||
func CheckData(data interface{}) error { |
||||
if !structs.IsStruct(data) { |
||||
return iodine.New(errors.New("Invalid argument type. Expecing \"struct\" type."), nil) |
||||
} |
||||
|
||||
st := structs.New(data) |
||||
f, ok := st.FieldOk("Version") |
||||
if !ok { |
||||
return iodine.New(fmt.Errorf("Invalid type of struct argument. No [%s.Version] field found.", st.Name()), nil) |
||||
} |
||||
|
||||
if f.Kind() != reflect.String { |
||||
return iodine.New(fmt.Errorf("Invalid type of struct argument. Expecting \"string\" type [%s.Version] field.", st.Name()), nil) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// New - instantiate a new config
|
||||
func New(data interface{}) (Config, error) { |
||||
err := CheckData(data) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
d := new(config) |
||||
d.data = &data |
||||
d.lock = new(sync.RWMutex) |
||||
return d, nil |
||||
} |
||||
|
||||
// Version returns the current config file format version
|
||||
func (d config) Version() string { |
||||
st := structs.New(*d.data) |
||||
|
||||
f, ok := st.FieldOk("Version") |
||||
if !ok { |
||||
return "" |
||||
} |
||||
|
||||
val := f.Value() |
||||
ver, ok := val.(string) |
||||
if ok { |
||||
return ver |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
// String converts JSON config to printable string
|
||||
func (d config) String() string { |
||||
configBytes, _ := json.MarshalIndent(*d.data, "", "\t") |
||||
return string(configBytes) |
||||
} |
||||
|
||||
// Save writes config data in JSON format to a file.
|
||||
func (d config) Save(filename string) (err error) { |
||||
d.lock.Lock() |
||||
defer d.lock.Unlock() |
||||
|
||||
jsonData, err := json.MarshalIndent(d.data, "", "\t") |
||||
if err != nil { |
||||
return iodine.New(err, nil) |
||||
} |
||||
|
||||
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) |
||||
if err != nil { |
||||
return iodine.New(err, nil) |
||||
} |
||||
defer file.Close() |
||||
|
||||
if runtime.GOOS == "windows" { |
||||
jsonData = []byte(strings.Replace(string(jsonData), "\n", "\r\n", -1)) |
||||
} |
||||
_, err = file.Write(jsonData) |
||||
if err != nil { |
||||
return iodine.New(err, nil) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Load - loads JSON config from file and merge with currently set values
|
||||
func (d *config) Load(filename string) (err error) { |
||||
(*d).lock.Lock() |
||||
defer (*d).lock.Unlock() |
||||
|
||||
_, err = os.Stat(filename) |
||||
if err != nil { |
||||
return iodine.New(err, nil) |
||||
} |
||||
|
||||
fileData, err := ioutil.ReadFile(filename) |
||||
if err != nil { |
||||
return iodine.New(err, nil) |
||||
} |
||||
|
||||
if runtime.GOOS == "windows" { |
||||
fileData = []byte(strings.Replace(string(fileData), "\r\n", "\n", -1)) |
||||
} |
||||
|
||||
err = json.Unmarshal(fileData, (*d).data) |
||||
if err != nil { |
||||
return iodine.New(err, nil) |
||||
} |
||||
|
||||
err = CheckData(*(*d).data) |
||||
if err != nil { |
||||
return iodine.New(err, nil) |
||||
} |
||||
|
||||
st := structs.New(*(*d).data) |
||||
f, ok := st.FieldOk("Version") |
||||
if !ok { |
||||
return iodine.New(fmt.Errorf("Argument struct [%s] does not contain field \"Version\".", st.Name()), nil) |
||||
} |
||||
|
||||
if (*d).Version() != f.Value() { |
||||
return iodine.New(errors.New("Version mismatch"), nil) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// Data - grab internal data map for reading
|
||||
func (d config) Data() interface{} { |
||||
return *d.data |
||||
} |
||||
|
||||
//Diff - list fields that are in A but not in B
|
||||
func (d config) Diff(c Config) (fields []structs.Field, err error) { |
||||
err = CheckData(c.Data()) |
||||
if err != nil { |
||||
return []structs.Field{}, iodine.New(err, nil) |
||||
} |
||||
|
||||
currFields := structs.Fields(d.Data()) |
||||
newFields := structs.Fields(c.Data()) |
||||
|
||||
found := false |
||||
for _, currField := range currFields { |
||||
found = false |
||||
for _, newField := range newFields { |
||||
if reflect.DeepEqual(currField.Name(), newField.Name()) { |
||||
found = true |
||||
} |
||||
} |
||||
if !found { |
||||
fields = append(fields, *currField) |
||||
} |
||||
} |
||||
return fields, nil |
||||
} |
||||
|
||||
//DeepDiff - list fields in A that are missing or not equal to fields in B
|
||||
func (d config) DeepDiff(c Config) (fields []structs.Field, err error) { |
||||
err = CheckData(c.Data()) |
||||
if err != nil { |
||||
return []structs.Field{}, iodine.New(err, nil) |
||||
} |
||||
|
||||
currFields := structs.Fields(d.Data()) |
||||
newFields := structs.Fields(c.Data()) |
||||
|
||||
found := false |
||||
for _, currField := range currFields { |
||||
found = false |
||||
for _, newField := range newFields { |
||||
if reflect.DeepEqual(currField.Value(), newField.Value()) { |
||||
found = true |
||||
} |
||||
} |
||||
if !found { |
||||
fields = append(fields, *currField) |
||||
} |
||||
} |
||||
return fields, nil |
||||
} |
@ -0,0 +1,145 @@ |
||||
/* |
||||
* Quick - Quick key value store for config files and persistent state files |
||||
* |
||||
* Minio Client (C) 2015 Minio, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package quick |
||||
|
||||
import ( |
||||
"os" |
||||
"testing" |
||||
|
||||
. "github.com/minio/check" |
||||
) |
||||
|
||||
func Test(t *testing.T) { TestingT(t) } |
||||
|
||||
type MySuite struct{} |
||||
|
||||
var _ = Suite(&MySuite{}) |
||||
|
||||
func (s *MySuite) TestCheckData(c *C) { |
||||
err := CheckData(nil) |
||||
c.Assert(err, Not(IsNil)) |
||||
|
||||
type myStructBad struct { |
||||
User string |
||||
Password string |
||||
Folders []string |
||||
} |
||||
saveMeBad := myStructBad{"guest", "nopassword", []string{"Work", "Documents", "Music"}} |
||||
err = CheckData(&saveMeBad) |
||||
c.Assert(err, Not(IsNil)) |
||||
|
||||
type myStructGood struct { |
||||
Version string |
||||
User string |
||||
Password string |
||||
Folders []string |
||||
} |
||||
|
||||
saveMeGood := myStructGood{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}} |
||||
err = CheckData(&saveMeGood) |
||||
c.Assert(err, IsNil) |
||||
} |
||||
|
||||
func (s *MySuite) TestSaveLoad(c *C) { |
||||
defer os.RemoveAll("test.json") |
||||
type myStruct struct { |
||||
Version string |
||||
User string |
||||
Password string |
||||
Folders []string |
||||
} |
||||
saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}} |
||||
config, err := New(&saveMe) |
||||
c.Assert(err, IsNil) |
||||
c.Assert(config, Not(IsNil)) |
||||
config.Save("test.json") |
||||
|
||||
loadMe := myStruct{Version: "1"} |
||||
newConfig, err := New(&loadMe) |
||||
c.Assert(err, IsNil) |
||||
c.Assert(newConfig, Not(IsNil)) |
||||
newConfig.Load("test.json") |
||||
|
||||
c.Assert(config.Data(), DeepEquals, newConfig.Data()) |
||||
c.Assert(config.Data(), DeepEquals, &loadMe) |
||||
|
||||
mismatch := myStruct{"1.1", "guest", "nopassword", []string{"Work", "Documents", "Music"}} |
||||
c.Assert(newConfig.Data(), Not(DeepEquals), &mismatch) |
||||
} |
||||
|
||||
func (s *MySuite) TestDiff(c *C) { |
||||
type myStruct struct { |
||||
Version string |
||||
User string |
||||
Password string |
||||
Folders []string |
||||
} |
||||
saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}} |
||||
config, err := New(&saveMe) |
||||
c.Assert(err, IsNil) |
||||
c.Assert(config, Not(IsNil)) |
||||
|
||||
type myNewStruct struct { |
||||
Version string |
||||
// User string
|
||||
Password string |
||||
Folders []string |
||||
} |
||||
|
||||
mismatch := myNewStruct{"1", "nopassword", []string{"Work", "documents", "Music"}} |
||||
newConfig, err := New(&mismatch) |
||||
c.Assert(err, IsNil) |
||||
c.Assert(newConfig, Not(IsNil)) |
||||
|
||||
fields, ok := config.Diff(newConfig) |
||||
c.Assert(ok, IsNil) |
||||
c.Assert(len(fields), Equals, 1) |
||||
|
||||
// Uncomment for debugging
|
||||
// for i, field := range fields {
|
||||
// fmt.Printf("Diff[%d]: %s=%v\n", i, field.Name(), field.Value())
|
||||
// }
|
||||
} |
||||
|
||||
func (s *MySuite) TestDeepDiff(c *C) { |
||||
type myStruct struct { |
||||
Version string |
||||
User string |
||||
Password string |
||||
Folders []string |
||||
} |
||||
saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}} |
||||
config, err := New(&saveMe) |
||||
c.Assert(err, IsNil) |
||||
c.Assert(config, Not(IsNil)) |
||||
|
||||
mismatch := myStruct{"1", "Guest", "nopassword", []string{"Work", "documents", "Music"}} |
||||
newConfig, err := New(&mismatch) |
||||
c.Assert(err, IsNil) |
||||
c.Assert(newConfig, Not(IsNil)) |
||||
|
||||
fields, err := config.DeepDiff(newConfig) |
||||
c.Assert(err, IsNil) |
||||
c.Assert(len(fields), Equals, 2) |
||||
|
||||
// Uncomment for debugging
|
||||
// for i, field := range fields {
|
||||
// fmt.Printf("DeepDiff[%d]: %s=%v\n", i, field.Name(), field.Value())
|
||||
// }
|
||||
} |
Loading…
Reference in new issue