Skip to content

Commit a18a714

Browse files
committed
merge prerelease branch
1 parent 9d010a1 commit a18a714

File tree

4 files changed

+60
-110
lines changed

4 files changed

+60
-110
lines changed

ldattr/ref.go

Lines changed: 24 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ package ldattr
22

33
import (
44
"encoding/json"
5-
"strconv"
65
"strings"
76

87
"github.com/launchdarkly/go-sdk-common/v3/lderrors"
9-
"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
108

119
"github.com/launchdarkly/go-jsonstream/v3/jreader"
1210
)
@@ -34,11 +32,10 @@ import (
3432
// An attribute name can contain any characters, but must not be empty.
3533
//
3634
// If the first character is a slash, the string is interpreted as a slash-delimited path where the
37-
// first path component is an attribute name, and each subsequent path component is either the name of
38-
// a property in a JSON object, or a decimal numeric string that is the index of an element in a JSON
39-
// array. Any instances of the characters "/" or "~" in a path component are escaped as "~1" or "~0"
40-
// respectively. This syntax deliberately resembles JSON Pointer, but no JSON Pointer behaviors other
41-
// than those mentioned here are supported.
35+
// first path component is an attribute name, and each subsequent path component is the name of a
36+
// property in a JSON object. Any instances of the characters "/" or "~" in a path component are
37+
// escaped as "~1" or "~0" respectively. This syntax deliberately resembles JSON Pointer, but no JSON
38+
// Pointer behaviors other than those mentioned here are supported.
4239
//
4340
// # Examples
4441
//
@@ -48,30 +45,25 @@ import (
4845
// "kind": "user",
4946
// "key": "value1",
5047
// "address": {
51-
// "street": "value2",
52-
// "city": "value3"
48+
// "street": {
49+
// "line1": "value2",
50+
// "line2": "value3"
51+
// },
52+
// "city": "value4"
5353
// },
54-
// "groups": [ "value4", "value5" ],
55-
// "good/bad": "value6"
54+
// "good/bad": "value5"
5655
// }
5756
//
5857
// The attribute references "key" and "/key" would both point to "value1".
5958
//
60-
// The attribute reference "/address/street" would point to "value2".
59+
// The attribute reference "/address/street/line1" would point to "value2".
6160
//
62-
// The attribute reference "/groups/0" would point to "value4".
63-
//
64-
// The attribute references "good/bad" and "/good~1bad" would both point to "value6".
61+
// The attribute references "good/bad" and "/good~1bad" would both point to "value5".
6562
type Ref struct {
6663
err error
6764
rawPath string
6865
singlePathComponent string
69-
components []attrRefComponent
70-
}
71-
72-
type attrRefComponent struct {
73-
name string
74-
intValue ldvalue.OptionalInt
66+
components []string
7567
}
7668

7769
// NewRef creates a Ref from a string. For the supported syntax and examples, see comments on the Ref type.
@@ -98,7 +90,7 @@ func NewRef(referenceString string) Ref {
9890
return Ref{err: lderrors.ErrAttributeInvalidEscape{}, rawPath: referenceString}
9991
}
10092
parts := strings.Split(path, "/")
101-
ret := Ref{rawPath: referenceString, components: make([]attrRefComponent, 0, len(parts))}
93+
ret := Ref{rawPath: referenceString, components: make([]string, 0, len(parts))}
10294
for _, p := range parts {
10395
if p == "" {
10496
ret.err = lderrors.ErrAttributeExtraSlash{}
@@ -108,13 +100,7 @@ func NewRef(referenceString string) Ref {
108100
if !ok {
109101
return Ref{err: lderrors.ErrAttributeInvalidEscape{}, rawPath: referenceString}
110102
}
111-
component := attrRefComponent{name: unescaped}
112-
if p[0] >= '0' && p[0] <= '9' {
113-
if n, err := strconv.Atoi(p); err == nil {
114-
component.intValue = ldvalue.NewOptionalInt(n)
115-
}
116-
}
117-
ret.components = append(ret.components, component)
103+
ret.components = append(ret.components, unescaped)
118104
}
119105
return ret
120106
}
@@ -198,29 +184,23 @@ func (a Ref) Depth() int {
198184
// Component retrieves a single path component from the attribute reference.
199185
//
200186
// For a simple attribute reference such as "name" with no leading slash, if index is zero,
201-
// Component returns the attribute name and an empty ldvalue.OptionalInt{}.
187+
// Component returns the attribute name.
202188
//
203189
// For an attribute reference with a leading slash, if index is non-negative and less than
204-
// a.Depth(), Component returns the path component as a string for its first value. The
205-
// second value is an ldvalue.OptionalInt that is the integer value of that string as returned
206-
// by strconv.Atoi() if applicable, or an empty ldvalue.OptionalInt{} if the string does not
207-
// represent an integer; this is used to implement a "find a value by index within a JSON
208-
// array" behavior similar to JSON Pointer.
190+
// a.Depth(), Component returns the path component.
209191
//
210-
// If index is out of range, it returns "" and an empty ldvalue.OptionalInt{}.
192+
// If index is out of range, it returns "".
211193
//
212-
// NewRef("a").Component(0) // returns ("a", ldvalue.OptionalInt{})
213-
// NewRef("/a/b").Component(1) // returns ("b", ldvalue.OptionalInt{})
214-
// NewRef("/a/3").Component(1) // returns ("3", ldvalue.NewOptionalInt(3))
215-
func (a Ref) Component(index int) (string, ldvalue.OptionalInt) {
194+
// NewRef("a").Component(0) // returns "a"
195+
// NewRef("/a/b").Component(1) // returns "b"
196+
func (a Ref) Component(index int) string {
216197
if index == 0 && len(a.components) == 0 {
217-
return a.singlePathComponent, ldvalue.OptionalInt{}
198+
return a.singlePathComponent
218199
}
219200
if index < 0 || index >= len(a.components) {
220-
return "", ldvalue.OptionalInt{}
201+
return ""
221202
}
222-
c := a.components[index]
223-
return c.name, c.intValue
203+
return a.components[index]
224204
}
225205

226206
// String returns the attribute reference as a string, in the same format used by NewRef().

ldattr/ref_test.go

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"testing"
77

88
"github.com/launchdarkly/go-sdk-common/v3/lderrors"
9-
"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
109

1110
"github.com/stretchr/testify/assert"
1211
)
@@ -58,8 +57,7 @@ func TestRefWithNoLeadingSlash(t *testing.T) {
5857
assert.NoError(t, a.Err())
5958
assert.Equal(t, s, a.String())
6059
assert.Equal(t, 1, a.Depth())
61-
name, _ := a.Component(0)
62-
assert.Equal(t, s, name)
60+
assert.Equal(t, s, a.Component(0))
6361
})
6462
}
6563
}
@@ -82,8 +80,7 @@ func TestRefSimpleWithLeadingSlash(t *testing.T) {
8280
assert.NoError(t, a.Err())
8381
assert.Equal(t, params.input, a.String())
8482
assert.Equal(t, 1, a.Depth())
85-
name, _ := a.Component(0)
86-
assert.Equal(t, params.path, name)
83+
assert.Equal(t, params.path, a.Component(0))
8784
})
8885
}
8986
}
@@ -108,44 +105,37 @@ func TestNewLiteralRef(t *testing.T) {
108105
}
109106

