Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1125 lines
32 KiB
Go
1125 lines
32 KiB
Go
package bindnode
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/ipld/go-ipld-prime/datamodel"
|
|
"github.com/ipld/go-ipld-prime/node/basicnode"
|
|
"github.com/ipld/go-ipld-prime/schema"
|
|
)
|
|
|
|
func reprNode(node datamodel.Node) datamodel.Node {
|
|
if node, ok := node.(schema.TypedNode); ok {
|
|
return node.Representation()
|
|
}
|
|
// datamodel.Absent and datamodel.Null are not typed.
|
|
// TODO: is this a problem? surely a typed struct's fields are always
|
|
// typed, even when absent or null.
|
|
return node
|
|
}
|
|
|
|
func reprStrategy(typ schema.Type) interface{} {
|
|
// Can't use an interface check, as each method has a different result type.
|
|
// TODO: consider inlining this type switch at each call site,
|
|
// as the call sites need the underlying schema.Type too.
|
|
switch typ := typ.(type) {
|
|
case *schema.TypeStruct:
|
|
return typ.RepresentationStrategy()
|
|
case *schema.TypeUnion:
|
|
return typ.RepresentationStrategy()
|
|
case *schema.TypeEnum:
|
|
return typ.RepresentationStrategy()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type _prototypeRepr _prototype
|
|
|
|
func (w *_prototypeRepr) NewBuilder() datamodel.NodeBuilder {
|
|
return &_builderRepr{_assemblerRepr{
|
|
cfg: w.cfg,
|
|
schemaType: w.schemaType,
|
|
val: reflect.New(w.goType).Elem(),
|
|
}}
|
|
}
|
|
|
|
type _nodeRepr _node
|
|
|
|
func (w *_nodeRepr) Kind() datamodel.Kind {
|
|
switch reprStrategy(w.schemaType).(type) {
|
|
case schema.StructRepresentation_Stringjoin:
|
|
return datamodel.Kind_String
|
|
case schema.StructRepresentation_Map:
|
|
return datamodel.Kind_Map
|
|
case schema.StructRepresentation_Tuple:
|
|
return datamodel.Kind_List
|
|
case schema.UnionRepresentation_Keyed:
|
|
return datamodel.Kind_Map
|
|
case schema.UnionRepresentation_Kinded:
|
|
haveIdx, _ := unionMember(w.val)
|
|
if haveIdx < 0 {
|
|
panic(fmt.Sprintf("bindnode: kinded union %s has no member", w.val.Type()))
|
|
}
|
|
mtyp := w.schemaType.(*schema.TypeUnion).Members()[haveIdx]
|
|
return mtyp.RepresentationBehavior()
|
|
case schema.UnionRepresentation_Stringprefix:
|
|
return datamodel.Kind_String
|
|
case schema.EnumRepresentation_Int:
|
|
return datamodel.Kind_Int
|
|
case schema.EnumRepresentation_String:
|
|
return datamodel.Kind_String
|
|
default:
|
|
return (*_node)(w).Kind()
|
|
}
|
|
}
|
|
|
|
func outboundMappedKey(stg schema.StructRepresentation_Map, key string) string {
|
|
// TODO: why doesn't stg just allow us to "get" by the key string?
|
|
field := schema.SpawnStructField(key, "", false, false)
|
|
mappedKey := stg.GetFieldKey(field)
|
|
return mappedKey
|
|
}
|
|
|
|
func inboundMappedKey(typ *schema.TypeStruct, stg schema.StructRepresentation_Map, key string) string {
|
|
// TODO: can't do a "reverse" lookup... needs better API probably.
|
|
fields := typ.Fields()
|
|
for _, field := range fields {
|
|
mappedKey := stg.GetFieldKey(field)
|
|
if key == mappedKey {
|
|
return field.Name()
|
|
}
|
|
}
|
|
return key // fallback to the same key
|
|
}
|
|
|
|
func outboundMappedType(stg schema.UnionRepresentation_Keyed, key string) string {
|
|
// TODO: why doesn't stg just allow us to "get" by the key string?
|
|
typ := schema.SpawnBool(key)
|
|
mappedKey := stg.GetDiscriminant(typ)
|
|
return mappedKey
|
|
}
|
|
|
|
func inboundMappedType(typ *schema.TypeUnion, stg schema.UnionRepresentation_Keyed, key string) string {
|
|
// TODO: can't do a "reverse" lookup... needs better API probably.
|
|
for _, member := range typ.Members() {
|
|
mappedKey := stg.GetDiscriminant(member)
|
|
if key == mappedKey {
|
|
// println(key, "rev-mapped to", field.Name())
|
|
return member.Name()
|
|
}
|
|
}
|
|
// println(key, "had no mapping")
|
|
return key // fallback to the same key
|
|
}
|
|
|
|
// asKinded can be called on a kinded union node to obtain a node
|
|
// representing one of its members, identified by kind.
|
|
func (w *_nodeRepr) asKinded(stg schema.UnionRepresentation_Kinded, kind datamodel.Kind) *_nodeRepr {
|
|
name := stg.GetMember(kind)
|
|
members := w.schemaType.(*schema.TypeUnion).Members()
|
|
for i, member := range members {
|
|
if member.Name() != name {
|
|
continue
|
|
}
|
|
w2 := *w
|
|
w2.val = w.val.Field(i).Elem()
|
|
w2.schemaType = member
|
|
return &w2
|
|
}
|
|
panic("bindnode TODO: GetMember result is missing?")
|
|
}
|
|
|
|
func (w *_nodeRepr) LookupByString(key string) (datamodel.Node, error) {
|
|
if stg, ok := reprStrategy(w.schemaType).(schema.UnionRepresentation_Kinded); ok {
|
|
w = w.asKinded(stg, datamodel.Kind_Map)
|
|
}
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.StructRepresentation_Map:
|
|
revKey := inboundMappedKey(w.schemaType.(*schema.TypeStruct), stg, key)
|
|
v, err := (*_node)(w).LookupByString(revKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return reprNode(v), nil
|
|
case schema.UnionRepresentation_Keyed:
|
|
revKey := inboundMappedType(w.schemaType.(*schema.TypeUnion), stg, key)
|
|
v, err := (*_node)(w).LookupByString(revKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return reprNode(v), nil
|
|
default:
|
|
v, err := (*_node)(w).LookupByString(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return reprNode(v), nil
|
|
}
|
|
}
|
|
|
|
func (w *_nodeRepr) LookupByIndex(idx int64) (datamodel.Node, error) {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Kinded:
|
|
return w.asKinded(stg, datamodel.Kind_List).LookupByIndex(idx)
|
|
case schema.StructRepresentation_Tuple:
|
|
fields := w.schemaType.(*schema.TypeStruct).Fields()
|
|
if idx < 0 || int(idx) >= len(fields) {
|
|
return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfInt(idx)}
|
|
}
|
|
field := fields[idx]
|
|
v, err := (*_node)(w).LookupByString(field.Name())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return reprNode(v), nil
|
|
default:
|
|
v, err := (*_node)(w).LookupByIndex(idx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return reprNode(v), nil
|
|
}
|
|
}
|
|
|
|
func (w *_nodeRepr) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) {
|
|
switch w.Kind() {
|
|
case datamodel.Kind_Map:
|
|
return w.LookupByString(seg.String())
|
|
case datamodel.Kind_List:
|
|
idx, err := seg.Index()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return w.LookupByIndex(idx)
|
|
}
|
|
return nil, datamodel.ErrWrongKind{
|
|
TypeName: w.schemaType.Name(),
|
|
MethodName: "LookupBySegment",
|
|
AppropriateKind: datamodel.KindSet_Recursive,
|
|
ActualKind: w.Kind(),
|
|
}
|
|
}
|
|
|
|
func (w *_nodeRepr) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
|
|
switch w.Kind() {
|
|
case datamodel.Kind_Map:
|
|
s, err := key.AsString()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return w.LookupByString(s)
|
|
case datamodel.Kind_List:
|
|
i, err := key.AsInt()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return w.LookupByIndex(i)
|
|
}
|
|
return nil, datamodel.ErrWrongKind{
|
|
TypeName: w.schemaType.Name(),
|
|
MethodName: "LookupByNode",
|
|
AppropriateKind: datamodel.KindSet_Recursive,
|
|
ActualKind: w.Kind(),
|
|
}
|
|
}
|
|
|
|
func (w *_nodeRepr) MapIterator() datamodel.MapIterator {
|
|
// TODO: we can try to reuse reprStrategy here and elsewhere
|
|
if stg, ok := reprStrategy(w.schemaType).(schema.UnionRepresentation_Kinded); ok {
|
|
w = w.asKinded(stg, datamodel.Kind_Map)
|
|
}
|
|
switch reprStrategy(w.schemaType).(type) {
|
|
case schema.StructRepresentation_Map:
|
|
itr := (*_node)(w).MapIterator().(*_structIterator)
|
|
// When we reach the last non-absent field, we should stop.
|
|
itr.reprEnd = int(w.lengthMinusTrailingAbsents())
|
|
return (*_structIteratorRepr)(itr)
|
|
case schema.UnionRepresentation_Keyed:
|
|
itr := (*_node)(w).MapIterator().(*_unionIterator)
|
|
return (*_unionIteratorRepr)(itr)
|
|
default:
|
|
iter, _ := (*_node)(w).MapIterator().(*_mapIterator)
|
|
if iter == nil {
|
|
return nil
|
|
}
|
|
return (*_mapIteratorRepr)(iter)
|
|
}
|
|
}
|
|
|
|
type _mapIteratorRepr _mapIterator
|
|
|
|
func (w *_mapIteratorRepr) Next() (key, value datamodel.Node, _ error) {
|
|
k, v, err := (*_mapIterator)(w).Next()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return reprNode(k), reprNode(v), nil
|
|
}
|
|
|
|
func (w *_mapIteratorRepr) Done() bool {
|
|
return w.nextIndex >= w.keysVal.Len()
|
|
}
|
|
|
|
func (w *_nodeRepr) ListIterator() datamodel.ListIterator {
|
|
if stg, ok := reprStrategy(w.schemaType).(schema.UnionRepresentation_Kinded); ok {
|
|
w = w.asKinded(stg, datamodel.Kind_List)
|
|
}
|
|
switch reprStrategy(w.schemaType).(type) {
|
|
case schema.StructRepresentation_Tuple:
|
|
typ := w.schemaType.(*schema.TypeStruct)
|
|
iter := _tupleIteratorRepr{cfg: w.cfg, schemaType: typ, fields: typ.Fields(), val: w.val}
|
|
iter.reprEnd = int(w.lengthMinusTrailingAbsents())
|
|
return &iter
|
|
default:
|
|
iter, _ := (*_node)(w).ListIterator().(*_listIterator)
|
|
if iter == nil {
|
|
return nil
|
|
}
|
|
return (*_listIteratorRepr)(iter)
|
|
}
|
|
}
|
|
|
|
type _listIteratorRepr _listIterator
|
|
|
|
func (w *_listIteratorRepr) Next() (index int64, value datamodel.Node, _ error) {
|
|
idx, v, err := (*_listIterator)(w).Next()
|
|
if err != nil {
|
|
return idx, nil, err
|
|
}
|
|
return idx, reprNode(v), nil
|
|
}
|
|
|
|
func (w *_listIteratorRepr) Done() bool {
|
|
return w.nextIndex >= w.val.Len()
|
|
}
|
|
|
|
func (w *_nodeRepr) lengthMinusAbsents() int64 {
|
|
fields := w.schemaType.(*schema.TypeStruct).Fields()
|
|
n := int64(len(fields))
|
|
for i, field := range fields {
|
|
if field.IsOptional() && w.val.Field(i).IsNil() {
|
|
n--
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
type _tupleIteratorRepr struct {
|
|
// TODO: support embedded fields?
|
|
cfg config
|
|
schemaType *schema.TypeStruct
|
|
fields []schema.StructField
|
|
val reflect.Value // non-pointer
|
|
nextIndex int
|
|
|
|
// these are only used in repr.go
|
|
reprEnd int
|
|
}
|
|
|
|
func (w *_tupleIteratorRepr) Next() (index int64, value datamodel.Node, _ error) {
|
|
_skipAbsent:
|
|
_, value, err := (*_structIterator)(w).Next()
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
if w.nextIndex > w.reprEnd {
|
|
goto _skipAbsent
|
|
}
|
|
return int64(w.nextIndex), reprNode(value), nil
|
|
}
|
|
|
|
func (w *_tupleIteratorRepr) Done() bool {
|
|
return w.nextIndex >= w.reprEnd
|
|
}
|
|
|
|
func (w *_nodeRepr) lengthMinusTrailingAbsents() int64 {
|
|
fields := w.schemaType.(*schema.TypeStruct).Fields()
|
|
for i := len(fields) - 1; i >= 0; i-- {
|
|
field := fields[i]
|
|
if !field.IsOptional() || !w.val.Field(i).IsNil() {
|
|
return int64(i + 1)
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (w *_nodeRepr) Length() int64 {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.StructRepresentation_Stringjoin:
|
|
return -1
|
|
case schema.StructRepresentation_Map:
|
|
return w.lengthMinusAbsents()
|
|
case schema.StructRepresentation_Tuple:
|
|
return w.lengthMinusTrailingAbsents()
|
|
case schema.UnionRepresentation_Keyed:
|
|
return (*_node)(w).Length()
|
|
case schema.UnionRepresentation_Kinded:
|
|
w = w.asKinded(stg, w.Kind())
|
|
return (*_node)(w).Length()
|
|
default:
|
|
return (*_node)(w).Length()
|
|
}
|
|
}
|
|
|
|
func (w *_nodeRepr) IsAbsent() bool {
|
|
if reprStrategy(w.schemaType) == nil {
|
|
return (*_node)(w).IsAbsent()
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (w *_nodeRepr) IsNull() bool {
|
|
if reprStrategy(w.schemaType) == nil {
|
|
return (*_node)(w).IsNull()
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (w *_nodeRepr) AsBool() (bool, error) {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Kinded:
|
|
return w.asKinded(stg, datamodel.Kind_Bool).AsBool()
|
|
default:
|
|
return (*_node)(w).AsBool()
|
|
}
|
|
}
|
|
|
|
func (w *_nodeRepr) AsInt() (int64, error) {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Kinded:
|
|
return w.asKinded(stg, datamodel.Kind_Int).AsInt()
|
|
case schema.EnumRepresentation_Int:
|
|
kind := w.val.Kind()
|
|
if kind == reflect.String {
|
|
s, err := (*_node)(w).AsString()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
mapped, ok := stg[s]
|
|
if !ok {
|
|
// We assume that the schema strategy is correct,
|
|
// so we can only fail if the stored string isn't a valid member.
|
|
return 0, fmt.Errorf("AsInt: %q is not a valid member of enum %s", s, w.schemaType.Name())
|
|
}
|
|
// TODO: the strategy type should probably use int64 rather than int
|
|
return int64(mapped), nil
|
|
}
|
|
var i int
|
|
// TODO: check for overflows
|
|
if kindInt[kind] {
|
|
i = int(w.val.Int())
|
|
} else if kindUint[kind] {
|
|
i = int(w.val.Uint())
|
|
} else {
|
|
return 0, fmt.Errorf("AsInt: unexpected kind: %s", kind)
|
|
}
|
|
for _, reprInt := range stg {
|
|
if reprInt == i {
|
|
return int64(i), nil
|
|
}
|
|
}
|
|
// We assume that the schema strategy is correct,
|
|
// so we can only fail if the stored string isn't a valid member.
|
|
return 0, fmt.Errorf("AsInt: %d is not a valid member of enum %s", i, w.schemaType.Name())
|
|
default:
|
|
return (*_node)(w).AsInt()
|
|
}
|
|
}
|
|
|
|
func (w *_nodeRepr) AsFloat() (float64, error) {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Kinded:
|
|
return w.asKinded(stg, datamodel.Kind_Float).AsFloat()
|
|
default:
|
|
return (*_node)(w).AsFloat()
|
|
}
|
|
}
|
|
|
|
func (w *_nodeRepr) AsString() (string, error) {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.StructRepresentation_Stringjoin:
|
|
var b strings.Builder
|
|
itr := (*_node)(w).MapIterator()
|
|
first := true
|
|
for !itr.Done() {
|
|
_, v, err := itr.Next()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
s, err := reprNode(v).AsString()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if first {
|
|
first = false
|
|
} else {
|
|
b.WriteString(stg.GetDelim())
|
|
}
|
|
b.WriteString(s)
|
|
}
|
|
return b.String(), nil
|
|
case schema.UnionRepresentation_Stringprefix:
|
|
haveIdx, mval := unionMember(w.val)
|
|
mtyp := w.schemaType.(*schema.TypeUnion).Members()[haveIdx]
|
|
|
|
w2 := *w
|
|
w2.val = mval
|
|
w2.schemaType = mtyp
|
|
s, err := w2.AsString()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
name := stg.GetDiscriminant(mtyp)
|
|
return name + stg.GetDelim() + s, nil
|
|
case schema.UnionRepresentation_Kinded:
|
|
return w.asKinded(stg, datamodel.Kind_String).AsString()
|
|
case schema.EnumRepresentation_String:
|
|
s, err := (*_node)(w).AsString()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if mapped := stg[s]; mapped != "" {
|
|
return mapped, nil
|
|
}
|
|
members := w.schemaType.(*schema.TypeEnum).Members()
|
|
for _, member := range members {
|
|
if s == member {
|
|
return s, nil
|
|
}
|
|
}
|
|
for k, v := range stg {
|
|
// a programming error? we may have the enum string value rather than the type
|
|
if v == s {
|
|
return "", fmt.Errorf("AsString: %q is not a valid member of enum %s (bindnode works at the type level; did you mean %q?)", s, w.schemaType.Name(), k)
|
|
}
|
|
}
|
|
return "", fmt.Errorf("AsString: %q is not a valid member of enum %s", s, w.schemaType.Name())
|
|
default:
|
|
return (*_node)(w).AsString()
|
|
}
|
|
}
|
|
|
|
func (w *_nodeRepr) AsBytes() ([]byte, error) {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Kinded:
|
|
return w.asKinded(stg, datamodel.Kind_Bytes).AsBytes()
|
|
default:
|
|
return (*_node)(w).AsBytes()
|
|
}
|
|
}
|
|
|
|
func (w *_nodeRepr) AsLink() (datamodel.Link, error) {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Kinded:
|
|
return w.asKinded(stg, datamodel.Kind_Link).AsLink()
|
|
default:
|
|
return (*_node)(w).AsLink()
|
|
}
|
|
}
|
|
|
|
func (w *_nodeRepr) Prototype() datamodel.NodePrototype {
|
|
return (*_prototypeRepr)((*_node)(w).Prototype().(*_prototype))
|
|
}
|
|
|
|
type _builderRepr struct {
|
|
_assemblerRepr
|
|
}
|
|
|
|
// TODO: returning a repr node here is probably good, but there's a gotcha: one
|
|
// can go from a typed node to a repr node via the Representation method, but
|
|
// not the other way. That's probably why codegen returns a typed node here.
|
|
// The solution might be to add a way to go from the repr node to its parent
|
|
// typed node.
|
|
|
|
func (w *_builderRepr) Build() datamodel.Node {
|
|
// TODO: see the notes above.
|
|
// return &_nodeRepr{schemaType: w.schemaType, val: w.val}
|
|
return &_node{cfg: w.cfg, schemaType: w.schemaType, val: w.val}
|
|
}
|
|
|
|
func (w *_builderRepr) Reset() {
|
|
panic("bindnode TODO: Reset")
|
|
}
|
|
|
|
type _assemblerRepr struct {
|
|
cfg config
|
|
schemaType schema.Type
|
|
val reflect.Value // non-pointer
|
|
finish func() error
|
|
|
|
nullable bool
|
|
}
|
|
|
|
func assemblerRepr(am datamodel.NodeAssembler) datamodel.NodeAssembler {
|
|
switch am := am.(type) {
|
|
case *_assembler:
|
|
return (*_assemblerRepr)(am)
|
|
case _errorAssembler:
|
|
return am
|
|
default:
|
|
panic(fmt.Sprintf("unexpected NodeAssembler type: %T", am))
|
|
}
|
|
}
|
|
|
|
func (w *_assemblerRepr) asKinded(stg schema.UnionRepresentation_Kinded, kind datamodel.Kind) datamodel.NodeAssembler {
|
|
name := stg.GetMember(kind)
|
|
members := w.schemaType.(*schema.TypeUnion).Members()
|
|
kindSet := make([]datamodel.Kind, 0, len(members))
|
|
for idx, member := range members {
|
|
if member.Name() != name {
|
|
kindSet = append(kindSet, member.RepresentationBehavior())
|
|
continue
|
|
}
|
|
w2 := *w
|
|
goType := w.val.Field(idx).Type().Elem()
|
|
valPtr := reflect.New(goType)
|
|
w2.val = valPtr.Elem()
|
|
w2.schemaType = member
|
|
|
|
// Layer a new finish func on top, to set Index/Value.
|
|
w2.finish = func() error {
|
|
unionSetMember(w.val, idx, valPtr)
|
|
if w.finish != nil {
|
|
if err := w.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
return &w2
|
|
}
|
|
return _errorAssembler{datamodel.ErrWrongKind{
|
|
TypeName: w.schemaType.Name() + ".Repr",
|
|
MethodName: "", // TODO: we could fill it via runtime.Callers
|
|
AppropriateKind: datamodel.KindSet(kindSet),
|
|
ActualKind: kind,
|
|
}}
|
|
}
|
|
|
|
func (w *_assemblerRepr) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
|
|
if stg, ok := reprStrategy(w.schemaType).(schema.UnionRepresentation_Kinded); ok {
|
|
return w.asKinded(stg, datamodel.Kind_Map).BeginMap(sizeHint)
|
|
}
|
|
asm, err := (*_assembler)(w).BeginMap(sizeHint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch asm := asm.(type) {
|
|
case *_structAssembler:
|
|
return (*_structAssemblerRepr)(asm), nil
|
|
case *_mapAssembler:
|
|
return (*_mapAssemblerRepr)(asm), nil
|
|
case *_unionAssembler:
|
|
return (*_unionAssemblerRepr)(asm), nil
|
|
case *basicMapAssembler:
|
|
return asm, nil
|
|
default:
|
|
return nil, fmt.Errorf("bindnode BeginMap TODO: %T", asm)
|
|
}
|
|
}
|
|
|
|
func (w *_assemblerRepr) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Kinded:
|
|
return w.asKinded(stg, datamodel.Kind_List).BeginList(sizeHint)
|
|
case schema.StructRepresentation_Tuple:
|
|
asm, err := (*_assembler)(w).BeginMap(sizeHint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return (*_listStructAssemblerRepr)(asm.(*_structAssembler)), nil
|
|
default:
|
|
asm, err := (*_assembler)(w).BeginList(sizeHint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, ok := asm.(*basicListAssembler); ok {
|
|
return asm, nil
|
|
}
|
|
return (*_listAssemblerRepr)(asm.(*_listAssembler)), nil
|
|
}
|
|
}
|
|
|
|
func (w *_assemblerRepr) AssignNull() error {
|
|
return (*_assembler)(w).AssignNull()
|
|
}
|
|
|
|
func (w *_assemblerRepr) AssignBool(b bool) error {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Kinded:
|
|
return w.asKinded(stg, datamodel.Kind_Bool).AssignBool(b)
|
|
default:
|
|
return (*_assembler)(w).AssignBool(b)
|
|
}
|
|
}
|
|
|
|
func (w *_assemblerRepr) assignUInt(uin datamodel.UintNode) error {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Kinded:
|
|
return w.asKinded(stg, datamodel.Kind_Int).(*_assemblerRepr).assignUInt(uin)
|
|
case schema.EnumRepresentation_Int:
|
|
uin, err := uin.AsUint()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return fmt.Errorf("AssignInt: %d is not a valid member of enum %s", uin, w.schemaType.Name())
|
|
default:
|
|
return (*_assembler)(w).assignUInt(uin)
|
|
}
|
|
}
|
|
|
|
func (w *_assemblerRepr) AssignInt(i int64) error {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Kinded:
|
|
return w.asKinded(stg, datamodel.Kind_Int).AssignInt(i)
|
|
case schema.EnumRepresentation_Int:
|
|
for member, reprInt := range stg {
|
|
if int64(reprInt) != i {
|
|
continue
|
|
}
|
|
val := (*_assembler)(w).createNonPtrVal()
|
|
kind := val.Kind()
|
|
if kind == reflect.String {
|
|
// Reuse AssignString so we don't have to repeat ten lines.
|
|
return (*_assembler)(w).AssignString(member)
|
|
}
|
|
// Short-cut to storing the repr int directly, akin to node.go's AssignInt.
|
|
if kindInt[kind] {
|
|
val.SetInt(i)
|
|
} else if kindUint[kind] {
|
|
if i < 0 {
|
|
// TODO: write a test
|
|
return fmt.Errorf("bindnode: cannot assign negative integer to %s", w.val.Type())
|
|
}
|
|
val.SetUint(uint64(i))
|
|
} else {
|
|
return fmt.Errorf("AsInt: unexpected kind: %s", val.Kind())
|
|
}
|
|
if w.finish != nil {
|
|
if err := w.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
return fmt.Errorf("AssignInt: %d is not a valid member of enum %s", i, w.schemaType.Name())
|
|
default:
|
|
return (*_assembler)(w).AssignInt(i)
|
|
}
|
|
}
|
|
|
|
func (w *_assemblerRepr) AssignFloat(f float64) error {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Kinded:
|
|
return w.asKinded(stg, datamodel.Kind_Float).AssignFloat(f)
|
|
default:
|
|
return (*_assembler)(w).AssignFloat(f)
|
|
}
|
|
}
|
|
|
|
func (w *_assemblerRepr) AssignString(s string) error {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.StructRepresentation_Stringjoin:
|
|
fields := w.schemaType.(*schema.TypeStruct).Fields()
|
|
parts := strings.Split(s, stg.GetDelim())
|
|
if len(parts) != len(fields) {
|
|
return fmt.Errorf("bindnode TODO: len mismatch")
|
|
}
|
|
mapAsm, err := (*_assembler)(w).BeginMap(-1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for i, field := range fields {
|
|
entryAsm, err := mapAsm.AssembleEntry(field.Name())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
entryAsm = assemblerRepr(entryAsm)
|
|
if err := entryAsm.AssignString(parts[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return mapAsm.Finish()
|
|
case schema.UnionRepresentation_Kinded:
|
|
return w.asKinded(stg, datamodel.Kind_String).AssignString(s)
|
|
case schema.UnionRepresentation_Stringprefix:
|
|
hasDelim := stg.GetDelim() != ""
|
|
|
|
var prefix, remainder string
|
|
if hasDelim {
|
|
parts := strings.SplitN(s, stg.GetDelim(), 2)
|
|
if len(parts) != 2 {
|
|
return fmt.Errorf("schema rejects data: the union type %s expects delimiter %q, and it was not found in the data %q", w.schemaType.Name(), stg.GetDelim(), s)
|
|
}
|
|
prefix, remainder = parts[0], parts[1]
|
|
}
|
|
|
|
members := w.schemaType.(*schema.TypeUnion).Members()
|
|
for idx, member := range members {
|
|
descrm := stg.GetDiscriminant(member)
|
|
if hasDelim {
|
|
if stg.GetDiscriminant(member) != prefix {
|
|
continue
|
|
}
|
|
} else {
|
|
if !strings.HasPrefix(s, descrm) {
|
|
continue
|
|
}
|
|
remainder = s[len(descrm):]
|
|
}
|
|
|
|
// TODO: DRY: this has much in common with the asKinded method; it differs only in that we picked idx already in a different way.
|
|
w2 := *w
|
|
goType := w.val.Field(idx).Type().Elem()
|
|
valPtr := reflect.New(goType)
|
|
w2.val = valPtr.Elem()
|
|
w2.schemaType = member
|
|
w2.finish = func() error {
|
|
unionSetMember(w.val, idx, valPtr)
|
|
if w.finish != nil {
|
|
if err := w.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
return w2.AssignString(remainder)
|
|
}
|
|
return fmt.Errorf("schema rejects data: the union type %s requires a known prefix, and it was not found in the data %q", w.schemaType.Name(), s)
|
|
case schema.EnumRepresentation_String:
|
|
// Note that we need to do a reverse lookup.
|
|
for member, mapped := range stg {
|
|
if mapped == s {
|
|
return (*_assembler)(w).AssignString(member)
|
|
}
|
|
}
|
|
members := w.schemaType.(*schema.TypeEnum).Members()
|
|
for _, member := range members {
|
|
if s == member {
|
|
return (*_assembler)(w).AssignString(member)
|
|
}
|
|
}
|
|
return fmt.Errorf("AssignString: %q is not a valid member of enum %s", s, w.schemaType.Name())
|
|
case schema.EnumRepresentation_Int:
|
|
return datamodel.ErrWrongKind{
|
|
TypeName: w.schemaType.Name(),
|
|
MethodName: "AssignString",
|
|
AppropriateKind: datamodel.KindSet_JustInt,
|
|
ActualKind: datamodel.Kind_String,
|
|
}
|
|
default:
|
|
return (*_assembler)(w).AssignString(s)
|
|
}
|
|
}
|
|
|
|
func (w *_assemblerRepr) AssignBytes(p []byte) error {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Kinded:
|
|
return w.asKinded(stg, datamodel.Kind_Bytes).AssignBytes(p)
|
|
default:
|
|
return (*_assembler)(w).AssignBytes(p)
|
|
}
|
|
}
|
|
|
|
func (w *_assemblerRepr) AssignLink(link datamodel.Link) error {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Kinded:
|
|
return w.asKinded(stg, datamodel.Kind_Link).AssignLink(link)
|
|
default:
|
|
return (*_assembler)(w).AssignLink(link)
|
|
}
|
|
}
|
|
|
|
func (w *_assemblerRepr) AssignNode(node datamodel.Node) error {
|
|
// TODO: attempt to take a shortcut, like assembler.AssignNode
|
|
if uintNode, ok := node.(datamodel.UintNode); ok {
|
|
return w.assignUInt(uintNode)
|
|
}
|
|
return datamodel.Copy(node, w)
|
|
}
|
|
|
|
func (w *_assemblerRepr) Prototype() datamodel.NodePrototype {
|
|
panic("bindnode TODO: Assembler.Prototype")
|
|
}
|
|
|
|
type _structAssemblerRepr _structAssembler
|
|
|
|
func (w *_structAssemblerRepr) AssembleKey() datamodel.NodeAssembler {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.StructRepresentation_Map:
|
|
return (*_structAssembler)(w).AssembleKey()
|
|
case schema.StructRepresentation_Stringjoin,
|
|
schema.StructRepresentation_StringPairs:
|
|
// TODO: perhaps the ErrorWrongKind type should also be extended to explicitly describe whether the method was applied on bare DM, type-level, or repr-level.
|
|
return _errorAssembler{datamodel.ErrWrongKind{
|
|
TypeName: w.schemaType.Name() + ".Repr",
|
|
MethodName: "AssembleKey",
|
|
AppropriateKind: datamodel.KindSet_JustMap,
|
|
ActualKind: datamodel.Kind_String,
|
|
}}
|
|
case schema.StructRepresentation_Tuple:
|
|
return _errorAssembler{datamodel.ErrWrongKind{
|
|
TypeName: w.schemaType.Name() + ".Repr",
|
|
MethodName: "AssembleKey",
|
|
AppropriateKind: datamodel.KindSet_JustMap,
|
|
ActualKind: datamodel.Kind_List,
|
|
}}
|
|
default:
|
|
return _errorAssembler{fmt.Errorf("bindnode AssembleKey TODO: %T", stg)}
|
|
}
|
|
}
|
|
|
|
func (w *_structAssemblerRepr) AssembleValue() datamodel.NodeAssembler {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.StructRepresentation_Map:
|
|
key := w.curKey.val.String()
|
|
revKey := inboundMappedKey(w.schemaType, stg, key)
|
|
w.curKey.val.SetString(revKey)
|
|
|
|
valAsm := (*_structAssembler)(w).AssembleValue()
|
|
valAsm = assemblerRepr(valAsm)
|
|
return valAsm
|
|
default:
|
|
return _errorAssembler{fmt.Errorf("bindnode AssembleValue TODO: %T", stg)}
|
|
}
|
|
}
|
|
|
|
func (w *_structAssemblerRepr) AssembleEntry(k string) (datamodel.NodeAssembler, error) {
|
|
if err := w.AssembleKey().AssignString(k); err != nil {
|
|
return nil, err
|
|
}
|
|
am := w.AssembleValue()
|
|
return am, nil
|
|
}
|
|
|
|
func (w *_structAssemblerRepr) Finish() error {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.StructRepresentation_Map:
|
|
err := (*_structAssembler)(w).Finish()
|
|
if err, ok := err.(schema.ErrMissingRequiredField); ok {
|
|
for i, name := range err.Missing {
|
|
serial := outboundMappedKey(stg, name)
|
|
if serial != name {
|
|
err.Missing[i] += fmt.Sprintf(" (serial:%q)", serial)
|
|
}
|
|
}
|
|
}
|
|
return err
|
|
default:
|
|
return fmt.Errorf("bindnode Finish TODO: %T", stg)
|
|
}
|
|
}
|
|
|
|
func (w *_structAssemblerRepr) KeyPrototype() datamodel.NodePrototype {
|
|
panic("bindnode TODO")
|
|
}
|
|
|
|
func (w *_structAssemblerRepr) ValuePrototype(k string) datamodel.NodePrototype {
|
|
panic("bindnode TODO: struct ValuePrototype")
|
|
}
|
|
|
|
type _mapAssemblerRepr _mapAssembler
|
|
|
|
func (w *_mapAssemblerRepr) AssembleKey() datamodel.NodeAssembler {
|
|
asm := (*_mapAssembler)(w).AssembleKey()
|
|
return (*_assemblerRepr)(asm.(*_assembler))
|
|
}
|
|
|
|
func (w *_mapAssemblerRepr) AssembleValue() datamodel.NodeAssembler {
|
|
asm := (*_mapAssembler)(w).AssembleValue()
|
|
return (*_assemblerRepr)(asm.(*_assembler))
|
|
}
|
|
|
|
func (w *_mapAssemblerRepr) AssembleEntry(k string) (datamodel.NodeAssembler, error) {
|
|
if err := w.AssembleKey().AssignString(k); err != nil {
|
|
return nil, err
|
|
}
|
|
am := w.AssembleValue()
|
|
return am, nil
|
|
}
|
|
|
|
func (w *_mapAssemblerRepr) Finish() error {
|
|
return (*_mapAssembler)(w).Finish()
|
|
}
|
|
|
|
func (w *_mapAssemblerRepr) KeyPrototype() datamodel.NodePrototype {
|
|
panic("bindnode TODO")
|
|
}
|
|
|
|
func (w *_mapAssemblerRepr) ValuePrototype(k string) datamodel.NodePrototype {
|
|
panic("bindnode TODO: struct ValuePrototype")
|
|
}
|
|
|
|
type _listStructAssemblerRepr _structAssembler
|
|
|
|
func (w *_listStructAssemblerRepr) AssembleValue() datamodel.NodeAssembler {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.StructRepresentation_Tuple:
|
|
fields := w.schemaType.Fields()
|
|
if w.nextIndex >= len(fields) {
|
|
return _errorAssembler{datamodel.ErrNotExists{
|
|
Segment: datamodel.PathSegmentOfInt(int64(w.nextIndex)),
|
|
}}
|
|
}
|
|
field := fields[w.nextIndex]
|
|
w.doneFields[w.nextIndex] = true
|
|
w.nextIndex++
|
|
|
|
entryAsm, err := (*_structAssembler)(w).AssembleEntry(field.Name())
|
|
if err != nil {
|
|
return _errorAssembler{err}
|
|
}
|
|
entryAsm = assemblerRepr(entryAsm)
|
|
return entryAsm
|
|
default:
|
|
return _errorAssembler{fmt.Errorf("bindnode AssembleValue TODO: %T", stg)}
|
|
}
|
|
}
|
|
|
|
func (w *_listStructAssemblerRepr) Finish() error {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.StructRepresentation_Tuple:
|
|
return (*_structAssembler)(w).Finish()
|
|
default:
|
|
return fmt.Errorf("bindnode Finish TODO: %T", stg)
|
|
}
|
|
}
|
|
|
|
func (w *_listStructAssemblerRepr) ValuePrototype(idx int64) datamodel.NodePrototype {
|
|
panic("bindnode TODO: list ValuePrototype")
|
|
}
|
|
|
|
// Note that lists do not have any representation strategy right now.
|
|
type _listAssemblerRepr _listAssembler
|
|
|
|
func (w *_listAssemblerRepr) AssembleValue() datamodel.NodeAssembler {
|
|
asm := (*_listAssembler)(w).AssembleValue()
|
|
return (*_assemblerRepr)(asm.(*_assembler))
|
|
}
|
|
|
|
func (w *_listAssemblerRepr) Finish() error {
|
|
return (*_listAssembler)(w).Finish()
|
|
}
|
|
|
|
func (w *_listAssemblerRepr) ValuePrototype(idx int64) datamodel.NodePrototype {
|
|
panic("bindnode TODO: list ValuePrototype")
|
|
}
|
|
|
|
type _unionAssemblerRepr _unionAssembler
|
|
|
|
func (w *_unionAssemblerRepr) AssembleKey() datamodel.NodeAssembler {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Keyed:
|
|
return (*_unionAssembler)(w).AssembleKey()
|
|
default:
|
|
return _errorAssembler{fmt.Errorf("bindnode AssembleKey TODO: %T", stg)}
|
|
}
|
|
}
|
|
|
|
func (w *_unionAssemblerRepr) AssembleValue() datamodel.NodeAssembler {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Keyed:
|
|
key := w.curKey.val.String()
|
|
revKey := inboundMappedType(w.schemaType, stg, key)
|
|
w.curKey.val.SetString(revKey)
|
|
|
|
valAsm := (*_unionAssembler)(w).AssembleValue()
|
|
valAsm = assemblerRepr(valAsm)
|
|
return valAsm
|
|
default:
|
|
return _errorAssembler{fmt.Errorf("bindnode AssembleValue TODO: %T", stg)}
|
|
}
|
|
}
|
|
|
|
func (w *_unionAssemblerRepr) AssembleEntry(k string) (datamodel.NodeAssembler, error) {
|
|
if err := w.AssembleKey().AssignString(k); err != nil {
|
|
return nil, err
|
|
}
|
|
am := w.AssembleValue()
|
|
return am, nil
|
|
}
|
|
|
|
func (w *_unionAssemblerRepr) Finish() error {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Keyed:
|
|
return (*_unionAssembler)(w).Finish()
|
|
default:
|
|
return fmt.Errorf("bindnode Finish TODO: %T", stg)
|
|
}
|
|
}
|
|
|
|
func (w *_unionAssemblerRepr) KeyPrototype() datamodel.NodePrototype {
|
|
panic("bindnode TODO")
|
|
}
|
|
|
|
func (w *_unionAssemblerRepr) ValuePrototype(k string) datamodel.NodePrototype {
|
|
panic("bindnode TODO: union ValuePrototype")
|
|
}
|
|
|
|
type _structIteratorRepr _structIterator
|
|
|
|
func (w *_structIteratorRepr) Next() (key, value datamodel.Node, _ error) {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.StructRepresentation_Map:
|
|
_skipAbsent:
|
|
key, value, err := (*_structIterator)(w).Next()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if value.IsAbsent() {
|
|
goto _skipAbsent
|
|
}
|
|
keyStr, _ := key.AsString()
|
|
mappedKey := outboundMappedKey(stg, keyStr)
|
|
if mappedKey != keyStr {
|
|
key = basicnode.NewString(mappedKey)
|
|
}
|
|
return key, reprNode(value), nil
|
|
default:
|
|
return nil, nil, fmt.Errorf("bindnode Next TODO: %T", stg)
|
|
}
|
|
}
|
|
|
|
func (w *_structIteratorRepr) Done() bool {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.StructRepresentation_Map:
|
|
// TODO: the fact that repr map iterators skip absents should be
|
|
// documented somewhere
|
|
return w.nextIndex >= w.reprEnd
|
|
default:
|
|
panic(fmt.Sprintf("bindnode Done TODO: %T", stg))
|
|
}
|
|
}
|
|
|
|
type _unionIteratorRepr _unionIterator
|
|
|
|
func (w *_unionIteratorRepr) Next() (key, value datamodel.Node, _ error) {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Keyed:
|
|
key, value, err := (*_unionIterator)(w).Next()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
keyStr, _ := key.AsString()
|
|
mappedKey := outboundMappedType(stg, keyStr)
|
|
if mappedKey != keyStr {
|
|
key = basicnode.NewString(mappedKey)
|
|
}
|
|
return key, reprNode(value), nil
|
|
default:
|
|
return nil, nil, fmt.Errorf("bindnode Next TODO: %T", stg)
|
|
}
|
|
}
|
|
|
|
func (w *_unionIteratorRepr) Done() bool {
|
|
switch stg := reprStrategy(w.schemaType).(type) {
|
|
case schema.UnionRepresentation_Keyed:
|
|
return (*_unionIterator)(w).Done()
|
|
default:
|
|
panic(fmt.Sprintf("bindnode Done TODO: %T", stg))
|
|
}
|
|
}
|