Skip to content

Commit 4d17e8b

Browse files
committed
Refactor StringN
1 parent 32c7b85 commit 4d17e8b

File tree

9 files changed

+203
-123
lines changed

9 files changed

+203
-123
lines changed

bson/raw_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ func createMassiveArraysDocument(arraySize int) D {
518518
func createUniqueVoluminousDocument(t *testing.T, size int) bsoncore.Document {
519519
t.Helper()
520520

521-
docs := make(D, size)
521+
docs := make(D, 0, size)
522522

523523
for i := 0; i < size; i++ {
524524
docs = append(docs, E{
@@ -561,7 +561,7 @@ func createLargeSingleDoc(t *testing.T) bsoncore.Document {
561561
func createVoluminousArrayDocuments(t *testing.T, size int) bsoncore.Document {
562562
t.Helper()
563563

564-
docs := make(D, size)
564+
docs := make(D, 0, size)
565565

566566
for i := 0; i < size; i++ {
567567
docs = append(docs, E{

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: 44 additions & 27 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,55 +82,73 @@ 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+
str, _ := a.stringN(0)
86+
return str
8787
}
8888

8989
// StringN stringifies an array upto N bytes
9090
func (a Array) StringN(n int) string {
91-
if lens, _, _ := ReadLength(a); lens < 5 || n <= 0 {
91+
if n <= 0 {
9292
return ""
9393
}
94+
str, _ := a.stringN(n)
95+
return str
96+
}
97+
98+
// stringN stringify an array. If N is larger than 0, it will truncate the string to N bytes.
99+
func (a Array) stringN(n int) (string, bool) {
100+
if lens, _, _ := ReadLength(a); lens < 5 {
101+
return "", false
102+
}
94103

95104
var buf strings.Builder
96105
buf.WriteByte('[')
97106

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

110+
var truncated bool
101111
var elem Element
102112
var ok bool
103-
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()
113+
var str string
114+
first := true
115+
for length > 0 && !truncated {
116+
l := 0
117+
if n > 0 {
118+
if buf.Len() >= n {
119+
truncated = true
120+
break
119121
}
120-
121-
if length > 1 {
122-
buf.WriteByte(',')
122+
l = n - buf.Len()
123+
}
124+
if !first {
125+
buf.WriteByte(',')
126+
if l > 0 {
127+
l--
128+
if l == 0 {
129+
truncated = true
130+
break
131+
}
123132
}
124133
}
125-
if length != 1 { // Missing final null byte or inaccurate length
126-
return ""
134+
135+
elem, rem, ok = ReadElement(rem)
136+
length -= int32(len(elem))
137+
if !ok || length < 0 {
138+
return "", true
127139
}
140+
141+
str, truncated = elem.Value().stringN(l)
142+
buf.WriteString(str)
143+
144+
first = false
128145
}
129146

130-
if buf.Len()+1 <= n {
147+
if n <= 0 || (buf.Len() < n && !truncated) {
131148
buf.WriteByte(']')
132149
}
133150

134-
return buf.String()
151+
return buf.String(), truncated
135152
}
136153

137154
// Values returns this array as a slice of values. The returned slice will contain valid values.

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: 42 additions & 30 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,58 +261,73 @@ 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+
str, _ := d.stringN(0)
265+
return str
268266
}
269267

270268
// StringN stringifies a document upto N bytes
271269
func (d Document) StringN(n int) string {
272-
if len(d) < 5 || n <= 0 {
270+
if n <= 0 {
273271
return ""
274272
}
273+
str, _ := d.stringN(n)
274+
return str
275+
}
275276

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

283+
var buf strings.Builder
278284
buf.WriteByte('{')
279285

280286
length, rem, _ := ReadLength(d)
281-
length -= 4
287+
length -= (4 /* length bytes */ + 1 /* final null byte */)
282288

289+
var truncated bool
283290
var elem Element
284291
var ok bool
285-
292+
var str string
286293
first := true
287-
truncated := false
288-
289-
if n > 0 {
290-
for length > 1 {
291-
if !first {
292-
buf.WriteByte(',')
293-
}
294-
elem, rem, ok = ReadElement(rem)
295-
length -= int32(len(elem))
296-
if !ok {
297-
return ""
298-
}
299-
300-
str := elem.StringN(n)
301-
if buf.Len()+len(str) > n {
302-
truncatedStr := bsoncoreutil.Truncate(str, n-buf.Len())
303-
buf.WriteString(truncatedStr)
304-
294+
for length > 0 && !truncated {
295+
l := 0
296+
if n > 0 {
297+
if buf.Len() >= n {
305298
truncated = true
306299
break
307300
}
301+
l = n - buf.Len()
302+
}
303+
if !first {
304+
buf.WriteByte(',')
305+
if l > 0 {
306+
l--
307+
if l == 0 {
308+
truncated = true
309+
break
310+
}
311+
}
312+
}
308313

309-
buf.WriteString(str)
310-
first = false
314+
elem, rem, ok = ReadElement(rem)
315+
length -= int32(len(elem))
316+
if !ok || length < 0 {
317+
return "", true
311318
}
319+
320+
str, truncated = elem.stringN(l)
321+
buf.WriteString(str)
322+
323+
first = false
312324
}
313325

314-
if !truncated {
326+
if n <= 0 || (buf.Len() < n && !truncated) {
315327
buf.WriteByte('}')
316328
}
317329

318-
return buf.String()
330+
return buf.String(), truncated
319331
}
320332

321333
// Elements returns this document as a slice of elements. The returned slice will contain valid

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: 57 additions & 12 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,78 @@ 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+
str, _ := e.stringN(0)
121+
return str
119122
}
120123

121124
// StringN implements the fmt.String interface for upto N bytes. The output will be in extended JSON format.
122125
func (e Element) StringN(n int) string {
123-
if len(e) == 0 {
126+
if n <= 0 {
124127
return ""
125128
}
129+
str, _ := e.stringN(n)
130+
return str
131+
}
132+
133+
// stringN stringify an element. If N is larger than 0, it will truncate the string to N bytes.
134+
func (e Element) stringN(n int) (string, bool) {
135+
if len(e) == 0 {
136+
return "", false
137+
}
138+
if n == 1 {
139+
return `"`, true
140+
}
141+
126142
t := Type(e[0])
127143
idx := bytes.IndexByte(e[1:], 0x00)
128-
if idx == -1 {
129-
return ""
130-
}
131-
key, valBytes := []byte(e[1:idx+1]), []byte(e[idx+2:])
132-
val, _, valid := ReadValue(valBytes, t)
144+
if idx <= 0 {
145+
return "", false
146+
}
147+
key := e[1 : idx+1]
148+
149+
var buf strings.Builder
150+
buf.WriteByte('"')
151+
const postfix = `": `
152+
switch {
153+
case n <= 0 || idx <= n-4:
154+
buf.Write(key)
155+
buf.WriteString(postfix)
156+
case idx < n:
157+
buf.Write(key)
158+
buf.WriteString(postfix[:n-idx])
159+
default:
160+
buf.WriteString(bsoncoreutil.Truncate(string(key), n-1))
161+
}
162+
163+
l := 0
164+
if n > 0 {
165+
if buf.Len() >= n {
166+
return buf.String(), true
167+
}
168+
l = n - buf.Len()
169+
}
170+
171+
val, _, valid := ReadValue(e[idx+2:], t)
133172
if !valid {
134-
return ""
173+
return "", false
135174
}
136175

137176
var str string
177+
var truncated bool
138178
if _, ok := val.StringValueOK(); ok {
139-
str = val.StringN(n)
179+
str, truncated = val.stringN(l)
140180
} else if arr, ok := val.ArrayOK(); ok {
141-
str = arr.StringN(n)
181+
str, truncated = arr.stringN(l)
142182
} else {
143183
str = val.String()
184+
if l > 0 && len(str) > l {
185+
truncated = true
186+
str = bsoncoreutil.Truncate(str, l)
187+
}
144188
}
145189

146-
return "\"" + string(key) + "\": " + str
190+
buf.WriteString(str)
191+
return buf.String(), truncated
147192
}
148193

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

0 commit comments

Comments
 (0)