Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ require (
github.com/sdcio/cache v0.0.38
github.com/sdcio/logger v0.0.3
github.com/sdcio/schema-server v0.0.34
github.com/sdcio/sdc-protos v0.0.51
github.com/sdcio/sdc-protos v0.0.52-0.20260504101706-192d1e8e12bf
github.com/sdcio/yang-parser v0.0.12
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ github.com/sdcio/logger v0.0.3 h1:IFUbObObGry+S8lHGwOQKKRxJSuOphgRU/hxVhOdMOM=
github.com/sdcio/logger v0.0.3/go.mod h1:yWaOxK/G6vszjg8tKZiMqiEjlZouHsjFME4zSk+SAEA=
github.com/sdcio/schema-server v0.0.34 h1:NNDOkvtUMONtBA7cVvN96F+FWGD/Do6HNqfchy9B8eI=
github.com/sdcio/schema-server v0.0.34/go.mod h1:6t8HLXpqUqEJmE5yNZh29u/KZw0jlOICdNWns7zE4GE=
github.com/sdcio/sdc-protos v0.0.51 h1:sFc2ct8v4D7rBgFdg/fmXgJffcFhupfx4QJKsSAoolA=
github.com/sdcio/sdc-protos v0.0.51/go.mod h1:FkJMZWtp7Rcc/EedbX2mt1tET/j8KdavNl2BsHf03+o=
github.com/sdcio/sdc-protos v0.0.52-0.20260504101706-192d1e8e12bf h1:O+6B6u6J9ul6uGdG5vyeK5B2eu9ujgubVCtERl0lG8o=
github.com/sdcio/sdc-protos v0.0.52-0.20260504101706-192d1e8e12bf/go.mod h1:FkJMZWtp7Rcc/EedbX2mt1tET/j8KdavNl2BsHf03+o=
github.com/sdcio/yang-parser v0.0.12 h1:RSSeqfAOIsJx5Lno5u4/ezyOmQYUduQ22rBfU/mtpJ4=
github.com/sdcio/yang-parser v0.0.12/go.mod h1:CBqn3Miq85qmFVGHxHXHLluXkaIOsTzV06IM4DW6+D4=
github.com/sirikothe/gotextfsm v1.0.1-0.20200816110946-6aa2cfd355e4 h1:FHUL2HofYJuslFOQdy/JjjP36zxqIpd/dcoiwLMIs7k=
Expand Down
5 changes: 3 additions & 2 deletions pkg/tree/importer/import_config_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ type ImportConfigAdapterElement interface {
// When and were to expect a Leafs or LeafList is defined by the yang schema.
// The String value is typically used for the keys.
GetKeyValue(ctx context.Context, slt *sdcpb.SchemaLeafType) (string, error)
// GetTVValue returns the TypedValue based value defined via the SchemaLeafType. Can also only be called on Leafs or LeafLists
GetTVValue(ctx context.Context, slt *sdcpb.SchemaLeafType) (*sdcpb.TypedValue, error)
// GetTVValue returns the TypedValue based value defined via the SchemaLeafType. Can also only be called on Leafs or LeafLists.
// For union-typed leaves the second return value is the matched branch SchemaLeafType; for non-union or proto-loaded values it is nil.
GetTVValue(ctx context.Context, slt *sdcpb.SchemaLeafType) (*sdcpb.TypedValue, *sdcpb.SchemaLeafType, error)
// returns the name of the actual Level.
GetName() string
}
4 changes: 2 additions & 2 deletions pkg/tree/importer/json/json_tree_importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ func (j *JsonTreeImporterElement) GetKeyValue(ctx context.Context, slt *sdcpb.Sc
return fmt.Sprintf("%v", j.data), nil
}

func (j *JsonTreeImporterElement) GetTVValue(ctx context.Context, slt *sdcpb.SchemaLeafType) (*sdcpb.TypedValue, error) {
return sdcpb.ConvertJsonValueToTv(j.data, slt)
func (j *JsonTreeImporterElement) GetTVValue(ctx context.Context, slt *sdcpb.SchemaLeafType) (*sdcpb.TypedValue, *sdcpb.SchemaLeafType, error) {
return sdcpb.ConvertJsonValueToTvWithType(j.data, slt)
}

func (j *JsonTreeImporterElement) GetName() string {
Expand Down
45 changes: 35 additions & 10 deletions pkg/tree/importer/json/json_tree_importer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,12 @@ func TestJsonTreeImporter_GetTVValue(t *testing.T) {
slt *sdcpb.SchemaLeafType
}
tests := []struct {
name string
fields fields
args args
want *sdcpb.TypedValue
wantErr bool
name string
fields fields
args args
want *sdcpb.TypedValue
wantMatchedType string // expected matched type name ("" means same as input type)
wantErr bool
}{
{
name: "string",
Expand All @@ -144,8 +145,9 @@ func TestJsonTreeImporter_GetTVValue(t *testing.T) {
Type: "string",
},
},
wantErr: false,
want: &sdcpb.TypedValue{Value: &sdcpb.TypedValue_StringVal{StringVal: "foobar"}},
wantErr: false,
wantMatchedType: "string",
want: &sdcpb.TypedValue{Value: &sdcpb.TypedValue_StringVal{StringVal: "foobar"}},
},
{
name: "int",
Expand All @@ -158,8 +160,28 @@ func TestJsonTreeImporter_GetTVValue(t *testing.T) {
Type: "int32",
},
},
wantErr: false,
want: &sdcpb.TypedValue{Value: &sdcpb.TypedValue_IntVal{IntVal: 5}},
wantErr: false,
wantMatchedType: "int32",
want: &sdcpb.TypedValue{Value: &sdcpb.TypedValue_IntVal{IntVal: 5}},
},
{
name: "union matched string branch",
fields: fields{
name: "foo",
data: "hello",
},
args: args{
&sdcpb.SchemaLeafType{
Type: "union",
UnionTypes: []*sdcpb.SchemaLeafType{
{Type: "uint32"},
{Type: "string"},
},
},
},
wantErr: false,
wantMatchedType: "string",
want: &sdcpb.TypedValue{Value: &sdcpb.TypedValue_StringVal{StringVal: "hello"}},
},
}
for _, tt := range tests {
Expand All @@ -168,14 +190,17 @@ func TestJsonTreeImporter_GetTVValue(t *testing.T) {
data: tt.fields.data,
name: tt.fields.name,
}
got, err := j.GetTVValue(ctx, tt.args.slt)
got, matchedType, err := j.GetTVValue(ctx, tt.args.slt)
if (err != nil) != tt.wantErr {
t.Errorf("JsonTreeImporter.GetTVValue() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("JsonTreeImporter.GetTVValue() = %v, want %v", got, tt.want)
}
if tt.wantMatchedType != "" && (matchedType == nil || matchedType.Type != tt.wantMatchedType) {
t.Errorf("JsonTreeImporter.GetTVValue() matchedType = %v, want type %q", matchedType, tt.wantMatchedType)
}
})
}
}
10 changes: 6 additions & 4 deletions pkg/tree/importer/proto/proto_tree_importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,22 @@ func (p *ProtoTreeImporterElement) GetElement(key string) importer.ImportConfigA
}