110107
func TestRefComponents(t *testing.T) {
111-
undefined := -99
112108
for _, params := range []struct {
113-
input string
114-
depth int
115-
index int
116-
expectedName string
117-
expectedIndex int
109+
input string
110+
depth int
111+
index int
112+
expectedName string
118113
}{
119-
{"", 0, 0, "", undefined},
120-
{"key", 1, 0, "key", undefined},
121-
{"/key", 1, 0, "key", undefined},
122-
{"/a/b", 2, 0, "a", undefined},
123-
{"/a/b", 2, 1, "b", undefined},
124-
{"/a~1b/c", 2, 0, "a/b", undefined},
125-
{"/a~0b/c", 2, 0, "a~b", undefined},
126-
{"/a/10/20/30x", 4, 1, "10", 10},
127-
{"/a/10/20/30x", 4, 2, "20", 20},
128-
{"/a/10/20/30x", 4, 3, "30x", undefined},
114+
{"", 0, 0, ""},
115+
{"key", 1, 0, "key"},
116+
{"/key", 1, 0, "key"},
117+
{"/a/b", 2, 0, "a"},
118+
{"/a/b", 2, 1, "b"},
119+
{"/a~1b/c", 2, 0, "a/b"},
120+
{"/a~0b/c", 2, 0, "a~b"},
121+
{"/a/10/20/30x", 4, 1, "10"},
122+
{"/a/10/20/30x", 4, 2, "20"},
123+
{"/a/10/20/30x", 4, 3, "30x"},
129124

130125
// invalid arguments don't cause an error, they just return empty values
131-
{"", 0, -1, "", undefined},
132-
{"key", 1, -1, "", undefined},
133-
{"key", 1, 1, "", undefined},
134-
{"/key", 1, -1, "", undefined},
135-
{"/key", 1, 1, "", undefined},
136-
{"/a/b", 2, -1, "", undefined},
137-
{"/a/b", 2, 2, "", undefined},
126+
{"", 0, -1, ""},
127+
{"key", 1, -1, ""},
128+
{"key", 1, 1, ""},
129+
{"/key", 1, -1, ""},
130+
{"/key", 1, 1, ""},
131+
{"/a/b", 2, -1, ""},
132+
{"/a/b", 2, 2, ""},
138133
} {
139134
t.Run(fmt.Sprintf("input string %q, index %d", params.input, params.index), func(t *testing.T) {
140135
a := NewRef(params.input)
141136
assert.Equal(t, params.depth, a.Depth())
142-
name, index := a.Component(params.index)
137+
name := a.Component(params.index)
143138
assert.Equal(t, params.expectedName, name)
144-
if params.expectedIndex == undefined {
145-
assert.Equal(t, ldvalue.OptionalInt{}, index)
146-
} else {
147-
assert.Equal(t, ldvalue.NewOptionalInt(params.expectedIndex), index)
148-
}
149139
})
150140
}
151141
}

