diff --git a/node/selection.go b/node/selection.go index 4b8d699..8d14085 100644 --- a/node/selection.go +++ b/node/selection.go @@ -621,7 +621,8 @@ func (sel *Selection) set(r *FieldRequest, hnd *ValueHandle) error { return nil } -// GetValue let's you get the leaf value as a Value instance. Returns null if value is null +// Get let's you get the leaf value as a Value instance. Returns null if value is null +// Returns error if path is not found. func (sel *Selection) Get() (val.Value, error) { if !meta.IsLeaf(sel.Path.Meta) { return nil, fmt.Errorf("%s is not a leaf", sel.Path.Meta.Ident()) @@ -640,6 +641,16 @@ func (sel *Selection) Get() (val.Value, error) { return hnd.Val, err } +// GetValue let's you get the leaf value at the specified path or ident. Returns null if +// value is null. Returns error if path is not found. +func (sel *Selection) GetValue(pathOrIdent string) (val.Value, error) { + s, err := sel.Find(pathOrIdent) + if err != nil { + return nil, err + } + return s.Get() +} + func (sel *Selection) get(r *FieldRequest, hnd *ValueHandle, useDefault bool) error { if proceed, constraintErr := sel.Constraints.CheckFieldPreConstraints(r, hnd); !proceed || constraintErr != nil { return constraintErr diff --git a/node/value.go b/node/value.go index d452b02..4c6afaf 100644 --- a/node/value.go +++ b/node/value.go @@ -65,7 +65,6 @@ func NewValue(typ *meta.Type, v interface{}) (val.Value, error) { } switch typ.Format() { case val.FmtIdentityRef: - return toIdentRef(typ.Base(), v) case val.FmtIdentityRefList: return toIdentRefList(typ.Base(), v) @@ -78,6 +77,8 @@ func NewValue(typ *meta.Type, v interface{}) (val.Value, error) { return cvt, err case val.FmtUnionList: return toUnionList(typ, v) + case val.FmtLeafRef, val.FmtLeafRefList: + return NewValue(typ.Resolve(), v) } return val.Conv(typ.Format(), v) } diff --git a/nodeutil/json_rdr.go b/nodeutil/json_rdr.go index 23810f7..e06ea65 100644 --- a/nodeutil/json_rdr.go +++ b/nodeutil/json_rdr.go @@ -134,7 +134,7 @@ func JsonContainerReader(container map[string]interface{}) node.Node { } s.OnChild = func(r node.ChildRequest) (child node.Node, e error) { if r.New { - panic("Cannot write to JSON reader") + panic("cannot write to JSON reader") } if value, found := fqkGet(r.Meta, container); found { if meta.IsList(r.Meta) { @@ -146,7 +146,7 @@ func JsonContainerReader(container map[string]interface{}) node.Node { } s.OnField = func(r node.FieldRequest, hnd *node.ValueHandle) (err error) { if r.Write { - panic("Cannot write to JSON reader") + panic("cannot write to JSON reader") } if val, found := fqkGet(r.Meta, container); found { hnd.Val, err = leafOrLeafListJsonReader(r.Meta, val) diff --git a/nodeutil/node.go b/nodeutil/node.go index b7b10c7..dc39904 100644 --- a/nodeutil/node.go +++ b/nodeutil/node.go @@ -2,6 +2,7 @@ package nodeutil import ( "context" + "errors" "fmt" "reflect" "sort" @@ -151,6 +152,10 @@ type Node struct { // an rpc input. OnNewObject func(t reflect.Type, m meta.Definition, insideList bool) (reflect.Value, error) + // OnNewNode is called when a new node needs to be created whether it is a child node of + // an rpc i/o or notification event. + OnNewNode func(n *Node, m meta.Meta, obj any) (node.Node, error) + OnContext func(n *Node, s *node.Selection) context.Context c reflectContainer // internal handler based on object type to handle containers and leafs @@ -291,7 +296,7 @@ func (n *Node) DoAction(r node.ActionRequest) (node.Node, error) { if err != nil { return nil, err } - return a.do(n, r.Input) + return a.do(n, r.Meta, r.Input) } func (n *Node) Notify(r node.NotifyRequest) (node.NotifyCloser, error) { @@ -343,6 +348,18 @@ func (ref *Node) exists(m meta.Definition) bool { if found != nil && cerr == nil { return true } + } else if meta.IsChoice(m) { + cs, cerr := ref.Choose(nil, m.(*meta.Choice)) + if cs != nil && cerr == nil { + // getting a case def might be the default case and not evidence + // data actually exists so we need to recurse into the case defs + for _, ddef := range cs.DataDefinitions() { + if ref.exists(ddef) { + return true + } + } + return true + } } else { r := node.FieldRequest{Meta: m.(meta.Leafable)} var hnd node.ValueHandle @@ -466,10 +483,10 @@ func (ref *Node) DoNewChild(r node.ChildRequest) (node.Node, error) { return nil, err } if meta.IsList(r.Meta) && r.Selection.Path.Meta != r.Meta { - return ref.NewList(obj.Interface(), ref.onListUpdate(r.Meta.(*meta.List))) + return ref.NewList(r.Meta, obj.Interface(), ref.onListUpdate(r.Meta.(*meta.List))) } - return ref.New(obj.Interface()), nil + return ref.New(r.Meta, obj.Interface()) } func (ref *Node) onListUpdate(m *meta.List) NodeListUpdate { @@ -508,30 +525,42 @@ func (ref *Node) DoGetChild(r node.ChildRequest) (node.Node, error) { return nil, nil } if meta.IsList(r.Meta) && r.Selection.Path.Meta != r.Meta { - return ref.NewList(obj.Interface(), ref.onListUpdate(r.Meta.(*meta.List))) + return ref.NewList(r.Meta, obj.Interface(), ref.onListUpdate(r.Meta.(*meta.List))) } - return ref.New(obj.Interface()), nil + return ref.New(r.Meta, obj.Interface()) } -func (ref *Node) NewList(obj any, u NodeListUpdate) (*Node, error) { - copy := ref.New(obj) - var err error - copy.l, err = ref.newListHandler(reflect.ValueOf(obj), u) +func (ref *Node) NewList(m meta.Meta, obj any, u NodeListUpdate) (node.Node, error) { + n, err := ref.New(m, obj) if err != nil { return nil, err } - return copy, nil + if copy, isCopy := n.(*Node); isCopy { + var err error + copy.l, err = ref.newListHandler(reflect.ValueOf(obj), u) + if err != nil { + return nil, err + } + } + return n, nil +} + +func (ref *Node) New(m meta.Meta, obj any) (node.Node, error) { + if ref.OnNewNode != nil { + return ref.OnNewNode(ref, m, obj) + } + return ref.DoNewNode(m, obj) } -func (ref *Node) New(obj any) *Node { +func (ref *Node) DoNewNode(m meta.Meta, obj any) (*Node, error) { if _, isVal := obj.(reflect.Value); isVal { - panic("passing in reflect.Value and not true obj") + return nil, errors.New("passing in reflect.Value and not true obj") } copy := *ref copy.Object = obj copy.l = nil copy.c = nil - return © + return ©, nil } type reflectContainer interface { @@ -587,7 +616,7 @@ func (ref *Node) DoGetByKey(r node.ListRequest) (node.Node, error) { if err != nil || !item.IsValid() || item.IsNil() { return nil, err } - return ref.New(item.Interface()), nil + return ref.New(r.Meta, item.Interface()) } func (ref *Node) DoGetByRow(r node.ListRequest) (node.Node, []val.Value, error) { @@ -605,7 +634,8 @@ func (ref *Node) DoGetByRow(r node.ListRequest) (node.Node, []val.Value, error) } } } - return ref.New(item.Interface()), key, nil + n, err := ref.New(r.Meta, item.Interface()) + return n, key, err } @@ -618,7 +648,7 @@ func (ref *Node) DoNewListItem(r node.ListRequest) (node.Node, error) { if err != nil || !item.IsValid() || item.IsNil() { return nil, err } - return ref.New(item.Interface()), nil + return ref.New(r.Meta, item.Interface()) } func (ref *Node) getValue(v val.Value) any { diff --git a/nodeutil/node_action.go b/nodeutil/node_action.go index 17a2996..a6b1953 100644 --- a/nodeutil/node_action.go +++ b/nodeutil/node_action.go @@ -33,13 +33,17 @@ func newActionHandler(src reflect.Value, rpc *meta.Rpc, opts NodeOptions) (*acti return def, nil } -func (def *actionHandler) do(n *Node, in *node.Selection) (node.Node, error) { +func (def *actionHandler) do(n *Node, m *meta.Rpc, in *node.Selection) (node.Node, error) { inVal, err := def.newInput(in != nil) if err != nil { return nil, err } if in != nil { - if err = in.UpsertInto(n.New(inVal.Interface())); err != nil { + inNode, err := n.New(m.Input(), inVal.Interface()) + if err != nil { + return nil, err + } + if err = in.UpsertInto(inNode); err != nil { return nil, err } } @@ -49,7 +53,7 @@ func (def *actionHandler) do(n *Node, in *node.Selection) (node.Node, error) { } if respVal.IsValid() { - return n.New(respVal.Interface()), nil + return n.New(m.Output(), respVal.Interface()) } return nil, nil diff --git a/nodeutil/node_slice.go b/nodeutil/node_slice.go index e11a976..23546a4 100644 --- a/nodeutil/node_slice.go +++ b/nodeutil/node_slice.go @@ -24,7 +24,7 @@ func newSliceAsList(ref *Node, src reflect.Value, u NodeListUpdate) *sliceAsList } func (def *sliceAsList) getByKey(r node.ListRequest) (reflect.Value, error) { - row, v, err := def.findByKey(r.Key, r.Meta.KeyMeta()) + row, v, err := def.findByKey(r.Meta, r.Key, r.Meta.KeyMeta()) if row < 0 || err != nil { return v, err } @@ -40,17 +40,20 @@ func (def *sliceAsList) getByRow(r node.ListRequest) (reflect.Value, []reflect.V if !v.IsValid() || v.IsZero() { return empty, nil, nil } - key, _, err := def.getKey(v, r.Meta.KeyMeta()) + key, _, err := def.getKey(v, r.Meta, r.Meta.KeyMeta()) return v, key, err } -func (def *sliceAsList) getKey(item reflect.Value, keyMeta []meta.Leafable) ([]reflect.Value, []val.Value, error) { +func (def *sliceAsList) getKey(item reflect.Value, m meta.Meta, keyMeta []meta.Leafable) ([]reflect.Value, []val.Value, error) { if len(keyMeta) == 0 || reflectIsEmpty(item) { return nil, nil, nil } // construct mock field requests to get key so we ensure we consult the correct customizations - ref2 := def.ref.New(item.Interface()) + ref2, err := def.ref.New(m, item.Interface()) + if err != nil { + return nil, nil, fmt.Errorf("%w attempting to get key", err) + } rvKey := make([]reflect.Value, len(keyMeta)) nvKey := make([]val.Value, len(keyMeta)) for i, kmeta := range keyMeta { @@ -61,12 +64,19 @@ func (def *sliceAsList) getKey(item reflect.Value, keyMeta []meta.Leafable) ([]r return nil, nil, fmt.Errorf("%w when get key", err) } nvKey[i] = hnd.Val - rvKey[i] = reflect.ValueOf(ref2.getValue(hnd.Val)) + switch x := ref2.(type) { + case *Node: + // use opts to help coerse value to right + rvKey[i] = reflect.ValueOf(x.getValue(hnd.Val)) + default: + // not a *Node, so just use value directly and trust it's right type + rvKey[i] = reflect.ValueOf(hnd.Val.Value()) + } } return rvKey, nvKey, nil } -func (def *sliceAsList) findByKey(target []val.Value, keyMeta []meta.Leafable) (int, reflect.Value, error) { +func (def *sliceAsList) findByKey(m meta.Meta, target []val.Value, keyMeta []meta.Leafable) (int, reflect.Value, error) { notfound := -1 var empty reflect.Value // full table scan of items in list to find first item that matches key. consider replacing @@ -77,7 +87,7 @@ func (def *sliceAsList) findByKey(target []val.Value, keyMeta []meta.Leafable) ( if !candidate.IsValid() { return notfound, empty, fmt.Errorf("row %d of %T is invalid", row, def.src.Type()) } - _, candidateKey, err := def.getKey(candidate, keyMeta) + _, candidateKey, err := def.getKey(candidate, m, keyMeta) if err != nil { return notfound, empty, err } @@ -95,7 +105,7 @@ func (def *sliceAsList) findByKey(target []val.Value, keyMeta []meta.Leafable) ( } func (def *sliceAsList) deleteByKey(r node.ListRequest) error { - row, _, err := def.findByKey(r.Key, r.Meta.KeyMeta()) + row, _, err := def.findByKey(r.Meta, r.Key, r.Meta.KeyMeta()) if row < 0 || err != nil { return err } diff --git a/nodeutil/schema2.go b/nodeutil/schema2.go index 582bb49..f306bda 100644 --- a/nodeutil/schema2.go +++ b/nodeutil/schema2.go @@ -64,7 +64,7 @@ func (api schema2) manage(obj any) node.Node { return n, nil case "dataDef": if x, isChoice := n.Object.(*meta.Choice); isChoice { - return n.NewList(x.Cases(), nil) + return n.NewList(r.Meta, x.Cases(), nil) } hasDefs := n.Object.(meta.HasDataDefinitions) if hasRecursiveChild(hasDefs) { @@ -77,7 +77,7 @@ func (api schema2) manage(obj any) node.Node { copy[i] = defs[i] } } - return n.NewList(copy, nil) + return n.NewList(r.Meta, copy, nil) } case "unique": if x, ok := n.Object.(*meta.List); ok { diff --git a/parser/testdata/augment/gold/refine.lex b/parser/testdata/augment/gold/refine.lex new file mode 100644 index 0000000..4b65cc3 --- /dev/null +++ b/parser/testdata/augment/gold/refine.lex @@ -0,0 +1,67 @@ +module "module" +[ident] "refine" +{ "{" +grouping "grouping" +[ident] "g1" +{ "{" +uses "uses" +[ident] "g2" +; ";" +} "}" +grouping "grouping" +[ident] "g2" +{ "{" +uses "uses" +[ident] "g3" +{ "{" +augment "augment" +[string] "\"t1/t2\"" +{ "{" +leaf "leaf" +[ident] "l2" +{ "{" +description "descriptio"... +[string] "\"orig\"" +; ";" +type "type" +[ident] "string" +; ";" +} "}" +} "}" +} "}" +} "}" +grouping "grouping" +[ident] "g3" +{ "{" +choice "choice" +[ident] "t1" +{ "{" +case "case" +[ident] "t2" +{ "{" +leaf "leaf" +[ident] "l1" +{ "{" +type "type" +[ident] "string" +; ";" +} "}" +} "}" +} "}" +} "}" +notification "notificati"... +[ident] "c" +{ "{" +uses "uses" +[ident] "g1" +{ "{" +refine "refine" +[string] "\"t1/t2/l2\"" +{ "{" +description "descriptio"... +[string] "\"here\"" +; ";" +} "}" +} "}" +} "}" +} "}" diff --git a/val/conv.go b/val/conv.go index ef00014..78a0095 100644 --- a/val/conv.go +++ b/val/conv.go @@ -17,14 +17,14 @@ func ConvOneOf(f []Format, val interface{}) (Value, Format, error) { return v, f, nil } } - return nil, 0, fmt.Errorf("Could not convert %v to any of the allowed types", val) + return nil, 0, fmt.Errorf("could not convert %v to any of the allowed types", val) } func Conv(f Format, val interface{}) (Value, error) { var err error defer func() { if r := recover(); r != nil { - err = fmt.Errorf("Could not convert %v to type %s", val, f) + err = fmt.Errorf("could not convert %v to type %s", val, f) } }() if val == nil {