sig_value.h

include/sig/value/sig_value.h

SIG_VALUE_MAX_DEPTH

#define SIG_VALUE_MAX_DEPTH 32

Maximum container nesting accepted by sig_value_dup, the JSON serializer and the JSON parser. Builders are not capped (a sig_ht cannot carry a depth), and sig_value_dtor recurses without a limit.

sig_value_kind_t

typedef enum {
    SIG_VAL_INT,
    SIG_VAL_BOOL,
    SIG_VAL_DOUBLE,
    SIG_VAL_STRING,
    SIG_VAL_BYTES,
    SIG_VAL_MAP,  /* string -> sig_value, backed by sig_ht */
    SIG_VAL_LIST, /* ordered sig_value sequence, backed by sig_ll */
} sig_value_kind_t;

sig_string

typedef struct sig_string sig_string;

sig_bytes

typedef struct sig_bytes sig_bytes;

sig_value

typedef struct {
    sig_value_kind_t kind;
    union {
        sig_int_t i;
        bool b;
        double d;
        sig_string *s;
        sig_bytes *by;
        sig_ht *map;
        sig_ll *list;
    } as;
} sig_value;

sig_value_int

sig_value sig_value_int(sig_int_t v)
ParameterType
vsig_int_t

sig_value_bool

sig_value sig_value_bool(bool v)
ParameterType
vbool

sig_value_double

sig_value sig_value_double(double v)
ParameterType
vdouble

sig_value_string

sig_status_t sig_value_string(sig_value * out, const char * s)
ParameterType
outsig_value *
sconst char *

sig_value_string_n

sig_status_t sig_value_string_n(sig_value * out, const char * s, size_t len)
ParameterType
outsig_value *
sconst char *
lensize_t

sig_value_bytes

sig_status_t sig_value_bytes(sig_value * out, const void * data, size_t len)
ParameterType
outsig_value *
dataconst void *
lensize_t

sig_value_kind

sig_value_kind_t sig_value_kind(const sig_value * v)
ParameterType
vconst sig_value *

sig_value_dtor

void sig_value_dtor(sig_value * v)
ParameterType
vsig_value *

sig_value_copy

void sig_value_copy(const sig_value * src, sig_value * dest)
ParameterType
srcconst sig_value *
destsig_value *

Shallow copy: for string/bytes kinds, src and dest end up pointing at the SAME underlying buffer (its refcount is bumped). Containers (map/list) are shared by refcount too; a shared container is frozen (immutable) while shared -- mutators return SIG_ERR_STATE -- so copying a container freezes the original until the other handles are released or the value is sig_value_dup'd. Both sig_values must be independently dtor'd. The payload is immutable and its refcount is atomic, so a value may safely be sig_value_copy'd across threads and each thread's copy used/dtor'd concurrently. dest is overwritten unconditionally. If dest already holds an owned string/bytes value, that value's buffer is leaked -- destroy dest first if it may already own something.

sig_value_dup

sig_status_t sig_value_dup(const sig_value * src, sig_value * dest)
ParameterType
srcconst sig_value *
destsig_value *

Deep copy: for string/bytes kinds, dest gets its own independently allocated buffer with no state shared with src. Contrast with sig_value_copy(), which shares the buffer via refcount. For map/list it is a recursive deep copy producing a unique, mutable tree, and returns SIG_ERR_INVALID_ARG if nesting exceeds SIG_VALUE_MAX_DEPTH. dest is overwritten unconditionally; the same leak caveat as sig_value_copy() applies if dest already owns a value.

sig_value_box

sig_status_t sig_value_box(const sig_value * v, sig_value ** out)
ParameterType
vconst sig_value *
outsig_value **

Heap-allocates a sig_value and copies *v into it via sig_value_copy (shares the underlying string/bytes buffer via refcount -- boxing only relocates the small by-value wrapper onto the heap, it does not deep-duplicate the payload). Boxing a container shares it (refcount) and so freezes it, like sig_value_copy. The returned pointer is suitable for storing in a void*-payload container (sig_ht/sig_ll) alongside sig_value_dtor_v as the destructor.

sig_value_dtor_v

void sig_value_dtor_v(void * v)
ParameterType
vvoid *

Destructor shaped exactly like sig_ht's/sig_ll's sig_dtor_fn (void (*)(void*)) -- pass this directly as a container's destructor with no function-pointer cast. Destroys the boxed value via sig_value_dtor() and frees the box itself. Only call on a pointer previously returned by sig_value_box().

sig_value_get_int

sig_status_t sig_value_get_int(const sig_value * v, sig_int_t * out)
ParameterType
vconst sig_value *
outsig_int_t *

sig_value_get_bool

sig_status_t sig_value_get_bool(const sig_value * v, bool * out)
ParameterType
vconst sig_value *
outbool *

sig_value_get_double

sig_status_t sig_value_get_double(const sig_value * v, double * out)
ParameterType
vconst sig_value *
outdouble *

sig_value_get_string

sig_status_t sig_value_get_string(const sig_value * v, const char ** out, size_t * out_len)
ParameterType
vconst sig_value *
outconst char **
out_lensize_t *

out is a borrowed pointer into v's own buffer: valid only for v's lifetime, not owned by the caller, and must not be freed or used after v is passed to sig_value_dtor().

sig_value_get_bytes

sig_status_t sig_value_get_bytes(const sig_value * v, const uint8_t ** out, size_t * out_len)
ParameterType
vconst sig_value *
outconst uint8_t **
out_lensize_t *

out is a borrowed pointer into v's own buffer: valid only for v's lifetime, not owned by the caller, and must not be freed or used after v is passed to sig_value_dtor().

