forked from cerc-io/ipld-eth-server
109 lines
3.1 KiB
Go
109 lines
3.1 KiB
Go
package obj
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/polydawn/refmt/obj/atlas"
|
|
. "github.com/polydawn/refmt/tok"
|
|
)
|
|
|
|
type marshalMachineStructAtlas struct {
|
|
cfg *atlas.AtlasEntry // set on initialization
|
|
|
|
target_rv reflect.Value
|
|
index int // Progress marker
|
|
value_rv reflect.Value // Next value (or nil if next step is key).
|
|
}
|
|
|
|
func (mach *marshalMachineStructAtlas) Reset(slab *marshalSlab, rv reflect.Value, _ reflect.Type) error {
|
|
mach.target_rv = rv
|
|
mach.index = -1
|
|
mach.value_rv = reflect.Value{}
|
|
slab.grow() // we'll reuse the same row for all fields
|
|
return nil
|
|
}
|
|
|
|
func (mach *marshalMachineStructAtlas) Step(driver *Marshaller, slab *marshalSlab, tok *Token) (done bool, err error) {
|
|
//fmt.Printf("--step on %#v: i=%d/%d v=%v\n", mach.target_rv, mach.index, len(mach.cfg.Fields), mach.value)
|
|
|
|
// Check boundaries and do the special steps or either start or end.
|
|
nEntries := len(mach.cfg.StructMap.Fields)
|
|
if mach.index < 0 {
|
|
tok.Type = TMapOpen
|
|
tok.Length = countEmittableStructFields(mach.cfg, mach.target_rv)
|
|
tok.Tagged = mach.cfg.Tagged
|
|
tok.Tag = mach.cfg.Tag
|
|
mach.index++
|
|
return false, nil
|
|
}
|
|
if mach.index == nEntries {
|
|
tok.Type = TMapClose
|
|
mach.index++
|
|
slab.release()
|
|
return true, nil
|
|
}
|
|
if mach.index > nEntries {
|
|
return true, fmt.Errorf("invalid state: entire struct (%d fields) already consumed", nEntries)
|
|
}
|
|
|
|
// If value loaded from last step, recurse into handling that.
|
|
fieldEntry := mach.cfg.StructMap.Fields[mach.index]
|
|
if mach.value_rv != (reflect.Value{}) {
|
|
child_rv := mach.value_rv
|
|
mach.index++
|
|
mach.value_rv = reflect.Value{}
|
|
return false, driver.Recurse(
|
|
tok,
|
|
child_rv,
|
|
fieldEntry.Type,
|
|
slab.yieldMachine(fieldEntry.Type),
|
|
)
|
|
}
|
|
|
|
// If value was nil, that indicates we're supposed to pick the value and yield a key.
|
|
// We have to look ahead to the value because if it's zero and tagged as
|
|
// omitEmpty, then we have to skip emitting the key as well.
|
|
for fieldEntry.Ignore {
|
|
mach.index++
|
|
if mach.index == nEntries {
|
|
tok.Type = TMapClose
|
|
mach.index++
|
|
slab.release()
|
|
return true, nil
|
|
}
|
|
fieldEntry = mach.cfg.StructMap.Fields[mach.index]
|
|
}
|
|
mach.value_rv = fieldEntry.ReflectRoute.TraverseToValue(mach.target_rv)
|
|
if fieldEntry.OmitEmpty && isEmptyValue(mach.value_rv) {
|
|
mach.value_rv = reflect.Value{}
|
|
mach.index++
|
|
return mach.Step(driver, slab, tok)
|
|
}
|
|
tok.Type = TString
|
|
tok.Str = fieldEntry.SerialName
|
|
return false, nil
|
|
}
|
|
|
|
// Count how many fields in a struct should actually be marshalled.
|
|
// Fields that are tagged omitEmpty and are isEmptyValue are not counted, and
|
|
// StructMapEntry used to flag ignored fields unmarshalling never count, so
|
|
// this number may be less than the number of fields in the AtlasEntry.StructMap.
|
|
func countEmittableStructFields(cfg *atlas.AtlasEntry, target_rv reflect.Value) int {
|
|
total := 0
|
|
for _, fieldEntry := range cfg.StructMap.Fields {
|
|
if fieldEntry.Ignore {
|
|
continue
|
|
}
|
|
if !fieldEntry.OmitEmpty {
|
|
total++
|
|
continue
|
|
}
|
|
if !isEmptyValue(fieldEntry.ReflectRoute.TraverseToValue(target_rv)) {
|
|
total++
|
|
continue
|
|
}
|
|
}
|
|
return total
|
|
}
|