map.go
1package ygo
2
3/*
4#include "libyrs.h"
5#include <stdlib.h>
6*/
7import "C"
8import (
9 "encoding/json"
10 "fmt"
11 "unsafe"
12)
13
14// Map represents a collaborative map type.
15type Map struct {
16 branch *C.Branch
17}
18
19// GetMap retrieves or creates a root-level YMap with the given name.
20func (d *Doc) GetMap(name string) (*Map, error) {
21 if d.ptr == nil {
22 return nil, ErrNilDocument
23 }
24 cName := C.CString(name)
25 defer C.free(unsafe.Pointer(cName))
26
27 branch := C.ymap(d.ptr, cName)
28 if branch == nil {
29 return nil, fmt.Errorf("failed to get or create map field %q", name)
30 }
31
32 m := &Map{branch: branch}
33 return m, nil
34}
35
36// Destroy releases resources.
37func (m *Map) Destroy() {
38}
39
40// Len returns the number of entries.
41// Panics if the Map is nil or if txn is nil.
42func (m *Map) Len(txn *Transaction) uint32 {
43 if m.branch == nil {
44 panic("ygo: Map.Len called on nil Map")
45 }
46 if txn == nil || txn.ptr == nil {
47 panic("ygo: Map.Len called with nil transaction")
48 }
49 return uint32(C.ymap_len(m.branch, txn.ptr))
50}
51
52// Insert adds or updates a key-value pair.
53func (m *Map) Insert(txn *Transaction, key string, value Input) error {
54 if m.branch == nil {
55 return ErrNilBranch
56 }
57 if txn == nil || txn.ptr == nil {
58 return ErrNilTransaction
59 }
60 if !txn.IsWriteable() {
61 return ErrNotWriteable
62 }
63 cKey := C.CString(key)
64 defer C.free(unsafe.Pointer(cKey))
65
66 C.ymap_insert(m.branch, txn.ptr, cKey, &value.cInput)
67 return nil
68}
69
70// Remove deletes a key and returns true if it existed.
71func (m *Map) Remove(txn *Transaction, key string) (bool, error) {
72 if m.branch == nil {
73 return false, ErrNilBranch
74 }
75 if txn == nil || txn.ptr == nil {
76 return false, ErrNilTransaction
77 }
78 if !txn.IsWriteable() {
79 return false, ErrNotWriteable
80 }
81 cKey := C.CString(key)
82 defer C.free(unsafe.Pointer(cKey))
83
84 return C.ymap_remove(m.branch, txn.ptr, cKey) != 0, nil
85}
86
87// Get retrieves a value by key. Returns ErrKeyNotFound if key doesn't exist.
88func (m *Map) Get(txn *Transaction, key string) (*Output, error) {
89 if m.branch == nil {
90 return nil, ErrNilBranch
91 }
92 if txn == nil || txn.ptr == nil {
93 return nil, ErrNilTransaction
94 }
95 cKey := C.CString(key)
96 defer C.free(unsafe.Pointer(cKey))
97
98 ptr := C.ymap_get(m.branch, txn.ptr, cKey)
99 if ptr == nil {
100 return nil, ErrKeyNotFound
101 }
102 return &Output{ptr: ptr}, nil
103}
104
105// Clear removes all entries.
106func (m *Map) Clear(txn *Transaction) error {
107 if m.branch == nil {
108 return ErrNilBranch
109 }
110 if txn == nil || txn.ptr == nil {
111 return ErrNilTransaction
112 }
113 if !txn.IsWriteable() {
114 return ErrNotWriteable
115 }
116 C.ymap_remove_all(m.branch, txn.ptr)
117 return nil
118}
119
120// Branch returns the underlying branch pointer.
121func (m *Map) Branch() unsafe.Pointer {
122 return unsafe.Pointer(m.branch)
123}
124
125// GetNestedMap retrieves a nested Y.Map value by key.
126// Returns error if the key doesn't exist or the value is not a Y.Map.
127func (m *Map) GetNestedMap(txn *Transaction, key string) (*Map, error) {
128 out, err := m.Get(txn, key)
129 if err != nil {
130 return nil, err
131 }
132 defer out.Destroy()
133
134 if out.Tag() != TagMap {
135 return nil, fmt.Errorf("value at key %q is not a Y.Map (got %v)", key, out.Tag())
136 }
137
138 branch := C.youtput_read_ymap(out.ptr)
139 if branch == nil {
140 return nil, fmt.Errorf("failed to read nested ymap")
141 }
142
143 return &Map{branch: branch}, nil
144}
145
146// GetNestedArray retrieves a nested Y.Array value by key.
147// Returns error if the key doesn't exist or the value is not a Y.Array.
148func (m *Map) GetNestedArray(txn *Transaction, key string) (*Array, error) {
149 out, err := m.Get(txn, key)
150 if err != nil {
151 return nil, err
152 }
153 defer out.Destroy()
154
155 if out.Tag() != TagArray {
156 return nil, fmt.Errorf("value at key %q is not a Y.Array (got %v)", key, out.Tag())
157 }
158
159 branch := C.youtput_read_yarray(out.ptr)
160 if branch == nil {
161 return nil, fmt.Errorf("failed to read nested yarray")
162 }
163
164 return &Array{branch: branch}, nil
165}
166
167// GetString retrieves a string value by key.
168// Returns ErrKeyNotFound if key doesn't exist.
169// Returns error if value is not a string.
170func (m *Map) GetString(txn *Transaction, key string) (string, error) {
171 out, err := m.Get(txn, key)
172 if err != nil {
173 return "", err
174 }
175 defer out.Destroy()
176
177 str, ok := out.String()
178 if !ok {
179 return "", fmt.Errorf("value at key %q is not a string (got %v)", key, out.Tag())
180 }
181 return str, nil
182}
183
184// GetInt retrieves an integer value by key.
185// Returns ErrKeyNotFound if key doesn't exist.
186// Returns error if value is not an integer or float.
187func (m *Map) GetInt(txn *Transaction, key string) (int64, error) {
188 out, err := m.Get(txn, key)
189 if err != nil {
190 return 0, err
191 }
192 defer out.Destroy()
193
194 if val, ok := out.Int(); ok {
195 return val, nil
196 }
197 if val, ok := out.Float(); ok {
198 return int64(val), nil
199 }
200 return 0, fmt.Errorf("value at key %q is not a number (got %v)", key, out.Tag())
201}
202
203// GetFloat retrieves a float value by key.
204// Returns ErrKeyNotFound if key doesn't exist.
205// Returns error if value is not a number.
206func (m *Map) GetFloat(txn *Transaction, key string) (float64, error) {
207 out, err := m.Get(txn, key)
208 if err != nil {
209 return 0, err
210 }
211 defer out.Destroy()
212
213 if val, ok := out.Float(); ok {
214 return val, nil
215 }
216 if val, ok := out.Int(); ok {
217 return float64(val), nil
218 }
219 return 0, fmt.Errorf("value at key %q is not a number (got %v)", key, out.Tag())
220}
221
222// GetBool retrieves a boolean value by key.
223// Returns ErrKeyNotFound if key doesn't exist.
224// Returns error if value is not a boolean.
225func (m *Map) GetBool(txn *Transaction, key string) (bool, error) {
226 out, err := m.Get(txn, key)
227 if err != nil {
228 return false, err
229 }
230 defer out.Destroy()
231
232 val, ok := out.Bool()
233 if !ok {
234 return false, fmt.Errorf("value at key %q is not a bool (got %v)", key, out.Tag())
235 }
236 return val, nil
237}
238
239// MapEntry represents a key-value pair from a map.
240type MapEntry struct {
241 Key string
242 Value *Output
243}
244
245// MapIter iterates over map entries.
246type MapIter struct {
247 ptr *C.YMapIter
248}
249
250// Destroy releases iterator resources.
251func (it *MapIter) Destroy() {
252 if it.ptr != nil {
253 C.ymap_iter_destroy(it.ptr)
254 it.ptr = nil
255 }
256}
257
258// Next returns the next entry. Returns nil when iteration is complete.
259func (it *MapIter) Next() (*MapEntry, error) {
260 if it.ptr == nil {
261 return nil, ErrIteratorExhausted
262 }
263 entry := C.ymap_iter_next(it.ptr)
264 if entry == nil {
265 return nil, nil // End of iteration
266 }
267
268 // Convert key from C string
269 key := C.GoString(entry.key)
270
271 // Wrap the YOutput pointer
272 // Note: YMapEntry's Drop impl destroys the value when the entry is freed,
273 // so callers should NOT call Destroy() on entry.Value
274 value := &Output{ptr: entry.value}
275
276 return &MapEntry{Key: key, Value: value}, nil
277}
278
279// Iter returns an iterator over the map entries.
280func (m *Map) Iter(txn *Transaction) (*MapIter, error) {
281 if m.branch == nil {
282 return nil, ErrNilBranch
283 }
284 if txn == nil || txn.ptr == nil {
285 return nil, ErrNilTransaction
286 }
287 ptr := C.ymap_iter(m.branch, txn.ptr)
288 if ptr == nil {
289 return nil, fmt.Errorf("failed to create map iterator")
290 }
291 return &MapIter{ptr: ptr}, nil
292}
293
294// ForEach iterates over all entries in the map, calling fn for each key-value pair.
295// If fn returns an error, iteration stops and the error is returned.
296// The Output is automatically destroyed after fn returns.
297func (m *Map) ForEach(txn *Transaction, fn func(key string, value *Output) error) error {
298 iter, err := m.Iter(txn)
299 if err != nil {
300 return err
301 }
302 defer iter.Destroy()
303
304 for {
305 entry, err := iter.Next()
306 if err != nil {
307 return err
308 }
309 if entry == nil {
310 break
311 }
312
313 if err := fn(entry.Key, entry.Value); err != nil {
314 entry.Value.Destroy()
315 return err
316 }
317 entry.Value.Destroy()
318 }
319 return nil
320}
321
322// MarshalJSONWithTxn implements JSON marshaling for Map.
323// Returns a JSON object with string keys and JSON values.
324func (m *Map) MarshalJSONWithTxn(txn *Transaction) ([]byte, error) {
325 if m.branch == nil {
326 return nil, ErrNilBranch
327 }
328 if txn == nil || txn.ptr == nil {
329 return nil, ErrNilTransaction
330 }
331
332 // Collect all entries
333 data := make(map[string]interface{})
334
335 iter, err := m.Iter(txn)
336 if err != nil {
337 return nil, err
338 }
339 defer iter.Destroy()
340
341 for {
342 entry, err := iter.Next()
343 if err != nil {
344 return nil, err
345 }
346 if entry == nil {
347 break
348 }
349
350 // Convert value to JSON-compatible type
351 val, err := m.valueToJSON(entry.Value, txn)
352 if err != nil {
353 entry.Value.Destroy()
354 return nil, fmt.Errorf("key %q: %w", entry.Key, err)
355 }
356
357 data[entry.Key] = val
358 entry.Value.Destroy()
359 }
360
361 return json.Marshal(data)
362}
363
364// valueToJSON converts an Output value to a JSON-compatible Go value.
365// For primitive types, uses Output methods. For shared types, returns error
366// (shared type marshaling implemented in later tasks).
367func (m *Map) valueToJSON(out *Output, txn *Transaction) (interface{}, error) {
368 if out == nil || out.ptr == nil {
369 return nil, nil
370 }
371
372 switch out.Tag() {
373 // Primitive JSON types - use Output methods
374 case TagJSONNull:
375 return nil, nil
376 case TagJSONBool:
377 if v, ok := out.Bool(); ok {
378 return v, nil
379 }
380 return nil, fmt.Errorf("failed to read boolean")
381 case TagJSONNum:
382 if v, ok := out.Float(); ok {
383 return v, nil
384 }
385 return nil, fmt.Errorf("failed to read float")
386 case TagJSONInt:
387 if v, ok := out.Int(); ok {
388 return v, nil
389 }
390 return nil, fmt.Errorf("failed to read int")
391 case TagJSONStr:
392 if v, ok := out.String(); ok {
393 return v, nil
394 }
395 return nil, fmt.Errorf("failed to read string")
396 case TagJSONBuf:
397 if v, ok := out.Binary(); ok {
398 return v, nil
399 }
400 return nil, fmt.Errorf("failed to read binary")
401
402 // JSON map - parsed JSON object
403 case TagJSONMap:
404 return out.convertToJSONMap()
405
406 // Shared types - recursively marshal
407 case TagMap:
408 branch := C.youtput_read_ymap(out.ptr)
409 if branch == nil {
410 return nil, fmt.Errorf("failed to read ymap from output")
411 }
412 m := &Map{branch: branch}
413 data := make(map[string]interface{})
414 iter, err := m.Iter(txn)
415 if err != nil {
416 return nil, err
417 }
418 defer iter.Destroy()
419 for {
420 entry, err := iter.Next()
421 if err != nil {
422 return nil, err
423 }
424 if entry == nil {
425 break
426 }
427 val, err := m.valueToJSON(entry.Value, txn)
428 if err != nil {
429 entry.Value.Destroy()
430 return nil, fmt.Errorf("key %q: %w", entry.Key, err)
431 }
432 data[entry.Key] = val
433 entry.Value.Destroy()
434 }
435 return data, nil
436 case TagArray:
437 branch := C.youtput_read_yarray(out.ptr)
438 if branch == nil {
439 return nil, fmt.Errorf("failed to read yarray from output")
440 }
441 arr := &Array{branch: branch}
442 var elements []interface{}
443 iter, err := arr.Iter(txn)
444 if err != nil {
445 return nil, err
446 }
447 defer iter.Destroy()
448 for {
449 el, err := iter.Next()
450 if err != nil {
451 return nil, err
452 }
453 if el == nil {
454 break
455 }
456 val, err := arr.valueToJSON(el, txn)
457 if err != nil {
458 el.Destroy()
459 return nil, err
460 }
461 elements = append(elements, val)
462 el.Destroy()
463 }
464 return elements, nil
465 case TagText, TagXmlElem, TagXmlText, TagXmlFrag:
466 // XML types not supported in map context
467 return nil, fmt.Errorf("XML types not supported in map JSON marshaling: %v", out.Tag())
468 case TagDoc:
469 return nil, fmt.Errorf("nested documents not supported in JSON marshaling")
470 case TagWeakLink, TagUndefined, TagJSONUndef:
471 return nil, nil
472 default:
473 return nil, fmt.Errorf("unsupported type tag: %v", out.Tag())
474 }
475}