func (p *ProtoTreeImporterElement) GetKeyValue(ctx context.Context, slt *sdcpb.SchemaLeafType) (string, error) {
tv, err := p.GetTVValue(ctx, slt)
tv, _, err := p.GetTVValue(ctx, slt)
if err != nil {
return "", fmt.Errorf("failed GetTVValue for %s", p.data.Name)
}
return tv.ToString(), nil
}

func (p *ProtoTreeImporterElement) GetTVValue(ctx context.Context, slt *sdcpb.SchemaLeafType) (*sdcpb.TypedValue, error) {
// GetTVValue unmarshals the proto-serialized TypedValue. No original lexical context is available,
// so the matched union branch type is always nil (graceful fallback to outer schema type in validators).
func (p *ProtoTreeImporterElement) GetTVValue(ctx context.Context, slt *sdcpb.SchemaLeafType) (*sdcpb.TypedValue, *sdcpb.SchemaLeafType, error) {
result := &sdcpb.TypedValue{}
err := proto.Unmarshal(p.data.LeafVariant, result)
if err != nil {
return nil, err
return nil, nil, err
}
return result, nil
return result, nil, nil
}
func (p *ProtoTreeImporterElement) GetName() string {
return p.data.Name
Expand Down
35 changes: 35 additions & 0 deletions pkg/tree/importer/proto/proto_tree_importer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import (
"github.com/sdcio/data-server/pkg/tree/ops"
"github.com/sdcio/data-server/pkg/tree/types"
"github.com/sdcio/data-server/pkg/utils/testhelper"
sdcpb "github.com/sdcio/sdc-protos/sdcpb"
"github.com/sdcio/sdc-protos/tree_persist"
"go.uber.org/mock/gomock"
"google.golang.org/protobuf/proto"
)

func TestProtoTreeImporter(t *testing.T) {
Expand Down Expand Up @@ -175,3 +178,35 @@ func TestProtoTreeImporter(t *testing.T) {
})
}
}