ldcontext/context.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ func (c Context) GetValueForRef(ref ldattr.Ref) ldvalue.Value {
188188
return ldvalue.Null()
189189
}
190190

191-
firstPathComponent, _ := ref.Component(0)
191+
firstPathComponent := ref.Component(0)
192192

193193
if c.Multiple() {
194194
if ref.Depth() == 1 && firstPathComponent == ldattr.KindAttr {
@@ -203,13 +203,9 @@ func (c Context) GetValueForRef(ref ldattr.Ref) ldvalue.Value {
203203
return ldvalue.Null()
204204
}
205205
for i := 1; i < ref.Depth(); i++ {
206-
name, index := ref.Component(i)
207-
if index.IsDefined() && value.Type() == ldvalue.ArrayType {
208-
value, ok = value.TryGetByIndex(index.IntValue())
209-
} else {
210-
value, ok = value.TryGetByKey(name)
211-
}
212-
if !ok {
206+
name := ref.Component(i)
207+
value, ok = value.TryGetByKey(name)
208+
if !ok { // ok is false if either the name is not found or the value was not an object
213209
return ldvalue.Null()
214210
}
215211
}

ldcontext/context_test.go

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -270,38 +270,22 @@ func TestGetValueForRefCustomAttributeSingleKind(t *testing.T) {
270270
expectAttributeNotFoundForRef(t, c, "/my-attr/my-prop")
271271
})
272272

273-
t.Run("element in array", func(t *testing.T) {
273+
t.Run("property whose name is a numeric string", func(t *testing.T) {
274274
expected := ldvalue.String("good")
275-
array := ldvalue.ArrayOf(ldvalue.String("bad"), expected, ldvalue.String("worse"))
276-
c := makeBasicBuilder().SetValue("my-attr", array).Build()
277-
expectAttributeFoundForRef(t, expected, c, "/my-attr/1")
278-
})
279-
280-
t.Run("element in nested array in object", func(t *testing.T) {
281-
expected := ldvalue.String("good")
282-
array := ldvalue.ArrayOf(ldvalue.String("bad"), expected, ldvalue.String("worse"))
283-
object := ldvalue.ObjectBuild().Set("my-prop", array).Build()
275+
object := ldvalue.ObjectBuild().Set("1", expected).Build()
284276
c := makeBasicBuilder().SetValue("my-attr", object).Build()
285-
expectAttributeFoundForRef(t, expected, c, "/my-attr/my-prop/1")
286-
})
287-
288-
t.Run("index too low in array", func(t *testing.T) {
289-
expected := ldvalue.String("good")
290-
array := ldvalue.ArrayOf(ldvalue.String("bad"), expected, ldvalue.String("worse"))
291-
c := makeBasicBuilder().SetValue("my-attr", array).Build()
292-
expectAttributeNotFoundForRef(t, c, "/my-attr/-1")
277+
expectAttributeFoundForRef(t, expected, c, "/my-attr/1")
293278
})
294279

295-
t.Run("index too high in array", func(t *testing.T) {
296-
expected := ldvalue.String("good")
297-
array := ldvalue.ArrayOf(ldvalue.String("bad"), expected, ldvalue.String("worse"))
280+
t.Run("property not applicable to array", func(t *testing.T) {
281+
array := ldvalue.ArrayOf(ldvalue.String("bad"), ldvalue.String("worse"))
298282
c := makeBasicBuilder().SetValue("my-attr", array).Build()
299-
expectAttributeNotFoundForRef(t, c, "/my-attr/3")
283+
expectAttributeNotFoundForRef(t, c, "/my-attr/1")
300284
})
301285

302-
t.Run("index in value that is not an object", func(t *testing.T) {
286+
t.Run("property not applicable to simple value", func(t *testing.T) {
303287
c := makeBasicBuilder().SetValue("my-attr", ldvalue.String("xyz")).Build()
304-
expectAttributeNotFoundForRef(t, c, "/my-attr/1")
288+
expectAttributeNotFoundForRef(t, c, "/my-attr/a")
305289
})
306290
}
307291

0 commit comments

Comments
 (0)