Skip to content

Commit 050c014

Browse files
committed
Refactor StringN
1 parent 32c7b85 commit 050c014

File tree

8 files changed

+159
-95
lines changed

8 files changed

+159
-95
lines changed

internal/bsoncoreutil/bsoncoreutil.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ package bsoncoreutil
88

99
// Truncate truncates a given string for a certain width
1010
func Truncate(str string, width int) string {
11-
if width == 0 {
11+
if width <= 0 {
1212
return ""
1313
}
1414

x/bsonx/bsoncore/array.go

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ package bsoncore
99
import (
1010
"fmt"
1111
"io"
12-
"math"
1312
"strconv"
1413
"strings"
1514
)
@@ -83,51 +82,64 @@ func (a Array) DebugString() string {
8382
// String outputs an ExtendedJSON version of Array. If the Array is not valid, this method
8483
// returns an empty string.
8584
func (a Array) String() string {
86-
return a.StringN(math.MaxInt)
85+
return a.stringN(0)
8786
}
8887

8988
// StringN stringifies an array upto N bytes
9089
func (a Array) StringN(n int) string {
91-
if lens, _, _ := ReadLength(a); lens < 5 || n <= 0 {
90+
if n <= 0 {
91+
return ""
92+
}
93+
return a.stringN(n)
94+
}
95+
96+
// stringN stringify an array. If N is larger than 0, it will truncate the string to N bytes.
97+
func (a Array) stringN(n int) string {
98+
if lens, _, _ := ReadLength(a); lens < 5 {
9299
return ""
93100
}
94101

95102
var buf strings.Builder
96103
buf.WriteByte('[')
97104

98105
length, rem, _ := ReadLength(a) // We know we have enough bytes to read the length
99-
length -= 4
106+
length -= (4 /* length bytes */ + 1 /* final null byte */)
100107

101108
var elem Element
102109
var ok bool
103110

104-
if n > 0 {
105-
for length > 1 {
106-
elem, rem, ok = ReadElement(rem)
107-
108-
length -= int32(len(elem))
109-
if !ok {
110-
return ""
111-
}
112-
113-
str := elem.Value().StringN(n - buf.Len())
114-
115-
buf.WriteString(str)
116-
117-
if buf.Len() == n {
118-
return buf.String()
111+
first := true
112+
for length > 0 {
113+
l := 0
114+
if n > 0 {
115+
if buf.Len() >= n {
116+
break
119117
}
120-
121-
if length > 1 {
122-
buf.WriteByte(',')
118+
l = n - buf.Len()
119+
}
120+
if !first {
121+
buf.WriteByte(',')
122+
if l > 0 {
123+
l -= 1
124+
if l == 0 {
125+
break
126+
}
123127
}
124128
}
125-
if length != 1 { // Missing final null byte or inaccurate length
129+
130+
elem, rem, ok = ReadElement(rem)
131+
length -= int32(len(elem))
132+
if !ok || length < 0 {
126133
return ""
127134
}
135+
136+
str := elem.Value().stringN(l)
137+
buf.WriteString(str)
138+
139+
first = false
128140
}
129141

130-
if buf.Len()+1 <= n {
142+
if n <= 0 || buf.Len() < n {
131143
buf.WriteByte(']')
132144
}
133145

x/bsonx/bsoncore/array_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ func TestArray_StringN(t *testing.T) {
506506
},
507507
{
508508
description: "n>0, array EQ n",
509-
n: 22,
509+
n: 20,
510510
values: []Value{
511511
{
512512
Type: TypeInt32,
@@ -576,7 +576,7 @@ func TestArray_StringN(t *testing.T) {
576576
got := Array(BuildArray(nil, tc.values...)).StringN(tc.n)
577577
assert.Equal(t, tc.want, got)
578578
if tc.n >= 0 {
579-
assert.LessOrEqual(t, len(got), tc.n)
579+
assert.LessOrEqual(t, len(got), tc.n, "got %v, want %v", got, tc.want)
580580
}
581581
})
582582
}

x/bsonx/bsoncore/document.go

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,8 @@ import (
1010
"errors"
1111
"fmt"
1212
"io"
13-
"math"
1413
"strconv"
1514
"strings"
16-
17-
"go.mongodb.org/mongo-driver/v2/internal/bsoncoreutil"
1815
)
1916

2017
// ValidationError is an error type returned when attempting to validate a document or array.
@@ -264,54 +261,64 @@ func (d Document) DebugString() string {
264261
// String outputs an ExtendedJSON version of Document. If the document is not valid, this method
265262
// returns an empty string.
266263
func (d Document) String() string {
267-
return d.StringN(math.MaxInt)
264+
return d.stringN(0)
268265
}
269266

270267
// StringN stringifies a document upto N bytes
271268
func (d Document) StringN(n int) string {
272-
if len(d) < 5 || n <= 0 {
269+
if n <= 0 {
273270
return ""
274271
}
272+
return d.stringN(n)
273+
}
275274

276-
var buf strings.Builder
275+
// stringN stringify a document. If N is larger than 0, it will truncate the string to N bytes.
276+
func (d Document) stringN(n int) string {
277+
if len(d) < 5 {
278+
return ""
279+
}
277280

281+
var buf strings.Builder
278282
buf.WriteByte('{')
279283

280284
length, rem, _ := ReadLength(d)
281-
length -= 4
285+
length -= (4 /* length bytes */ + 1 /* final null byte */)
282286

283287
var elem Element
284288
var ok bool
285289

286290
first := true
287-
truncated := false
288-
289-
if n > 0 {
290-
for length > 1 {
291-
if !first {
292-
buf.WriteByte(',')
291+
for length > 0 {
292+
l := 0
293+
if n > 0 {
294+
if buf.Len() >= n {
295+
break
293296
}
294-
elem, rem, ok = ReadElement(rem)
295-
length -= int32(len(elem))
296-
if !ok {
297-
return ""
297+
l = n - buf.Len()
298+
}
299+
if !first {
300+
buf.WriteByte(',')
301+
if l > 0 {
302+
l -= 1
303+
if l == 0 {
304+
break
305+
}
298306
}
307+
}
299308

300-
str := elem.StringN(n)
301-
if buf.Len()+len(str) > n {
302-
truncatedStr := bsoncoreutil.Truncate(str, n-buf.Len())
303-
buf.WriteString(truncatedStr)
309+
elem, rem, ok = ReadElement(rem)
310+
length -= int32(len(elem))
311+
if !ok || length < 0 {
312+
return ""
313+
}
304314

305-
truncated = true
306-
break
307-
}
315+
str := elem.stringN(l)
316+
buf.WriteString(str)
308317

309-
buf.WriteString(str)
310-
first = false
311-
}
318+
first = false
312319
}
313320

314-
if !truncated {
321+
if n <= 0 || buf.Len() < n {
315322
buf.WriteByte('}')
316323
}
317324

x/bsonx/bsoncore/document_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ func TestDocument_StringN(t *testing.T) {
532532
got := bs.StringN(tc.n)
533533
assert.Equal(t, tc.want, got)
534534
if tc.n >= 0 {
535-
assert.LessOrEqual(t, len(got), tc.n)
535+
assert.LessOrEqual(t, len(got), tc.n, "got %v, want %v", got, tc.want)
536536
}
537537
})
538538
}

x/bsonx/bsoncore/element.go

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ package bsoncore
99
import (
1010
"bytes"
1111
"fmt"
12-
"math"
12+
"strings"
13+
14+
"go.mongodb.org/mongo-driver/v2/internal/bsoncoreutil"
1315
)
1416

1517
// MalformedElementError represents a class of errors that RawElement methods return.
@@ -115,35 +117,74 @@ func (e Element) ValueErr() (Value, error) {
115117

116118
// String implements the fmt.String interface. The output will be in extended JSON format.
117119
func (e Element) String() string {
118-
return e.StringN(math.MaxInt)
120+
return e.stringN(0)
119121
}
120122

121123
// StringN implements the fmt.String interface for upto N bytes. The output will be in extended JSON format.
122124
func (e Element) StringN(n int) string {
125+
if n <= 0 {
126+
return ""
127+
}
128+
return e.stringN(n)
129+
}
130+
131+
// stringN stringify an element. If N is larger than 0, it will truncate the string to N bytes.
132+
func (e Element) stringN(n int) string {
123133
if len(e) == 0 {
124134
return ""
125135
}
136+
if n == 1 {
137+
return `"`
138+
}
139+
126140
t := Type(e[0])
127141
idx := bytes.IndexByte(e[1:], 0x00)
128-
if idx == -1 {
142+
if idx <= 0 {
129143
return ""
130144
}
131-
key, valBytes := []byte(e[1:idx+1]), []byte(e[idx+2:])
132-
val, _, valid := ReadValue(valBytes, t)
145+
key := e[1 : idx+1]
146+
147+
var buf strings.Builder
148+
buf.WriteByte('"')
149+
const postfix = `": `
150+
switch {
151+
case n <= 0 || idx <= n-4:
152+
buf.Write(key)
153+
buf.WriteString(postfix)
154+
case idx < n:
155+
buf.Write(key)
156+
buf.WriteString(postfix[:n-idx])
157+
default:
158+
buf.WriteString(bsoncoreutil.Truncate(string(key), n-1))
159+
}
160+
161+
l := 0
162+
if n > 0 {
163+
if buf.Len() >= n {
164+
return buf.String()
165+
}
166+
l = n - buf.Len()
167+
}
168+
169+
val, _, valid := ReadValue(e[idx+2:], t)
133170
if !valid {
134171
return ""
135172
}
136173

137174
var str string
138175
if _, ok := val.StringValueOK(); ok {
139-
str = val.StringN(n)
176+
str = val.stringN(l)
140177
} else if arr, ok := val.ArrayOK(); ok {
141-
str = arr.StringN(n)
178+
str = arr.stringN(l)
142179
} else {
143180
str = val.String()
181+
if l > 0 && len(str) > l {
182+
str = bsoncoreutil.Truncate(str, l)
183+
}
144184
}
145185

146-
return "\"" + string(key) + "\": " + str
186+
buf.WriteString(str)
187+
return buf.String()
147188
}
148189

149190
// DebugString outputs a human readable version of RawElement. It will attempt to stringify the

0 commit comments

Comments
 (0)