-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #83 from 979156/master
Library structs internalization
- Loading branch information
Showing
4 changed files
with
756 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
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 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 initialized (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 returns an error if the field is not | ||
// settable (not addressable 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 | ||
} | ||
|
||
// Zero sets the field to its zero value. It returns an error if the field is not | ||
// settable (not addressable or not exported). | ||
func (f *Field) Zero() error { | ||
zero := reflect.Zero(f.value.Type()).Interface() | ||
return f.Set(zero) | ||
} | ||
|
||
// 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 | ||
} | ||
|
||
// FieldOk returns the field from a nested struct. The boolean returns whether | ||
// the field was found (true) or not (false). | ||
func (f *Field) FieldOk(name string) (*Field, bool) { | ||
value := &f.value | ||
// value must be settable so we need to make sure it holds the address of the | ||
// variable and not a copy, so we can pass the pointer to strctVal instead of a | ||
// copy (which is not assigned to any variable, hence not settable). | ||
// see "https://blog.golang.org/laws-of-reflection#TOC_8." | ||
if f.value.Kind() != reflect.Ptr { | ||
a := f.value.Addr() | ||
value = &a | ||
} | ||
v := strctVal(value.Interface()) | ||
t := v.Type() | ||
|
||
field, ok := t.FieldByName(name) | ||
if !ok { | ||
return nil, false | ||
} | ||
|
||
return &Field{ | ||
field: field, | ||
value: v.FieldByName(name), | ||
}, true | ||
} |
Oops, something went wrong.