mirror of
https://github.com/go-gitea/gitea.git
synced 2024-10-31 08:37:35 -04:00
694 lines
20 KiB
Go
694 lines
20 KiB
Go
|
//
|
||
|
// Copyright (c) 2011-2019 Canonical Ltd
|
||
|
//
|
||
|
// 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 yaml implements YAML support for the Go language.
|
||
|
//
|
||
|
// Source code and other details for the project are available at GitHub:
|
||
|
//
|
||
|
// https://github.com/go-yaml/yaml
|
||
|
//
|
||
|
package yaml
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"reflect"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"unicode/utf8"
|
||
|
)
|
||
|
|
||
|
// The Unmarshaler interface may be implemented by types to customize their
|
||
|
// behavior when being unmarshaled from a YAML document.
|
||
|
type Unmarshaler interface {
|
||
|
UnmarshalYAML(value *Node) error
|
||
|
}
|
||
|
|
||
|
type obsoleteUnmarshaler interface {
|
||
|
UnmarshalYAML(unmarshal func(interface{}) error) error
|
||
|
}
|
||
|
|
||
|
// The Marshaler interface may be implemented by types to customize their
|
||
|
// behavior when being marshaled into a YAML document. The returned value
|
||
|
// is marshaled in place of the original value implementing Marshaler.
|
||
|
//
|
||
|
// If an error is returned by MarshalYAML, the marshaling procedure stops
|
||
|
// and returns with the provided error.
|
||
|
type Marshaler interface {
|
||
|
MarshalYAML() (interface{}, error)
|
||
|
}
|
||
|
|
||
|
// Unmarshal decodes the first document found within the in byte slice
|
||
|
// and assigns decoded values into the out value.
|
||
|
//
|
||
|
// Maps and pointers (to a struct, string, int, etc) are accepted as out
|
||
|
// values. If an internal pointer within a struct is not initialized,
|
||
|
// the yaml package will initialize it if necessary for unmarshalling
|
||
|
// the provided data. The out parameter must not be nil.
|
||
|
//
|
||
|
// The type of the decoded values should be compatible with the respective
|
||
|
// values in out. If one or more values cannot be decoded due to a type
|
||
|
// mismatches, decoding continues partially until the end of the YAML
|
||
|
// content, and a *yaml.TypeError is returned with details for all
|
||
|
// missed values.
|
||
|
//
|
||
|
// Struct fields are only unmarshalled if they are exported (have an
|
||
|
// upper case first letter), and are unmarshalled using the field name
|
||
|
// lowercased as the default key. Custom keys may be defined via the
|
||
|
// "yaml" name in the field tag: the content preceding the first comma
|
||
|
// is used as the key, and the following comma-separated options are
|
||
|
// used to tweak the marshalling process (see Marshal).
|
||
|
// Conflicting names result in a runtime error.
|
||
|
//
|
||
|
// For example:
|
||
|
//
|
||
|
// type T struct {
|
||
|
// F int `yaml:"a,omitempty"`
|
||
|
// B int
|
||
|
// }
|
||
|
// var t T
|
||
|
// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
|
||
|
//
|
||
|
// See the documentation of Marshal for the format of tags and a list of
|
||
|
// supported tag options.
|
||
|
//
|
||
|
func Unmarshal(in []byte, out interface{}) (err error) {
|
||
|
return unmarshal(in, out, false)
|
||
|
}
|
||
|
|
||
|
// A Decoder reads and decodes YAML values from an input stream.
|
||
|
type Decoder struct {
|
||
|
parser *parser
|
||
|
knownFields bool
|
||
|
}
|
||
|
|
||
|
// NewDecoder returns a new decoder that reads from r.
|
||
|
//
|
||
|
// The decoder introduces its own buffering and may read
|
||
|
// data from r beyond the YAML values requested.
|
||
|
func NewDecoder(r io.Reader) *Decoder {
|
||
|
return &Decoder{
|
||
|
parser: newParserFromReader(r),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// KnownFields ensures that the keys in decoded mappings to
|
||
|
// exist as fields in the struct being decoded into.
|
||
|
func (dec *Decoder) KnownFields(enable bool) {
|
||
|
dec.knownFields = enable
|
||
|
}
|
||
|
|
||
|
// Decode reads the next YAML-encoded value from its input
|
||
|
// and stores it in the value pointed to by v.
|
||
|
//
|
||
|
// See the documentation for Unmarshal for details about the
|
||
|
// conversion of YAML into a Go value.
|
||
|
func (dec *Decoder) Decode(v interface{}) (err error) {
|
||
|
d := newDecoder()
|
||
|
d.knownFields = dec.knownFields
|
||
|
defer handleErr(&err)
|
||
|
node := dec.parser.parse()
|
||
|
if node == nil {
|
||
|
return io.EOF
|
||
|
}
|
||
|
out := reflect.ValueOf(v)
|
||
|
if out.Kind() == reflect.Ptr && !out.IsNil() {
|
||
|
out = out.Elem()
|
||
|
}
|
||
|
d.unmarshal(node, out)
|
||
|
if len(d.terrors) > 0 {
|
||
|
return &TypeError{d.terrors}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Decode decodes the node and stores its data into the value pointed to by v.
|
||
|
//
|
||
|
// See the documentation for Unmarshal for details about the
|
||
|
// conversion of YAML into a Go value.
|
||
|
func (n *Node) Decode(v interface{}) (err error) {
|
||
|
d := newDecoder()
|
||
|
defer handleErr(&err)
|
||
|
out := reflect.ValueOf(v)
|
||
|
if out.Kind() == reflect.Ptr && !out.IsNil() {
|
||
|
out = out.Elem()
|
||
|
}
|
||
|
d.unmarshal(n, out)
|
||
|
if len(d.terrors) > 0 {
|
||
|
return &TypeError{d.terrors}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func unmarshal(in []byte, out interface{}, strict bool) (err error) {
|
||
|
defer handleErr(&err)
|
||
|
d := newDecoder()
|
||
|
p := newParser(in)
|
||
|
defer p.destroy()
|
||
|
node := p.parse()
|
||
|
if node != nil {
|
||
|
v := reflect.ValueOf(out)
|
||
|
if v.Kind() == reflect.Ptr && !v.IsNil() {
|
||
|
v = v.Elem()
|
||
|
}
|
||
|
d.unmarshal(node, v)
|
||
|
}
|
||
|
if len(d.terrors) > 0 {
|
||
|
return &TypeError{d.terrors}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Marshal serializes the value provided into a YAML document. The structure
|
||
|
// of the generated document will reflect the structure of the value itself.
|
||
|
// Maps and pointers (to struct, string, int, etc) are accepted as the in value.
|
||
|
//
|
||
|
// Struct fields are only marshalled if they are exported (have an upper case
|
||
|
// first letter), and are marshalled using the field name lowercased as the
|
||
|
// default key. Custom keys may be defined via the "yaml" name in the field
|
||
|
// tag: the content preceding the first comma is used as the key, and the
|
||
|
// following comma-separated options are used to tweak the marshalling process.
|
||
|
// Conflicting names result in a runtime error.
|
||
|
//
|
||
|
// The field tag format accepted is:
|
||
|
//
|
||
|
// `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)`
|
||
|
//
|
||
|
// The following flags are currently supported:
|
||
|
//
|
||
|
// omitempty Only include the field if it's not set to the zero
|
||
|
// value for the type or to empty slices or maps.
|
||
|
// Zero valued structs will be omitted if all their public
|
||
|
// fields are zero, unless they implement an IsZero
|
||
|
// method (see the IsZeroer interface type), in which
|
||
|
// case the field will be excluded if IsZero returns true.
|
||
|
//
|
||
|
// flow Marshal using a flow style (useful for structs,
|
||
|
// sequences and maps).
|
||
|
//
|
||
|
// inline Inline the field, which must be a struct or a map,
|
||
|
// causing all of its fields or keys to be processed as if
|
||
|
// they were part of the outer struct. For maps, keys must
|
||
|
// not conflict with the yaml keys of other struct fields.
|
||
|
//
|
||
|
// In addition, if the key is "-", the field is ignored.
|
||
|
//
|
||
|
// For example:
|
||
|
//
|
||
|
// type T struct {
|
||
|
// F int `yaml:"a,omitempty"`
|
||
|
// B int
|
||
|
// }
|
||
|
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
|
||
|
// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n"
|
||
|
//
|
||
|
func Marshal(in interface{}) (out []byte, err error) {
|
||
|
defer handleErr(&err)
|
||
|
e := newEncoder()
|
||
|
defer e.destroy()
|
||
|
e.marshalDoc("", reflect.ValueOf(in))
|
||
|
e.finish()
|
||
|
out = e.out
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// An Encoder writes YAML values to an output stream.
|
||
|
type Encoder struct {
|
||
|
encoder *encoder
|
||
|
}
|
||
|
|
||
|
// NewEncoder returns a new encoder that writes to w.
|
||
|
// The Encoder should be closed after use to flush all data
|
||
|
// to w.
|
||
|
func NewEncoder(w io.Writer) *Encoder {
|
||
|
return &Encoder{
|
||
|
encoder: newEncoderWithWriter(w),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Encode writes the YAML encoding of v to the stream.
|
||
|
// If multiple items are encoded to the stream, the
|
||
|
// second and subsequent document will be preceded
|
||
|
// with a "---" document separator, but the first will not.
|
||
|
//
|
||
|
// See the documentation for Marshal for details about the conversion of Go
|
||
|
// values to YAML.
|
||
|
func (e *Encoder) Encode(v interface{}) (err error) {
|
||
|
defer handleErr(&err)
|
||
|
e.encoder.marshalDoc("", reflect.ValueOf(v))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Encode encodes value v and stores its representation in n.
|
||
|
//
|
||
|
// See the documentation for Marshal for details about the
|
||
|
// conversion of Go values into YAML.
|
||
|
func (n *Node) Encode(v interface{}) (err error) {
|
||
|
defer handleErr(&err)
|
||
|
e := newEncoder()
|
||
|
defer e.destroy()
|
||
|
e.marshalDoc("", reflect.ValueOf(v))
|
||
|
e.finish()
|
||
|
p := newParser(e.out)
|
||
|
p.textless = true
|
||
|
defer p.destroy()
|
||
|
doc := p.parse()
|
||
|
*n = *doc.Content[0]
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// SetIndent changes the used indentation used when encoding.
|
||
|
func (e *Encoder) SetIndent(spaces int) {
|
||
|
if spaces < 0 {
|
||
|
panic("yaml: cannot indent to a negative number of spaces")
|
||
|
}
|
||
|
e.encoder.indent = spaces
|
||
|
}
|
||
|
|
||
|
// Close closes the encoder by writing any remaining data.
|
||
|
// It does not write a stream terminating string "...".
|
||
|
func (e *Encoder) Close() (err error) {
|
||
|
defer handleErr(&err)
|
||
|
e.encoder.finish()
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func handleErr(err *error) {
|
||
|
if v := recover(); v != nil {
|
||
|
if e, ok := v.(yamlError); ok {
|
||
|
*err = e.err
|
||
|
} else {
|
||
|
panic(v)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type yamlError struct {
|
||
|
err error
|
||
|
}
|
||
|
|
||
|
func fail(err error) {
|
||
|
panic(yamlError{err})
|
||
|
}
|
||
|
|
||
|
func failf(format string, args ...interface{}) {
|
||
|
panic(yamlError{fmt.Errorf("yaml: "+format, args...)})
|
||
|
}
|
||
|
|
||
|
// A TypeError is returned by Unmarshal when one or more fields in
|
||
|
// the YAML document cannot be properly decoded into the requested
|
||
|
// types. When this error is returned, the value is still
|
||
|
// unmarshaled partially.
|
||
|
type TypeError struct {
|
||
|
Errors []string
|
||
|
}
|
||
|
|
||
|
func (e *TypeError) Error() string {
|
||
|
return fmt.Sprintf("yaml: unmarshal errors:\n %s", strings.Join(e.Errors, "\n "))
|
||
|
}
|
||
|
|
||
|
type Kind uint32
|
||
|
|
||
|
const (
|
||
|
DocumentNode Kind = 1 << iota
|
||
|
SequenceNode
|
||
|
MappingNode
|
||
|
ScalarNode
|
||
|
AliasNode
|
||
|
)
|
||
|
|
||
|
type Style uint32
|
||
|
|
||
|
const (
|
||
|
TaggedStyle Style = 1 << iota
|
||
|
DoubleQuotedStyle
|
||
|
SingleQuotedStyle
|
||
|
LiteralStyle
|
||
|
FoldedStyle
|
||
|
FlowStyle
|
||
|
)
|
||
|
|
||
|
// Node represents an element in the YAML document hierarchy. While documents
|
||
|
// are typically encoded and decoded into higher level types, such as structs
|
||
|
// and maps, Node is an intermediate representation that allows detailed
|
||
|
// control over the content being decoded or encoded.
|
||
|
//
|
||
|
// It's worth noting that although Node offers access into details such as
|
||
|
// line numbers, colums, and comments, the content when re-encoded will not
|
||
|
// have its original textual representation preserved. An effort is made to
|
||
|
// render the data plesantly, and to preserve comments near the data they
|
||
|
// describe, though.
|
||
|
//
|
||
|
// Values that make use of the Node type interact with the yaml package in the
|
||
|
// same way any other type would do, by encoding and decoding yaml data
|
||
|
// directly or indirectly into them.
|
||
|
//
|
||
|
// For example:
|
||
|
//
|
||
|
// var person struct {
|
||
|
// Name string
|
||
|
// Address yaml.Node
|
||
|
// }
|
||
|
// err := yaml.Unmarshal(data, &person)
|
||
|
//
|
||
|
// Or by itself:
|
||
|
//
|
||
|
// var person Node
|
||
|
// err := yaml.Unmarshal(data, &person)
|
||
|
//
|
||
|
type Node struct {
|
||
|
// Kind defines whether the node is a document, a mapping, a sequence,
|
||
|
// a scalar value, or an alias to another node. The specific data type of
|
||
|
// scalar nodes may be obtained via the ShortTag and LongTag methods.
|
||
|
Kind Kind
|
||
|
|
||
|
// Style allows customizing the apperance of the node in the tree.
|
||
|
Style Style
|
||
|
|
||
|
// Tag holds the YAML tag defining the data type for the value.
|
||
|
// When decoding, this field will always be set to the resolved tag,
|
||
|
// even when it wasn't explicitly provided in the YAML content.
|
||
|
// When encoding, if this field is unset the value type will be
|
||
|
// implied from the node properties, and if it is set, it will only
|
||
|
// be serialized into the representation if TaggedStyle is used or
|
||
|
// the implicit tag diverges from the provided one.
|
||
|
Tag string
|
||
|
|
||
|
// Value holds the unescaped and unquoted represenation of the value.
|
||
|
Value string
|
||
|
|
||
|
// Anchor holds the anchor name for this node, which allows aliases to point to it.
|
||
|
Anchor string
|
||
|
|
||
|
// Alias holds the node that this alias points to. Only valid when Kind is AliasNode.
|
||
|
Alias *Node
|
||
|
|
||
|
// Content holds contained nodes for documents, mappings, and sequences.
|
||
|
Content []*Node
|
||
|
|
||
|
// HeadComment holds any comments in the lines preceding the node and
|
||
|
// not separated by an empty line.
|
||
|
HeadComment string
|
||
|
|
||
|
// LineComment holds any comments at the end of the line where the node is in.
|
||
|
LineComment string
|
||
|
|
||
|
// FootComment holds any comments following the node and before empty lines.
|
||
|
FootComment string
|
||
|
|
||
|
// Line and Column hold the node position in the decoded YAML text.
|
||
|
// These fields are not respected when encoding the node.
|
||
|
Line int
|
||
|
Column int
|
||
|
}
|
||
|
|
||
|
// IsZero returns whether the node has all of its fields unset.
|
||
|
func (n *Node) IsZero() bool {
|
||
|
return n.Kind == 0 && n.Style == 0 && n.Tag == "" && n.Value == "" && n.Anchor == "" && n.Alias == nil && n.Content == nil &&
|
||
|
n.HeadComment == "" && n.LineComment == "" && n.FootComment == "" && n.Line == 0 && n.Column == 0
|
||
|
}
|
||
|
|
||
|
|
||
|
// LongTag returns the long form of the tag that indicates the data type for
|
||
|
// the node. If the Tag field isn't explicitly defined, one will be computed
|
||
|
// based on the node properties.
|
||
|
func (n *Node) LongTag() string {
|
||
|
return longTag(n.ShortTag())
|
||
|
}
|
||
|
|
||
|
// ShortTag returns the short form of the YAML tag that indicates data type for
|
||
|
// the node. If the Tag field isn't explicitly defined, one will be computed
|
||
|
// based on the node properties.
|
||
|
func (n *Node) ShortTag() string {
|
||
|
if n.indicatedString() {
|
||
|
return strTag
|
||
|
}
|
||
|
if n.Tag == "" || n.Tag == "!" {
|
||
|
switch n.Kind {
|
||
|
case MappingNode:
|
||
|
return mapTag
|
||
|
case SequenceNode:
|
||
|
return seqTag
|
||
|
case AliasNode:
|
||
|
if n.Alias != nil {
|
||
|
return n.Alias.ShortTag()
|
||
|
}
|
||
|
case ScalarNode:
|
||
|
tag, _ := resolve("", n.Value)
|
||
|
return tag
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
return shortTag(n.Tag)
|
||
|
}
|
||
|
|
||
|
func (n *Node) indicatedString() bool {
|
||
|
return n.Kind == ScalarNode &&
|
||
|
(shortTag(n.Tag) == strTag ||
|
||
|
(n.Tag == "" || n.Tag == "!") && n.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0)
|
||
|
}
|
||
|
|
||
|
// SetString is a convenience function that sets the node to a string value
|
||
|
// and defines its style in a pleasant way depending on its content.
|
||
|
func (n *Node) SetString(s string) {
|
||
|
n.Kind = ScalarNode
|
||
|
if utf8.ValidString(s) {
|
||
|
n.Value = s
|
||
|
n.Tag = strTag
|
||
|
} else {
|
||
|
n.Value = encodeBase64(s)
|
||
|
n.Tag = binaryTag
|
||
|
}
|
||
|
if strings.Contains(n.Value, "\n") {
|
||
|
n.Style = LiteralStyle
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// Maintain a mapping of keys to structure field indexes
|
||
|
|
||
|
// The code in this section was copied from mgo/bson.
|
||
|
|
||
|
// structInfo holds details for the serialization of fields of
|
||
|
// a given struct.
|
||
|
type structInfo struct {
|
||
|
FieldsMap map[string]fieldInfo
|
||
|
FieldsList []fieldInfo
|
||
|
|
||
|
// InlineMap is the number of the field in the struct that
|
||
|
// contains an ,inline map, or -1 if there's none.
|
||
|
InlineMap int
|
||
|
|
||
|
// InlineUnmarshalers holds indexes to inlined fields that
|
||
|
// contain unmarshaler values.
|
||
|
InlineUnmarshalers [][]int
|
||
|
}
|
||
|
|
||
|
type fieldInfo struct {
|
||
|
Key string
|
||
|
Num int
|
||
|
OmitEmpty bool
|
||
|
Flow bool
|
||
|
// Id holds the unique field identifier, so we can cheaply
|
||
|
// check for field duplicates without maintaining an extra map.
|
||
|
Id int
|
||
|
|
||
|
// Inline holds the field index if the field is part of an inlined struct.
|
||
|
Inline []int
|
||
|
}
|
||
|
|
||
|
var structMap = make(map[reflect.Type]*structInfo)
|
||
|
var fieldMapMutex sync.RWMutex
|
||
|
var unmarshalerType reflect.Type
|
||
|
|
||
|
func init() {
|
||
|
var v Unmarshaler
|
||
|
unmarshalerType = reflect.ValueOf(&v).Elem().Type()
|
||
|
}
|
||
|
|
||
|
func getStructInfo(st reflect.Type) (*structInfo, error) {
|
||
|
fieldMapMutex.RLock()
|
||
|
sinfo, found := structMap[st]
|
||
|
fieldMapMutex.RUnlock()
|
||
|
if found {
|
||
|
return sinfo, nil
|
||
|
}
|
||
|
|
||
|
n := st.NumField()
|
||
|
fieldsMap := make(map[string]fieldInfo)
|
||
|
fieldsList := make([]fieldInfo, 0, n)
|
||
|
inlineMap := -1
|
||
|
inlineUnmarshalers := [][]int(nil)
|
||
|
for i := 0; i != n; i++ {
|
||
|
field := st.Field(i)
|
||
|
if field.PkgPath != "" && !field.Anonymous {
|
||
|
continue // Private field
|
||
|
}
|
||
|
|
||
|
info := fieldInfo{Num: i}
|
||
|
|
||
|
tag := field.Tag.Get("yaml")
|
||
|
if tag == "" && strings.Index(string(field.Tag), ":") < 0 {
|
||
|
tag = string(field.Tag)
|
||
|
}
|
||
|
if tag == "-" {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
inline := false
|
||
|
fields := strings.Split(tag, ",")
|
||
|
if len(fields) > 1 {
|
||
|
for _, flag := range fields[1:] {
|
||
|
switch flag {
|
||
|
case "omitempty":
|
||
|
info.OmitEmpty = true
|
||
|
case "flow":
|
||
|
info.Flow = true
|
||
|
case "inline":
|
||
|
inline = true
|
||
|
default:
|
||
|
return nil, errors.New(fmt.Sprintf("unsupported flag %q in tag %q of type %s", flag, tag, st))
|
||
|
}
|
||
|
}
|
||
|
tag = fields[0]
|
||
|
}
|
||
|
|
||
|
if inline {
|
||
|
switch field.Type.Kind() {
|
||
|
case reflect.Map:
|
||
|
if inlineMap >= 0 {
|
||
|
return nil, errors.New("multiple ,inline maps in struct " + st.String())
|
||
|
}
|
||
|
if field.Type.Key() != reflect.TypeOf("") {
|
||
|
return nil, errors.New("option ,inline needs a map with string keys in struct " + st.String())
|
||
|
}
|
||
|
inlineMap = info.Num
|
||
|
case reflect.Struct, reflect.Ptr:
|
||
|
ftype := field.Type
|
||
|
for ftype.Kind() == reflect.Ptr {
|
||
|
ftype = ftype.Elem()
|
||
|
}
|
||
|
if ftype.Kind() != reflect.Struct {
|
||
|
return nil, errors.New("option ,inline may only be used on a struct or map field")
|
||
|
}
|
||
|
if reflect.PtrTo(ftype).Implements(unmarshalerType) {
|
||
|
inlineUnmarshalers = append(inlineUnmarshalers, []int{i})
|
||
|
} else {
|
||
|
sinfo, err := getStructInfo(ftype)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
for _, index := range sinfo.InlineUnmarshalers {
|
||
|
inlineUnmarshalers = append(inlineUnmarshalers, append([]int{i}, index...))
|
||
|
}
|
||
|
for _, finfo := range sinfo.FieldsList {
|
||
|
if _, found := fieldsMap[finfo.Key]; found {
|
||
|
msg := "duplicated key '" + finfo.Key + "' in struct " + st.String()
|
||
|
return nil, errors.New(msg)
|
||
|
}
|
||
|
if finfo.Inline == nil {
|
||
|
finfo.Inline = []int{i, finfo.Num}
|
||
|
} else {
|
||
|
finfo.Inline = append([]int{i}, finfo.Inline...)
|
||
|
}
|
||
|
finfo.Id = len(fieldsList)
|
||
|
fieldsMap[finfo.Key] = finfo
|
||
|
fieldsList = append(fieldsList, finfo)
|
||
|
}
|
||
|
}
|
||
|
default:
|
||
|
return nil, errors.New("option ,inline may only be used on a struct or map field")
|
||
|
}
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if tag != "" {
|
||
|
info.Key = tag
|
||
|
} else {
|
||
|
info.Key = strings.ToLower(field.Name)
|
||
|
}
|
||
|
|
||
|
if _, found = fieldsMap[info.Key]; found {
|
||
|
msg := "duplicated key '" + info.Key + "' in struct " + st.String()
|
||
|
return nil, errors.New(msg)
|
||
|
}
|
||
|
|
||
|
info.Id = len(fieldsList)
|
||
|
fieldsList = append(fieldsList, info)
|
||
|
fieldsMap[info.Key] = info
|
||
|
}
|
||
|
|
||
|
sinfo = &structInfo{
|
||
|
FieldsMap: fieldsMap,
|
||
|
FieldsList: fieldsList,
|
||
|
InlineMap: inlineMap,
|
||
|
InlineUnmarshalers: inlineUnmarshalers,
|
||
|
}
|
||
|
|
||
|
fieldMapMutex.Lock()
|
||
|
structMap[st] = sinfo
|
||
|
fieldMapMutex.Unlock()
|
||
|
return sinfo, nil
|
||
|
}
|
||
|
|
||
|
// IsZeroer is used to check whether an object is zero to
|
||
|
// determine whether it should be omitted when marshaling
|
||
|
// with the omitempty flag. One notable implementation
|
||
|
// is time.Time.
|
||
|
type IsZeroer interface {
|
||
|
IsZero() bool
|
||
|
}
|
||
|
|
||
|
func isZero(v reflect.Value) bool {
|
||
|
kind := v.Kind()
|
||
|
if z, ok := v.Interface().(IsZeroer); ok {
|
||
|
if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() {
|
||
|
return true
|
||
|
}
|
||
|
return z.IsZero()
|
||
|
}
|
||
|
switch kind {
|
||
|
case reflect.String:
|
||
|
return len(v.String()) == 0
|
||
|
case reflect.Interface, reflect.Ptr:
|
||
|
return v.IsNil()
|
||
|
case reflect.Slice:
|
||
|
return v.Len() == 0
|
||
|
case reflect.Map:
|
||
|
return v.Len() == 0
|
||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||
|
return v.Int() == 0
|
||
|
case reflect.Float32, reflect.Float64:
|
||
|
return v.Float() == 0
|
||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||
|
return v.Uint() == 0
|
||
|
case reflect.Bool:
|
||
|
return !v.Bool()
|
||
|
case reflect.Struct:
|
||
|
vt := v.Type()
|
||
|
for i := v.NumField() - 1; i >= 0; i-- {
|
||
|
if vt.Field(i).PkgPath != "" {
|
||
|
continue // Private field
|
||
|
}
|
||
|
if !isZero(v.Field(i)) {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|