/** * @file json_helpers.h * @brief Typed jansson accessors that report "missing" and "wrong type" as errors. * * Every asset in this library is described by a JSON document, and jansson's own * accessors answer "missing key", "wrong type", and "index past the end" all * with the same `NULL`. These wrappers split those apart -- AKERR_KEY, * AKERR_TYPE, AKERR_OUTOFBOUNDS -- so a malformed asset file produces a message * naming the key and what was wrong with it, instead of a `NULL` dereference * three frames later. * * Conventions that run through the whole set, so they need not be repeated per * function: * * - **Absence is an error here.** Unlike the search functions in libakstdlib, * these treat a missing key as AKERR_KEY. An *optional* key is expressed by * passing the resulting error to akgl_get_json_with_default() rather than by * the accessor staying quiet. * - **The result comes back through @p dest**, because the return value is the * error context. * - **`json_t *` results are borrowed, not owned.** Objects and arrays are * returned as pointers into the document; they are valid until the document is * freed and must not be `json_decref`'d. * - **@p dest is not `NULL`-checked** except where noted, so a `NULL` there is a * crash rather than an error context. * - **The document itself is never modified.** */ #ifndef _AKGL_JSON_HELPERS_H_ #define _AKGL_JSON_HELPERS_H_ #include #include #include #include /** * @brief Read a nested object out of a JSON object. * @param obj The object to read from. Required. * @param key The member name to look up. A `NULL` key is reported as a missing * key rather than as a `NULL` pointer. * @param dest Receives a borrowed pointer to the nested object; not written on * any failure path. Not checked for `NULL`. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p obj is `NULL`. * @throws AKERR_KEY If @p key is absent. The message names it. * @throws AKERR_TYPE If @p key is present but is not an object. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_object_value(json_t *obj, char *key, json_t **dest); /** * @brief Read a boolean out of a JSON object. * @param obj The object to read from. Required. * @param key The member name to look up. * @param dest Receives the value; not written on any failure path. Not checked * for `NULL`. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p obj is `NULL`. * @throws AKERR_KEY If @p key is absent. * @throws AKERR_TYPE If @p key is present but is not `true` or `false`. A `0` * or `1` is a number in JSON, and is refused here. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_boolean_value(json_t *obj, char *key, bool *dest); /** * @brief Read an integer out of a JSON object. * @param obj The object to read from. Required. * @param key The member name to look up. * @param dest Receives the value, narrowed to `int`. Not written on any failure * path, and not checked for `NULL`. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p obj is `NULL`. * @throws AKERR_KEY If @p key is absent. * @throws AKERR_TYPE If @p key is present but is not an integer. This is strict: * `3.0` is a real in JSON and is refused, not truncated. Use * akgl_get_json_number_value() where either spelling should be accepted. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_integer_value(json_t *obj, char *key, int *dest); /** * @brief Read a number out of a JSON object as a `float`. * @param obj The object to read from. Required. * @param key The member name to look up. * @param dest Receives the value, narrowed to `float`. Not written on any * failure path, and not checked for `NULL`. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p obj is `NULL`. * @throws AKERR_KEY If @p key is absent. * @throws AKERR_TYPE If @p key is present but is not a number. Integers and * reals are both accepted, so `1` and `1.0` behave alike. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_number_value(json_t *obj, char *key, float32_t *dest); /** * @brief Read a number out of a JSON object as a `double`. * * The full-precision form of akgl_get_json_number_value(), for the physics * constants, which are `double`. * * @param obj The object to read from. Required. * @param key The member name to look up. * @param dest Receives the value. Not written on any failure path, and not * checked for `NULL`. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p obj is `NULL`. * @throws AKERR_KEY If @p key is absent. * @throws AKERR_TYPE If @p key is present but is not a number. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_double_value(json_t *obj, char *key, float64_t *dest); /** * @brief Read a string out of a JSON object into a pooled akgl_String. * * Unlike the other accessors this copies, because the caller wants a buffer it * can keep rather than a pointer into the document. `*dest` doubles as an input: * `NULL` means "claim one for me", non-`NULL` means "write into this one". * * @param obj The object to read from. Required. * @param key The member name to look up. Required -- checked here, unlike the * other accessors in this file. * @param dest Address of the destination string. Required, and it must be * *initialized*: set `*dest` to `NULL` to have a pool string claimed * for you, or to a claimed string to write in place. An * indeterminate `*dest` is dereferenced. Either way the caller * releases it with akgl_heap_release_string(). * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p obj, @p key, or @p dest is `NULL`. * @throws AKERR_KEY If @p key is absent. * @throws AKERR_TYPE If @p key is present but is not a string. * @throws AKGL_ERR_HEAP If `*dest` was `NULL` and the string pool is exhausted. * * @note A value longer than #AKGL_MAX_STRING_LENGTH is truncated silently, and * is left without a terminator -- this is `strncpy`, not `strlcpy`. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_string_value(json_t *obj, char *key, akgl_String **dest); /** * @brief Read an array out of a JSON object. * @param obj The object to read from. Required. * @param key The member name to look up. * @param dest Receives a borrowed pointer to the array; not written on any * failure path. Not checked for `NULL`. Use `json_array_size()` on * it and the `akgl_get_json_array_index_*` family to walk it. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p obj is `NULL`. * @throws AKERR_KEY If @p key is absent. * @throws AKERR_TYPE If @p key is present but is not an array. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_array_value(json_t *obj, char *key, json_t **dest); /** * @brief Read one element of a JSON array as an object. * @param array The array to read from. Required. * @param index Zero-based element index. A negative index is reported the same * way as one past the end. * @param dest Receives a borrowed pointer to the element; not written on any * failure path. Not checked for `NULL`. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p array is `NULL`. * @throws AKERR_OUTOFBOUNDS If @p index is outside the array. The message * reports the index. * @throws AKERR_TYPE If the element is not an object. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_array_index_object(json_t *array, int index, json_t **dest); /** * @brief Read one element of a JSON array as an integer. * @param array The array to read from. Required. * @param index Zero-based element index. * @param dest Receives the value, narrowed to `int`; not written on any failure * path. Not checked for `NULL`. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p array is `NULL`. * @throws AKERR_OUTOFBOUNDS If @p index is outside the array. * @throws AKERR_TYPE If the element is not an integer. Strict, as in * akgl_get_json_integer_value(). */ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_array_index_integer(json_t *array, int index, int *dest); /** * @brief Read one element of a JSON array into a pooled akgl_String. * @param array The array to read from. Required. * @param index Zero-based element index. * @param dest Address of the destination string, with the same claim-or-reuse * contract as akgl_get_json_string_value(): required, must be * initialized, and released by the caller. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p array or @p dest is `NULL`. * @throws AKERR_OUTOFBOUNDS If @p index is outside the array. * @throws AKERR_TYPE If the element is not a string. * @throws AKGL_ERR_HEAP If `*dest` was `NULL` and the string pool is exhausted. * * @note Truncates silently at #AKGL_MAX_STRING_LENGTH, as * akgl_get_json_string_value() does. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_array_index_string(json_t *array, int index, akgl_String **dest); /** * @brief Turn a "key not found" error from one of the accessors above into a default value. * * This is how an optional key is spelled. Run the accessor, hand its error * context here along with the fallback, and a missing key becomes @p dest * holding @p defval and a `NULL` return; anything else propagates untouched: * * ```c * int width = 0; * int width_default = 32; * PASS(errctx, * akgl_get_json_with_default( * akgl_get_json_integer_value(json, "width", &width), * &width_default, * &width, * sizeof(int) * )); * ``` * * A `NULL` @p e -- the accessor succeeded -- is the ordinary case and returns at * once without touching @p dest. When the error *is* consumed it is also * released, so the caller must not release it again. * * @param e The error context to inspect, straight from an accessor. * `NULL` means "no error", which is not itself an error. * @param defval The fallback value to copy. Required when @p e is non-`NULL`. * @param dest Where to copy it. Required when @p e is non-`NULL`. Must be the * same destination the accessor was given, and at least * @p defsize bytes. * @param defsize Bytes to copy out of @p defval. Trusted, not derived -- it must * match the type both sides actually are, since this is a * `memcpy` through `void *` with no type information. * @return `NULL` when the error was consumed or there was none, otherwise an * error context owned by the caller. * @throws AKERR_NULLPOINTER If @p e is non-`NULL` and @p defval or @p dest is * `NULL`. * @throws AKERR_* Whatever @p e carried, if it is not one of the statuses this * defaults on -- an AKERR_TYPE from a key that exists but is the wrong * type propagates, which is right: that is a malformed document, not an * omitted setting. * * @note It defaults on AKERR_KEY and AKERR_INDEX, but *not* on * AKERR_OUTOFBOUNDS -- which is the status the * `akgl_get_json_array_index_*` family actually raises for a short array. * So this pairs with the object accessors and does not currently give an * array index a default. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_with_default(akerr_ErrorContext *e, void *defval, void *dest, uint32_t defsize); #endif // _AKGL_JSON_HELPERS_H_