func TestProtoTreeImporterElement_GetTVValue_NilMatchedType(t *testing.T) {
ctx := context.Background()

// Proto importer has no original lexical context, so matched type must always be nil.
tv := &sdcpb.TypedValue{Value: &sdcpb.TypedValue_StringVal{StringVal: "hello"}}
raw, err := proto.Marshal(tv)
if err != nil {
t.Fatal(err)
}

elem := NewProtoTreeImporterElement(&tree_persist.TreeElement{
Name: "leaf",
LeafVariant: raw,
})

unionSlt := &sdcpb.SchemaLeafType{
Type: "union",
UnionTypes: []*sdcpb.SchemaLeafType{
{Type: "uint32"},
{Type: "string"},
},
}

_, matchedType, err := elem.GetTVValue(ctx, unionSlt)
if err != nil {
t.Fatalf("GetTVValue() unexpected error: %v", err)
}
if matchedType != nil {
t.Errorf("proto importer should return nil matchedType, got %v", matchedType)
}
}
4 changes: 2 additions & 2 deletions pkg/tree/importer/xml/xml_tree_importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ func (x *XmlTreeImporterElement) GetKeyValue(ctx context.Context, slt *sdcpb.Sch
return tv.ToString(), nil
}

func (x *XmlTreeImporterElement) GetTVValue(ctx context.Context, slt *sdcpb.SchemaLeafType) (*sdcpb.TypedValue, error) {
return sdcpb.TVFromString(slt, x.elem.Text(), 0)
func (x *XmlTreeImporterElement) GetTVValue(ctx context.Context, slt *sdcpb.SchemaLeafType) (*sdcpb.TypedValue, *sdcpb.SchemaLeafType, error) {
return sdcpb.TVFromStringWithType(slt, x.elem.Text(), 0)
}

func (x *XmlTreeImporterElement) GetName() string {
Expand Down
64 changes: 64 additions & 0 deletions pkg/tree/importer/xml/xml_tree_importer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,67 @@ func TestXmlTreeImporterElement_IdentityRef(t *testing.T) {
t.Fatalf("Integrating xml failed. mismatch (-want +got).\nDiff:\n%s", diff)
}
}

func TestXmlTreeImporterElement_GetTVValue_MatchedType(t *testing.T) {
ctx := context.Background()

tests := []struct {
name string
xmlText string
slt *sdcpb.SchemaLeafType
wantMatchedType string // expected matched branch TypeName ("" = nil expected)
wantErr bool
}{
{
name: "non-union returns input type",
xmlText: "hello",
slt: &sdcpb.SchemaLeafType{Type: "string"},
wantMatchedType: "string",
},
{
name: "union matched string branch",
xmlText: "hello",
slt: &sdcpb.SchemaLeafType{
Type: "union",
UnionTypes: []*sdcpb.SchemaLeafType{
{Type: "uint32"},
{Type: "string"},
},
},
wantMatchedType: "string",
},
{
name: "union matched uint32 branch",
xmlText: "42",
slt: &sdcpb.SchemaLeafType{
Type: "union",
UnionTypes: []*sdcpb.SchemaLeafType{
{Type: "uint32"},
{Type: "string"},
},
},
wantMatchedType: "uint32",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
doc := etree.NewDocument()
doc.ReadFromString("<leaf>" + tt.xmlText + "</leaf>")
elem := NewXmlTreeImporterElement(doc.Root())

_, matchedType, err := elem.GetTVValue(ctx, tt.slt)
if (err != nil) != tt.wantErr {
t.Fatalf("GetTVValue() error = %v, wantErr %v", err, tt.wantErr)
}
if tt.wantMatchedType == "" {
if matchedType != nil {
t.Errorf("expected nil matchedType, got %v", matchedType)
}
} else {
if matchedType == nil || matchedType.Type != tt.wantMatchedType {
t.Errorf("matchedType = %v, want type %q", matchedType, tt.wantMatchedType)
}
}
})
}
}
29 changes: 24 additions & 5 deletions pkg/tree/ops/validation/validation_entry_leafref.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ func navigateLeafRef(ctx context.Context, e api.Entry) ([]api.Entry, error) {
return nil, fmt.Errorf("error not a leafref %s", e.SdcpbPath())
}

