-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathelement.go
More file actions
384 lines (344 loc) · 11.4 KB
/
element.go
File metadata and controls
384 lines (344 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
package purejson
import (
"math/bits"
"runtime"
"github.com/amikos-tech/pure-simdjson/internal/ffi"
)
// Element is the public value-view wrapper for a document root or child value.
type Element struct {
doc *Doc
view ffi.ValueView
}
// ElementType reports the concrete JSON value kind for an Element, preserving
// the distinct int64, uint64, and float64 classifications from simdjson's DOM.
type ElementType uint32
const (
// TypeInvalid reports a closed, invalid, or otherwise unusable element view.
TypeInvalid ElementType = ElementType(ffi.ValueKindInvalid)
// TypeNull reports a JSON null value.
TypeNull ElementType = ElementType(ffi.ValueKindNull)
// TypeBool reports a JSON boolean value.
TypeBool ElementType = ElementType(ffi.ValueKindBool)
// TypeInt64 reports a JSON number classified as int64.
TypeInt64 ElementType = ElementType(ffi.ValueKindInt64)
// TypeUint64 reports a JSON number classified as uint64.
TypeUint64 ElementType = ElementType(ffi.ValueKindUint64)
// TypeFloat64 reports a JSON number classified as float64.
TypeFloat64 ElementType = ElementType(ffi.ValueKindFloat64)
// TypeString reports a JSON string value.
TypeString ElementType = ElementType(ffi.ValueKindString)
// TypeArray reports a JSON array value.
TypeArray ElementType = ElementType(ffi.ValueKindArray)
// TypeObject reports a JSON object value.
TypeObject ElementType = ElementType(ffi.ValueKindObject)
)
// Array wraps an Element verified to represent a JSON array. Construct via
// Element.AsArray; the unexported field prevents callers from creating an
// unverified instance.
type Array struct{ element Element }
// Object wraps an Element verified to represent a JSON object. Construct via
// Element.AsObject; the unexported field prevents callers from creating an
// unverified instance.
type Object struct{ element Element }
func exactFloat64Uint64(value uint64) bool {
if value == 0 {
return true
}
significant := value >> bits.TrailingZeros64(value)
return bits.Len64(significant) <= 53
}
func exactFloat64Int64(value int64) bool {
magnitude := uint64(value)
if value < 0 {
magnitude = uint64(^value) + 1
}
return exactFloat64Uint64(magnitude)
}
func (e Element) usableDoc() (*Doc, error) {
if e.doc == nil {
return nil, ErrInvalidHandle
}
if e.doc.parser == nil || e.doc.parser.library == nil || e.doc.parser.library.bindings == nil {
return nil, ErrInvalidHandle
}
if e.doc.isClosed() {
return nil, ErrClosed
}
return e.doc, nil
}
// GetInt64 reads the current element as an int64 and returns ErrClosed when the
// owning document has already been released. Uint64 values larger than max
// int64 report ErrNumberOutOfRange, float-kind values report ErrWrongType, and
// native BIGINT classifications report ErrPrecisionLoss. Element accessors are
// not safe for concurrent use with Doc.Close.
func (e Element) GetInt64() (int64, error) {
doc, err := e.usableDoc()
if err != nil {
return 0, err
}
value, rc := doc.parser.library.bindings.ElementGetInt64(&e.view)
runtime.KeepAlive(doc)
if err := wrapStatus(rc); err != nil {
return 0, err
}
return value, nil
}
// Type reports the concrete JSON value kind for the current element. Closed,
// invalid, or tampered views collapse to TypeInvalid instead of returning an
// error.
func (e Element) Type() ElementType {
kind, err := e.TypeErr()
if err != nil {
return TypeInvalid
}
return kind
}
// TypeErr reports the concrete JSON value kind for the current element while
// preserving native failures such as ErrClosed, ErrInvalidHandle,
// ErrPrecisionLoss, or ErrPanic.
func (e Element) TypeErr() (ElementType, error) {
doc, err := e.usableDoc()
if err != nil {
return TypeInvalid, err
}
kind, rc := doc.parser.library.bindings.ElementType(&e.view)
runtime.KeepAlive(doc)
if err := wrapStatus(rc); err != nil {
return TypeInvalid, err
}
switch ffi.ValueKind(kind) {
case ffi.ValueKindNull:
return TypeNull, nil
case ffi.ValueKindBool:
return TypeBool, nil
case ffi.ValueKindInt64:
return TypeInt64, nil
case ffi.ValueKindUint64:
return TypeUint64, nil
case ffi.ValueKindFloat64:
return TypeFloat64, nil
case ffi.ValueKindString:
return TypeString, nil
case ffi.ValueKindArray:
return TypeArray, nil
case ffi.ValueKindObject:
return TypeObject, nil
default:
return TypeInvalid, nil
}
}
// GetUint64 reads the current element as a uint64 and returns ErrClosed when
// the owning document has already been released. Negative integers report
// ErrNumberOutOfRange, non-uint64 kinds report ErrWrongType, and native BIGINT
// classifications report ErrPrecisionLoss.
func (e Element) GetUint64() (uint64, error) {
doc, err := e.usableDoc()
if err != nil {
return 0, err
}
value, rc := doc.parser.library.bindings.ElementGetUint64(&e.view)
runtime.KeepAlive(doc)
if err := wrapStatus(rc); err != nil {
return 0, err
}
return value, nil
}
// GetFloat64 reads the current element as a float64 and returns ErrClosed when
// the owning document has already been released. Large int64 and uint64 values
// that would lose precision report ErrPrecisionLoss instead of rounding.
func (e Element) GetFloat64() (float64, error) {
doc, err := e.usableDoc()
if err != nil {
return 0, err
}
// Int64/Uint64 hints stay on the integer accessors so Go can enforce the
// exact-float64 contract without routing integer-backed values through the
// native float accessor. Invalid hints still fall through to Rust, which
// re-checks the native kind before applying the same precision-loss rule.
switch ffi.ValueKind(e.view.KindHint) {
case ffi.ValueKindInt64:
value, rc := doc.parser.library.bindings.ElementGetInt64(&e.view)
runtime.KeepAlive(doc)
if err := wrapStatus(rc); err != nil {
return 0, err
}
if !exactFloat64Int64(value) {
return 0, wrapStatus(int32(ffi.ErrPrecisionLoss))
}
return float64(value), nil
case ffi.ValueKindUint64:
value, rc := doc.parser.library.bindings.ElementGetUint64(&e.view)
runtime.KeepAlive(doc)
if err := wrapStatus(rc); err != nil {
return 0, err
}
if !exactFloat64Uint64(value) {
return 0, wrapStatus(int32(ffi.ErrPrecisionLoss))
}
return float64(value), nil
}
value, rc := doc.parser.library.bindings.ElementGetFloat64(&e.view)
runtime.KeepAlive(doc)
if err := wrapStatus(rc); err != nil {
return 0, err
}
return value, nil
}
// GetString reads the current element as a copied Go string and returns
// ErrClosed when the owning document has already been released.
func (e Element) GetString() (string, error) {
doc, err := e.usableDoc()
if err != nil {
return "", err
}
value, rc := doc.parser.library.bindings.ElementGetString(&e.view)
runtime.KeepAlive(doc)
if err := wrapStatus(rc); err != nil {
return "", err
}
return value, nil
}
// GetBool reads the current element as a bool and returns ErrClosed when the
// owning document has already been released.
func (e Element) GetBool() (bool, error) {
doc, err := e.usableDoc()
if err != nil {
return false, err
}
value, rc := doc.parser.library.bindings.ElementGetBool(&e.view)
runtime.KeepAlive(doc)
if err := wrapStatus(rc); err != nil {
return false, err
}
return value, nil
}
// IsNull reports whether the current element is a JSON null value. Closed,
// invalid, or tampered views return false.
func (e Element) IsNull() bool {
value, err := e.IsNullErr()
if err != nil {
return false
}
return value
}
// IsNullErr reports whether the current element is a JSON null value while
// preserving native failures such as ErrClosed or ErrInvalidHandle.
func (e Element) IsNullErr() (bool, error) {
doc, err := e.usableDoc()
if err != nil {
return false, err
}
value, rc := doc.parser.library.bindings.ElementIsNull(&e.view)
runtime.KeepAlive(doc)
if err := wrapStatus(rc); err != nil {
return false, err
}
return value, nil
}
// AsArray returns a typed Array view when the element represents a JSON array.
// Returns ErrClosed when the owning document is released and ErrWrongType when
// the underlying value kind is not an array.
func (e Element) AsArray() (Array, error) {
if _, err := e.usableDoc(); err != nil {
return Array{}, err
}
if ffi.ValueKind(e.view.KindHint) != ffi.ValueKindArray {
return Array{}, ErrWrongType
}
return Array{element: e}, nil
}
// AsObject returns a typed Object view when the element represents a JSON
// object. Returns ErrClosed when the owning document is released and
// ErrWrongType when the underlying value kind is not an object.
func (e Element) AsObject() (Object, error) {
if _, err := e.usableDoc(); err != nil {
return Object{}, err
}
if ffi.ValueKind(e.view.KindHint) != ffi.ValueKindObject {
return Object{}, ErrWrongType
}
return Object{element: e}, nil
}
// Iter returns a scanner-style iterator over the array contents in document
// order. Creating many descendant views or iterators on a long-lived document
// grows native bookkeeping proportionally; Doc.Close releases all of it at
// once.
func (a Array) Iter() *ArrayIter {
it := &ArrayIter{doc: a.element.doc}
doc, err := a.element.usableDoc()
if err != nil {
it.err = err
return it
}
if ffi.ValueKind(a.element.view.KindHint) != ffi.ValueKindArray {
it.err = ErrWrongType
return it
}
iter, rc := doc.parser.library.bindings.ArrayIterNew(&a.element.view)
runtime.KeepAlive(doc)
if err := normalizeIteratorError(doc, rc); err != nil {
it.err = err
return it
}
it.iter = iter
it.doc = doc
return it
}
// Iter returns a scanner-style iterator over the object fields in document
// order. Creating many descendant views or iterators on a long-lived document
// grows native bookkeeping proportionally; Doc.Close releases all of it at
// once.
func (o Object) Iter() *ObjectIter {
it := &ObjectIter{doc: o.element.doc}
doc, err := o.element.usableDoc()
if err != nil {
it.err = err
return it
}
if ffi.ValueKind(o.element.view.KindHint) != ffi.ValueKindObject {
it.err = ErrWrongType
return it
}
iter, rc := doc.parser.library.bindings.ObjectIterNew(&o.element.view)
runtime.KeepAlive(doc)
if err := normalizeIteratorError(doc, rc); err != nil {
it.err = err
return it
}
it.iter = iter
it.doc = doc
return it
}
// GetField returns the element for the given object key. Missing fields return
// ErrElementNotFound, while present null fields return a valid Element whose
// IsNull method reports true. When duplicate keys are present, GetField returns
// the first matching field. An empty key performs a literal lookup for the
// JSON key "". Creating many descendant views or iterators on a long-lived
// document grows native bookkeeping proportionally; Doc.Close releases all of
// it at once.
func (o Object) GetField(key string) (Element, error) {
doc, err := o.element.usableDoc()
if err != nil {
return Element{}, err
}
if ffi.ValueKind(o.element.view.KindHint) != ffi.ValueKindObject {
return Element{}, ErrWrongType
}
view, rc := doc.parser.library.bindings.ObjectGetField(&o.element.view, key)
runtime.KeepAlive(doc)
if err := normalizeIteratorError(doc, rc); err != nil {
return Element{}, err
}
return Element{doc: doc, view: view}, nil
}
// GetStringField returns the named field as a copied Go string using the same
// semantics as GetField followed by Element.GetString, including literal
// lookup for the JSON key "", ErrElementNotFound for missing fields, and
// ErrWrongType for present non-string values.
func (o Object) GetStringField(name string) (string, error) {
field, err := o.GetField(name)
if err != nil {
return "", err
}
return field.GetString()
}