---- Containers --------------------------------------------------- A map or list value owns one reference to a sig_ht / sig_ll whose elements are boxed sig_values (dtor = sig_value_dtor_v). Sharing follows the other refcounted kinds: sig_value_copy shares the container, sig_value_dup deep-copies the whole tree into a new, uniquely owned (mutable) one. Mutation (put/remove/append) is allowed only while the container is uniquely owned (its refcount is 1). Once a container has been shared with sig_value_copy it is frozen: mutators return SIG_ERR_STATE until the other handles are released with sig_value_dtor -- or you sig_value_dup it to get a mutable copy. Release your handle (sig_value_dtor) as soon as you have handed a value off, so containers do not stay frozen longer than necessary; sig_value_is_shared() is there for assertions and debugging. Like sig_ht/sig_ll themselves, a container must be mutated by one thread at a time; no locking is added.

sig_value_map_fn

typedef sig_status_t (*sig_value_map_fn)(const char *key, const sig_value *value, void *ctx);

sig_value_list_fn

typedef sig_status_t (*sig_value_list_fn)(size_t index, const sig_value *value, void *ctx);

sig_value_map

sig_status_t sig_value_map(sig_value * out)
ParameterType
outsig_value *

sig_value_list

sig_status_t sig_value_list(sig_value * out)
ParameterType
outsig_value *

sig_value_map_put

sig_status_t sig_value_map_put(sig_value * map, const char * key, sig_value * child)
ParameterType
mapsig_value *
keyconst char *
childsig_value *

put/append MOVE *child into the container: on success *child is reset to int 0 (so a later sig_value_dtor(child) is harmless); on any failure *child is untouched and the caller still owns it. put on an existing key replaces (and destroys) the old value. Errors: NULL argument, or child is the container itself -> SIG_ERR_INVALID_ARG; wrong kind -> SIG_ERR_TYPE_MISMATCH; container shared -> SIG_ERR_STATE; allocation failure -> SIG_ERR_ALLOC.

sig_value_list_append

sig_status_t sig_value_list_append(sig_value * list, sig_value * child)
ParameterType
listsig_value *
childsig_value *

shared with sig_value_map_put

put/append MOVE *child into the container: on success *child is reset to int 0 (so a later sig_value_dtor(child) is harmless); on any failure *child is untouched and the caller still owns it. put on an existing key replaces (and destroys) the old value. Errors: NULL argument, or child is the container itself -> SIG_ERR_INVALID_ARG; wrong kind -> SIG_ERR_TYPE_MISMATCH; container shared -> SIG_ERR_STATE; allocation failure -> SIG_ERR_ALLOC.

sig_value_map_remove

sig_status_t sig_value_map_remove(sig_value * map, const char * key)
ParameterType
mapsig_value *
keyconst char *

SIG_ERR_NOT_FOUND if key is absent; same other errors as put.

sig_value_map_get

sig_status_t sig_value_map_get(const sig_value * map, const char * key, const sig_value ** out)
ParameterType
mapconst sig_value *
keyconst char *
outconst sig_value **

out is a borrowed pointer valid until the container is mutated or destroyed. SIG_ERR_NOT_FOUND if key is absent / index >= count. list_get is O(n). Work on shared containers too. Children reachable through a shared parent are protected only by const: callers must not cast away const to mutate them. Handles must come from sig_value_copy (a bitwise copy of a container sig_value is a misuse).

sig_value_list_get

sig_status_t sig_value_list_get(const sig_value * list, size_t index, const sig_value ** out)
ParameterType
listconst sig_value *
indexsize_t
outconst sig_value **

shared with sig_value_map_get

out is a borrowed pointer valid until the container is mutated or destroyed. SIG_ERR_NOT_FOUND if key is absent / index >= count. list_get is O(n). Work on shared containers too. Children reachable through a shared parent are protected only by const: callers must not cast away const to mutate them. Handles must come from sig_value_copy (a bitwise copy of a container sig_value is a misuse).

sig_value_count

size_t sig_value_count(const sig_value * v)
ParameterType
vconst sig_value *

Element count of a map or list; 0 for NULL and for other kinds.

sig_value_map_walk

sig_status_t sig_value_map_walk(const sig_value * map, sig_value_map_fn fn, void * ctx)
ParameterType
mapconst sig_value *
fnsig_value_map_fn
ctxvoid *

Map walk order is hash order (unspecified); list walk is in append order. A non-SIG_OK return from fn stops the walk and is returned. `value` is borrowed for the duration of the callback.

sig_value_list_walk

sig_status_t sig_value_list_walk(const sig_value * list, sig_value_list_fn fn, void * ctx)
ParameterType
listconst sig_value *
fnsig_value_list_fn
ctxvoid *

shared with sig_value_map_walk

Map walk order is hash order (unspecified); list walk is in append order. A non-SIG_OK return from fn stops the walk and is returned. `value` is borrowed for the duration of the callback.

sig_value_merge_patch

sig_status_t sig_value_merge_patch(const sig_value * target, const sig_value * patch, sig_value * out)
ParameterType
targetconst sig_value *
patchconst sig_value *
outsig_value *

JSON Merge Patch (RFC 7386) over values, without null-deletion (a sig_value has no null). *out is a new, uniquely owned value; target and patch are not modified. A non-map patch yields a deep copy of the patch; a map patch yields a copy of target (an empty map if target is not a map) with every patch key merged in -- a map patch value merges recursively into an existing map value, anything else replaces. SIG_ERR_INVALID_ARG for NULL arguments, SIG_ERR_ALLOC on allocation failure (*out untouched).

sig_value_is_shared

bool sig_value_is_shared(const sig_value * v)
ParameterType
vconst sig_value *

True if v's refcounted payload (string, bytes, map or list) has more than one reference. False for NULL and for inline kinds.