Skip to content

Commit 555951f

Browse files
committed
Update StringN() behavior.
1 parent 3d68a75 commit 555951f

File tree

7 files changed

+466
-416
lines changed

7 files changed

+466
-416
lines changed

x/bsonx/bsoncore/array.go

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -82,28 +82,24 @@ func (a Array) DebugString() string {
8282
// String outputs an ExtendedJSON version of Array. If the Array is not valid, this method
8383
// returns an empty string.
8484
func (a Array) String() string {
85-
str, _ := a.stringN(0)
85+
str, _ := a.StringN(-1)
8686
return str
8787
}
8888

89-
// StringN stringifies an array upto N bytes
89+
// StringN stringifies an array. If N is non-negative, it will truncate the string to N bytes.
90+
// Otherwise, it will return the full string representation. The second return value indicates
91+
// whether the string was truncated or not.
9092
func (a Array) StringN(n int) (string, bool) {
91-
if n <= 0 {
92-
if l, _, ok := ReadLength(a); !ok || l < 5 {
93-
return "", false
94-
}
95-
return "", true
96-
}
97-
return a.stringN(n)
98-
}
99-
100-
// stringN stringify an array. If N is larger than 0, it will truncate the string to N bytes.
101-
func (a Array) stringN(n int) (string, bool) {
10293
length, rem, ok := ReadLength(a)
10394
if !ok || length < 5 {
10495
return "", false
10596
}
106-
length -= (4 /* length bytes */ + 1 /* final null byte */)
97+
length -= 4 // length bytes
98+
length-- // final null byte
99+
100+
if n == 0 {
101+
return "", true
102+
}
107103

108104
var buf strings.Builder
109105
buf.WriteByte('[')
@@ -113,19 +109,25 @@ func (a Array) stringN(n int) (string, bool) {
113109
var str string
114110
first := true
115111
for length > 0 && !truncated {
116-
l := 0
112+
needStrLen := -1
113+
// Set needStrLen if n is positive, meaning we want to limit the string length.
117114
if n > 0 {
115+
// Stop stringifying if we reach the limit, that also ensures needStrLen is
116+
// greater than 0 if we need to limit the length.
118117
if buf.Len() >= n {
119118
truncated = true
120119
break
121120
}
122-
l = n - buf.Len()
121+
needStrLen = n - buf.Len()
123122
}
123+
124+
// Append a comma if this is not the first element.
124125
if !first {
125126
buf.WriteByte(',')
126-
if l > 0 {
127-
l--
128-
if l == 0 {
127+
// If we are truncating, we need to account for the comma in the length.
128+
if needStrLen > 0 {
129+
needStrLen--
130+
if needStrLen == 0 {
129131
truncated = true
130132
break
131133
}
@@ -134,18 +136,22 @@ func (a Array) stringN(n int) (string, bool) {
134136

135137
elem, rem, ok = ReadElement(rem)
136138
length -= int32(len(elem))
139+
// Exit on malformed element.
137140
if !ok || length < 0 {
138141
return "", false
139142
}
140143

141-
str, truncated = elem.Value().stringN(l)
144+
// Delegate to StringN() on the element.
145+
str, truncated = elem.Value().StringN(needStrLen)
142146
buf.WriteString(str)
143147

144148
first = false
145149
}
146150

147151
if n <= 0 || (buf.Len() < n && !truncated) {
148152
buf.WriteByte(']')
153+
} else {
154+
truncated = true
149155
}
150156

151157
return buf.String(), truncated

x/bsonx/bsoncore/array_test.go

Lines changed: 60 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -349,76 +349,78 @@ func TestArray(t *testing.T) {
349349
})
350350
}
351351

352-
func TestArray_Stringer(t *testing.T) {
353-
testCases := []struct {
354-
description string
355-
array Array
356-
want string
357-
}{
358-
{
359-
description: "empty array",
360-
array: BuildArray(nil),
361-
want: `[]`,
362-
},
363-
{
364-
description: "array with 1 element",
365-
array: BuildArray(nil, Value{
352+
var arrayStringTestCases = []struct {
353+
description string
354+
array Array
355+
want string
356+
}{
357+
{
358+
description: "empty array",
359+
array: BuildArray(nil),
360+
want: `[]`,
361+
},
362+
{
363+
description: "array with 1 element",
364+
array: BuildArray(nil, Value{
365+
Type: TypeInt32,
366+
Data: AppendInt32(nil, 123),
367+
}),
368+
want: `[{"$numberInt":"123"}]`,
369+
},
370+
{
371+
description: "nested array",
372+
array: BuildArray(nil, Value{
373+
Type: TypeArray,
374+
Data: BuildArray(nil, Value{
375+
Type: TypeString,
376+
Data: AppendString(nil, "abc"),
377+
}),
378+
}),
379+
want: `[["abc"]]`,
380+
},
381+
{
382+
description: "array with mixed types",
383+
array: BuildArray(nil,
384+
Value{
385+
Type: TypeString,
386+
Data: AppendString(nil, "abc"),
387+
},
388+
Value{
366389
Type: TypeInt32,
367390
Data: AppendInt32(nil, 123),
368-
}),
369-
want: `[{"$numberInt":"123"}]`,
370-
},
371-
{
372-
description: "nested array",
373-
array: BuildArray(nil, Value{
374-
Type: TypeArray,
375-
Data: BuildArray(nil, Value{
376-
Type: TypeString,
377-
Data: AppendString(nil, "abc"),
378-
}),
379-
}),
380-
want: `[["abc"]]`,
381-
},
382-
{
383-
description: "array with mixed types",
384-
array: BuildArray(nil,
385-
Value{
386-
Type: TypeString,
387-
Data: AppendString(nil, "abc"),
388-
},
389-
Value{
390-
Type: TypeInt32,
391-
Data: AppendInt32(nil, 123),
392-
},
393-
Value{
394-
Type: TypeBoolean,
395-
Data: AppendBoolean(nil, true),
396-
},
397-
),
398-
want: `["abc",{"$numberInt":"123"},true]`,
399-
},
400-
}
391+
},
392+
Value{
393+
Type: TypeBoolean,
394+
Data: AppendBoolean(nil, true),
395+
},
396+
),
397+
want: `["abc",{"$numberInt":"123"},true]`,
398+
},
399+
}
401400

402-
for _, tc := range testCases {
403-
t.Run(fmt.Sprintf("String %s", tc.description), func(t *testing.T) {
401+
func TestArray_String(t *testing.T) {
402+
for _, tc := range arrayStringTestCases {
403+
t.Run(tc.description, func(t *testing.T) {
404404
got := tc.array.String()
405-
assert.Equal(t, tc.want, got)
405+
assert.Equal(t, tc.want, got, "expected string %s, got %s", tc.want, got)
406406
})
407407
}
408+
}
408409

409-
for _, tc := range testCases {
410+
func TestArray_StringN(t *testing.T) {
411+
for _, tc := range arrayStringTestCases {
410412
for n := -1; n <= len(tc.want)+1; n++ {
411-
t.Run(fmt.Sprintf("StringN %s n==%d", tc.description, n), func(t *testing.T) {
412-
got, _ := tc.array.StringN(n)
413+
t.Run(fmt.Sprintf("%s n==%d", tc.description, n), func(t *testing.T) {
414+
got, truncated := tc.array.StringN(n)
413415
l := n
414-
if l < 0 {
415-
l = 0
416-
}
417-
if l > len(tc.want) {
416+
toBeTruncated := true
417+
if l >= len(tc.want) || l < 0 {
418418
l = len(tc.want)
419+
toBeTruncated = false
419420
}
420421
want := tc.want[:l]
421-
assert.Equal(t, want, got, "got %v, want %v", got, want)
422+
assert.Equal(t, want, got, "expected truncated string %s, got %s", want, got)
423+
assert.Equal(t, toBeTruncated, truncated, "expected truncated to be %t, got %t", toBeTruncated, truncated)
422424
})
423425
}
424426
}

x/bsonx/bsoncore/document.go

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -261,28 +261,24 @@ func (d Document) DebugString() string {
261261
// String outputs an ExtendedJSON version of Document. If the document is not valid, this method
262262
// returns an empty string.
263263
func (d Document) String() string {
264-
str, _ := d.stringN(0)
264+
str, _ := d.StringN(-1)
265265
return str
266266
}
267267

268-
// StringN stringifies a document upto N bytes
268+
// StringN stringifies a document. If N is non-negative, it will truncate the string to N bytes.
269+
// Otherwise, it will return the full string representation. The second return value indicates
270+
// whether the string was truncated or not.
269271
func (d Document) StringN(n int) (string, bool) {
270-
if n <= 0 {
271-
if l, _, ok := ReadLength(d); !ok || l < 5 {
272-
return "", false
273-
}
274-
return "", true
275-
}
276-
return d.stringN(n)
277-
}
278-
279-
// stringN stringify a document. If N is larger than 0, it will truncate the string to N bytes.
280-
func (d Document) stringN(n int) (string, bool) {
281272
length, rem, ok := ReadLength(d)
282273
if !ok || length < 5 {
283274
return "", false
284275
}
285-
length -= (4 /* length bytes */ + 1 /* final null byte */)
276+
length -= 4 // length bytes
277+
length-- // final null byte
278+
279+
if n == 0 {
280+
return "", true
281+
}
286282

287283
var buf strings.Builder
288284
buf.WriteByte('{')
@@ -292,19 +288,25 @@ func (d Document) stringN(n int) (string, bool) {
292288
var str string
293289
first := true
294290
for length > 0 && !truncated {
295-
l := 0
291+
needStrLen := -1
292+
// Set needStrLen if n is positive, meaning we want to limit the string length.
296293
if n > 0 {
294+
// Stop stringifying if we reach the limit, that also ensures needStrLen is
295+
// greater than 0 if we need to limit the length.
297296
if buf.Len() >= n {
298297
truncated = true
299298
break
300299
}
301-
l = n - buf.Len()
300+
needStrLen = n - buf.Len()
302301
}
302+
303+
// Append a comma if this is not the first element.
303304
if !first {
304305
buf.WriteByte(',')
305-
if l > 0 {
306-
l--
307-
if l == 0 {
306+
// If we are truncating, we need to account for the comma in the length.
307+
if needStrLen > 0 {
308+
needStrLen--
309+
if needStrLen == 0 {
308310
truncated = true
309311
break
310312
}
@@ -313,18 +315,22 @@ func (d Document) stringN(n int) (string, bool) {
313315

314316
elem, rem, ok = ReadElement(rem)
315317
length -= int32(len(elem))
318+
// Exit on malformed element.
316319
if !ok || length < 0 {
317320
return "", false
318321
}
319322

320-
str, truncated = elem.stringN(l)
323+
// Delegate to StringN() on the element.
324+
str, truncated = elem.StringN(needStrLen)
321325
buf.WriteString(str)
322326

323327
first = false
324328
}
325329

326330
if n <= 0 || (buf.Len() < n && !truncated) {
327331
buf.WriteByte('}')
332+
} else {
333+
truncated = true
328334
}
329335

330336
return buf.String(), truncated

0 commit comments

Comments
 (0)