return navigateLeafRefByPath(ctx, e, lref)
}

// navigateLeafRefByPath navigates the tree to find entries matching the given leafref path and
// the current entry's value. It is the implementation for both direct leafref leaves and union
// leaves whose matched branch is a leafref.
func navigateLeafRefByPath(ctx context.Context, e api.Entry, lref string) ([]api.Entry, error) {
lv := e.GetLeafVariants().GetHighestPrecedence(false, true, false)
if lv == nil {
return nil, fmt.Errorf("no leafvariant found")
Expand Down Expand Up @@ -206,15 +213,27 @@ func validateLeafRefs(ctx context.Context, e api.Entry, resultChan chan<- *types
return
}

lref := e.GetSchema().GetField().GetType().GetLeafref()
if e.GetSchema() == nil || lref == "" {
if e.GetSchema() == nil || e.GetSchema().GetField() == nil {
return
}

// Resolve the effective leaf type — for union leaves, this returns the matched branch;
// for direct leafref leaves it returns the outer schema type unchanged.
lv := e.GetLeafVariants().GetHighestPrecedence(false, true, false)
effectiveType := e.GetSchema().GetField().GetType()
if lv != nil {
effectiveType = lv.Update.EffectiveLeafType(e.GetSchema().GetField().GetType())
}

lref := effectiveType.GetLeafref()
if lref == "" {
return
}

entry, err := navigateLeafRef(ctx, e)
entry, err := navigateLeafRefByPath(ctx, e, lref)
if err != nil || len(entry) == 0 {
// check if the OptionalInstance (!require-instances [https://datatracker.ietf.org/doc/html/rfc7950#section-9.9.3])
if e.GetSchema().GetField().GetType().GetOptionalInstance() {
if effectiveType.GetOptionalInstance() {
generateOptionalWarning(e, lref, resultChan)
return
}
Expand All @@ -239,7 +258,7 @@ func validateLeafRefs(ctx context.Context, e api.Entry, resultChan chan<- *types
}

// check if the OptionalInstance (!require-instances [https://datatracker.ietf.org/doc/html/rfc7950#section-9.9.3])
if e.GetSchema().GetField().GetType().GetOptionalInstance() {
if effectiveType.GetOptionalInstance() {
generateOptionalWarning(e, lref, resultChan)
return
}
Expand Down
15 changes: 9 additions & 6 deletions pkg/tree/ops/validation/validation_entry_length.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,33 @@ import (
func validateLength(_ context.Context, e api.Entry, resultChan chan<- *types.ValidationResultEntry, stats *types.ValidationStats) {
if schema := e.GetSchema().GetField(); schema != nil {

if len(schema.GetType().GetLength()) == 0 {
lv := e.GetLeafVariants().GetHighestPrecedence(false, true, false)
if lv == nil {
return
}

lv := e.GetLeafVariants().GetHighestPrecedence(false, true, false)
if lv == nil {
effectiveType := lv.Update.EffectiveLeafType(schema.GetType())

if len(effectiveType.GetLength()) == 0 {
return
}

value := lv.Value().GetStringVal()
actualLength := utf8.RuneCountInString(value)

for _, lengthDef := range schema.GetType().GetLength() {
for _, lengthDef := range effectiveType.GetLength() {

if lengthDef.Min.Value <= uint64(actualLength) && uint64(actualLength) <= lengthDef.Max.Value {
// continue if the length is within the range
continue
}
// this is already the failure case
lenghts := []string{}
for _, lengthDef := range schema.GetType().GetLength() {
for _, lengthDef := range effectiveType.GetLength() {
lenghts = append(lenghts, fmt.Sprintf("%d..%d", lengthDef.Min.Value, lengthDef.Max.Value))
}
resultChan <- types.NewValidationResultEntry(lv.Owner(), fmt.Errorf("error length of Path: %s, Value: %s not within allowed length %s", e.SdcpbPath().ToXPath(false), value, strings.Join(lenghts, ", ")), types.ValidationResultEntryTypeError)
}
stats.Add(types.StatTypeLength, uint32(len(schema.GetType().GetLength())))
stats.Add(types.StatTypeLength, uint32(len(effectiveType.GetLength())))
}
}
Loading
Loading