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}