Skip to content

Commit 880d39d

Browse files
committed
feat: Adds ExtractItems option to include key fields
Adds an option to `ExtractItems` to include key fields in the output.
1 parent dc61957 commit 880d39d

File tree

2 files changed

+148
-1
lines changed

2 files changed

+148
-1
lines changed

Diff for: typed/remove_test.go

+113
Original file line numberDiff line numberDiff line change
@@ -906,3 +906,116 @@ func TestReversibleExtract(t *testing.T) {
906906
})
907907
}
908908
}
909+
910+
type extractWithKeysTestCase struct {
911+
name string
912+
rootTypeName string
913+
schema typed.YAMLObject
914+
triplets []extractTriplet
915+
}
916+
917+
type extractTriplet struct {
918+
object typed.YAMLObject
919+
set *fieldpath.Set
920+
wantOutput interface{}
921+
}
922+
923+
var extractWithKeysCases = []extractWithKeysTestCase{{
924+
name: "associativeAndAtomicSchema",
925+
rootTypeName: "myRoot",
926+
schema: typed.YAMLObject(associativeAndAtomicSchema),
927+
triplets: []extractTriplet{
928+
{
929+
// extract with all key fields included
930+
object: `{"list":[{"key":"nginx","id":1,"nv":2}]}`,
931+
set: _NS(
932+
_P("list", _KBF("key", "nginx", "id", 1), "key"),
933+
_P("list", _KBF("key", "nginx", "id", 1), "id"),
934+
),
935+
wantOutput: typed.YAMLObject(`{"list":[{"key":"nginx","id":1}]}`),
936+
},
937+
{
938+
// extract no key field included
939+
object: `{"list":[{"key":"nginx","id":1,"nv":2}]}`,
940+
set: _NS(
941+
_P("list", _KBF("key", "nginx", "id", 1), "nv"),
942+
),
943+
wantOutput: typed.YAMLObject(`{"list":[{"key":"nginx","id":1, "nv":2}]}`),
944+
},
945+
{
946+
// extract with patial keys included
947+
object: `{"list":[{"key":"nginx","id":1,"nv":2}]}`,
948+
set: _NS(
949+
_P("list", _KBF("key", "nginx", "id", 1), "nv"),
950+
_P("list", _KBF("key", "nginx", "id", 1), "id"),
951+
),
952+
wantOutput: typed.YAMLObject(`{"list":[{"key":"nginx","id":1, "nv":2}]}`),
953+
},
954+
{
955+
// extract with null field value
956+
object: `{"list":[{"key":"nginx","id":1,"nv":2}]}`,
957+
set: _NS(
958+
_P("list", _KBF("key", "nginx", "id", 1), "value"),
959+
),
960+
wantOutput: map[string]interface{}{
961+
"list": []interface{}{nil},
962+
},
963+
},
964+
},
965+
}}
966+
967+
func (tt extractWithKeysTestCase) test(t *testing.T) {
968+
parser, err := typed.NewParser(tt.schema)
969+
if err != nil {
970+
t.Fatalf("failed to create schema: %v", err)
971+
}
972+
pt := parser.Type(tt.rootTypeName)
973+
974+
for i, triplet := range tt.triplets {
975+
triplet := triplet
976+
t.Run(fmt.Sprintf("%v-valid-%v", tt.name, i), func(t *testing.T) {
977+
t.Parallel()
978+
// source typedValue obj
979+
tv, err := pt.FromYAML(triplet.object)
980+
if err != nil {
981+
t.Fatal(err)
982+
}
983+
gotExtracted := tv.ExtractItems(triplet.set, typed.AppendKeyField)
984+
985+
switch triplet.wantOutput.(type) {
986+
case typed.YAMLObject:
987+
wantOut, err := pt.FromYAML(triplet.wantOutput.(typed.YAMLObject))
988+
if err != nil {
989+
t.Fatalf("unable to parser/validate removeOutput yaml: %v\n%v", err, triplet.wantOutput)
990+
}
991+
992+
if !value.Equals(gotExtracted.AsValue(), wantOut.AsValue()) {
993+
t.Errorf("ExtractItems expected\n%v\nbut got\n%v\n",
994+
value.ToString(wantOut.AsValue()), value.ToString(gotExtracted.AsValue()),
995+
)
996+
}
997+
default:
998+
// The extracted result
999+
wantOut := value.NewValueInterface(triplet.wantOutput)
1000+
if !value.Equals(gotExtracted.AsValue(), wantOut) {
1001+
t.Errorf("ExtractItems expected\n%v\nbut got\n%v\n",
1002+
value.ToString(wantOut), value.ToString(gotExtracted.AsValue()),
1003+
)
1004+
}
1005+
}
1006+
})
1007+
}
1008+
}
1009+
1010+
// TestExtractWithKeys ensures that when you extract
1011+
// items from an object with the AppendKeyField option,
1012+
// the key fields are also included in the output.
1013+
func TestExtractWithKeys(t *testing.T) {
1014+
for _, tt := range extractWithKeysCases {
1015+
tt := tt
1016+
t.Run(tt.name, func(t *testing.T) {
1017+
t.Parallel()
1018+
tt.test(t)
1019+
})
1020+
}
1021+
}

Diff for: typed/typed.go

+35-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ const (
3232
AllowDuplicates ValidationOptions = iota
3333
)
3434

35+
// ExtractItemsOptions is the list of all the options available when extracting items.
36+
type ExtractItemsOptions int
37+
38+
const (
39+
// AppendKeyField means that when extracting items, the key field would also be included.
40+
AppendKeyField ExtractItemsOptions = iota
41+
)
42+
3543
// AsTyped accepts a value and a type and returns a TypedValue. 'v' must have
3644
// type 'typeName' in the schema. An error is returned if the v doesn't conform
3745
// to the schema.
@@ -187,7 +195,33 @@ func (tv TypedValue) RemoveItems(items *fieldpath.Set) *TypedValue {
187195
}
188196

189197
// ExtractItems returns a value with only the provided list or map items extracted from the value.
190-
func (tv TypedValue) ExtractItems(items *fieldpath.Set) *TypedValue {
198+
func (tv TypedValue) ExtractItems(items *fieldpath.Set, opts ...ExtractItemsOptions) *TypedValue {
199+
for _, opt := range opts {
200+
switch opt {
201+
case AppendKeyField:
202+
tvPathSet, err := tv.ToFieldSet()
203+
if err != nil {
204+
continue
205+
}
206+
keyFieldPathSet := fieldpath.NewSet()
207+
items.Iterate(func(path fieldpath.Path) {
208+
if !tvPathSet.Has(path) {
209+
return
210+
}
211+
for i, pe := range path {
212+
if pe.Key == nil {
213+
continue
214+
}
215+
for _, keyField := range *pe.Key {
216+
keyName := keyField.Name
217+
keyFieldPath := append(path[:i+1:i+1], fieldpath.PathElement{FieldName: &keyName})
218+
keyFieldPathSet.Insert(keyFieldPath)
219+
}
220+
}
221+
})
222+
items = items.Union(keyFieldPathSet)
223+
}
224+
}
191225
tv.value = removeItemsWithSchema(tv.value, items, tv.schema, tv.typeRef, true)
192226
return &tv
193227
}

0 commit comments

Comments
 (0)