From 7695ea2643456ab0c837f958cc484d26529e1098 Mon Sep 17 00:00:00 2001 From: "David M. Lee" Date: Fri, 11 Jan 2013 22:31:42 +0000 Subject: Add JSON API for Asterisk. This provides a JSON API by pulling in and wrapping the Jansson JSON library[1]. The Asterisk API basically mirrors the Jansson functionality, with a few minor tweaks. * Some names have been asteriskified to protect the innocent. * Jansson provides both reference-stealing and reference-borrowing versions of several API's. The Asterisk API is exclusively reference-stealing for operations that put elements into arrays and objects. * No support for doubles, since we usually don't need that. * Coming along for the ride is the ast_test_validate macro, which made the unit tests much easier to write. [1]: http://www.digip.org/jansson/ (issue ASTERISK-20887) (closes issue ASTERISK-20888) Review: https://reviewboard.asterisk.org/r/2264/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@378915 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- build_tools/menuselect-deps.in | 1 + configure | 145 +++- configure.ac | 3 + contrib/scripts/install_prereq | 2 +- include/asterisk/autoconfig.h.in | 3 + include/asterisk/json.h | 762 +++++++++++++++++ include/asterisk/test.h | 28 +- main/Makefile | 1 + makeopts.in | 3 + res/res_json.c | 517 ++++++++++++ res/res_json.exports.in | 6 + tests/test_json.c | 1701 ++++++++++++++++++++++++++++++++++++++ 12 files changed, 3169 insertions(+), 3 deletions(-) create mode 100644 include/asterisk/json.h create mode 100644 res/res_json.c create mode 100644 res/res_json.exports.in create mode 100644 tests/test_json.c diff --git a/build_tools/menuselect-deps.in b/build_tools/menuselect-deps.in index 6f4418b26..b3bcf957d 100644 --- a/build_tools/menuselect-deps.in +++ b/build_tools/menuselect-deps.in @@ -25,6 +25,7 @@ IODBC=@PBX_IODBC@ ISDNNET=@PBX_ISDNNET@ IXJUSER=@PBX_IXJUSER@ JACK=@PBX_JACK@ +JANSSON=@PBX_JANSSON@ KQUEUE=@PBX_KQUEUE@ LDAP=@PBX_LDAP@ LIBEDIT=@PBX_LIBEDIT@ diff --git a/configure b/configure index 44bb31d79..edbf19ca1 100755 --- a/configure +++ b/configure @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.ac Revision: 377977 . +# From configure.ac Revision: 377981 . # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.69 for asterisk trunk. # @@ -974,6 +974,10 @@ PBX_KQUEUE KQUEUE_DIR KQUEUE_INCLUDE KQUEUE_LIB +PBX_JANSSON +JANSSON_DIR +JANSSON_INCLUDE +JANSSON_LIB PBX_JACK JACK_DIR JACK_INCLUDE @@ -1245,6 +1249,7 @@ with_inotify with_iodbc with_isdnnet with_jack +with_jansson with_kqueue with_ldap with_libcurl @@ -1972,6 +1977,7 @@ Optional Packages: --with-iodbc=PATH use iODBC files in PATH --with-isdnnet=PATH use ISDN4Linux files in PATH --with-jack=PATH use Jack Audio Connection Kit files in PATH + --with-jansson=PATH use Jansson JSON library files in PATH --with-kqueue=PATH use kqueue support files in PATH --with-ldap=PATH use OpenLDAP files in PATH --with-libcurl=DIR look for the curl library in DIR @@ -8911,6 +8917,38 @@ fi + JANSSON_DESCRIP="Jansson JSON library" + JANSSON_OPTION="jansson" + PBX_JANSSON=0 + +# Check whether --with-jansson was given. +if test "${with_jansson+set}" = set; then : + withval=$with_jansson; + case ${withval} in + n|no) + USE_JANSSON=no + # -1 is a magic value used by menuselect to know that the package + # was disabled, other than 'not found' + PBX_JANSSON=-1 + ;; + y|ye|yes) + ac_mandatory_list="${ac_mandatory_list} JANSSON" + ;; + *) + JANSSON_DIR="${withval}" + ac_mandatory_list="${ac_mandatory_list} JANSSON" + ;; + esac + +fi + + + + + + + + KQUEUE_DESCRIP="kqueue support" KQUEUE_OPTION="kqueue" PBX_KQUEUE=0 @@ -19865,6 +19903,111 @@ fi + +if test "x${PBX_JANSSON}" != "x1" -a "${USE_JANSSON}" != "no"; then + pbxlibdir="" + # if --with-JANSSON=DIR has been specified, use it. + if test "x${JANSSON_DIR}" != "x"; then + if test -d ${JANSSON_DIR}/lib; then + pbxlibdir="-L${JANSSON_DIR}/lib" + else + pbxlibdir="-L${JANSSON_DIR}" + fi + fi + pbxfuncname="json_dumps" + if test "x${pbxfuncname}" = "x" ; then # empty lib, assume only headers + AST_JANSSON_FOUND=yes + else + ast_ext_lib_check_save_CFLAGS="${CFLAGS}" + CFLAGS="${CFLAGS} " + as_ac_Lib=`$as_echo "ac_cv_lib_jansson_${pbxfuncname}" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${pbxfuncname} in -ljansson" >&5 +$as_echo_n "checking for ${pbxfuncname} in -ljansson... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ljansson ${pbxlibdir} $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char ${pbxfuncname} (); +int +main () +{ +return ${pbxfuncname} (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + AST_JANSSON_FOUND=yes +else + AST_JANSSON_FOUND=no +fi + + CFLAGS="${ast_ext_lib_check_save_CFLAGS}" + fi + + # now check for the header. + if test "${AST_JANSSON_FOUND}" = "yes"; then + JANSSON_LIB="${pbxlibdir} -ljansson " + # if --with-JANSSON=DIR has been specified, use it. + if test "x${JANSSON_DIR}" != "x"; then + JANSSON_INCLUDE="-I${JANSSON_DIR}/include" + fi + JANSSON_INCLUDE="${JANSSON_INCLUDE} " + if test "xjansson.h" = "x" ; then # no header, assume found + JANSSON_HEADER_FOUND="1" + else # check for the header + ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}" + CPPFLAGS="${CPPFLAGS} ${JANSSON_INCLUDE}" + ac_fn_c_check_header_mongrel "$LINENO" "jansson.h" "ac_cv_header_jansson_h" "$ac_includes_default" +if test "x$ac_cv_header_jansson_h" = xyes; then : + JANSSON_HEADER_FOUND=1 +else + JANSSON_HEADER_FOUND=0 +fi + + + CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}" + fi + if test "x${JANSSON_HEADER_FOUND}" = "x0" ; then + JANSSON_LIB="" + JANSSON_INCLUDE="" + else + if test "x${pbxfuncname}" = "x" ; then # only checking headers -> no library + JANSSON_LIB="" + fi + PBX_JANSSON=1 + cat >>confdefs.h <<_ACEOF +#define HAVE_JANSSON 1 +_ACEOF + + fi + fi +fi + + + # BSD (and OS X) equivalent of inotify if test "x${PBX_KQUEUE}" != "x1" -a "${USE_KQUEUE}" != "no"; then diff --git a/configure.ac b/configure.ac index 2b72b4e76..536d4425f 100644 --- a/configure.ac +++ b/configure.ac @@ -406,6 +406,7 @@ AST_EXT_LIB_SETUP([INOTIFY], [inotify support], [inotify]) AST_EXT_LIB_SETUP([IODBC], [iODBC], [iodbc]) AST_EXT_LIB_SETUP([ISDNNET], [ISDN4Linux], [isdnnet]) AST_EXT_LIB_SETUP([JACK], [Jack Audio Connection Kit], [jack]) +AST_EXT_LIB_SETUP([JANSSON], [Jansson JSON library], [jansson]) AST_EXT_LIB_SETUP([KQUEUE], [kqueue support], [kqueue]) AST_EXT_LIB_SETUP([LDAP], [OpenLDAP], [ldap]) AST_LIBCURL_CHECK_CONFIG([], [7.10.1]) @@ -1846,6 +1847,8 @@ AST_EXT_LIB_CHECK([INOTIFY], [c], [inotify_init], [sys/inotify.h]) AST_EXT_LIB_CHECK([JACK], [jack], [jack_activate], [jack/jack.h]) +AST_EXT_LIB_CHECK([JANSSON], [jansson], [json_dumps], [jansson.h]) + # BSD (and OS X) equivalent of inotify AST_EXT_LIB_CHECK([KQUEUE], [c], [kqueue], [sys/event.h]) diff --git a/contrib/scripts/install_prereq b/contrib/scripts/install_prereq index b59e35797..1359f9b84 100755 --- a/contrib/scripts/install_prereq +++ b/contrib/scripts/install_prereq @@ -27,7 +27,7 @@ PACKAGES_DEBIAN="$PACKAGES_DEBIAN libcurl-dev libspeex-dev libspeexdsp-dev libog PACKAGES_DEBIAN="$PACKAGES_DEBIAN libpq-dev unixodbc-dev libsqlite0-dev libmysqlclient15-dev libneon27-dev libgmime-dev libusb-dev liblua5.1-0-dev lua5.1" PACKAGES_DEBIAN="$PACKAGES_DEBIAN libopenh323-dev libvpb-dev libgtk2.0-dev libmysqlclient-dev libbluetooth-dev libradiusclient-ng-dev freetds-dev" PACKAGES_DEBIAN="$PACKAGES_DEBIAN libsnmp-dev libiksemel-dev libcorosync-dev libnewt-dev libpopt-dev libical-dev libspandsp-dev libjack-dev" -PACKAGES_DEBIAN="$PACKAGES_DEBIAN libresample-dev libc-client-dev binutils-dev libsrtp-dev libgsm1-dev libedit-dev doxygen" +PACKAGES_DEBIAN="$PACKAGES_DEBIAN libresample-dev libc-client-dev binutils-dev libsrtp-dev libgsm1-dev libedit-dev doxygen libjansson-dev" PACKAGES_RH="automake gcc gcc-c++ ncurses-devel openssl-devel libxml2-devel unixODBC-devel libcurl-devel libogg-devel libvorbis-devel speex-devel" PACKAGES_RH="$PACKAGES_RH spandsp-devel freetds-devel net-snmp-devel iksemel-devel corosynclib-devel newt-devel popt-devel libtool-ltdl-devel lua-devel" PACKAGES_RH="$PACKAGES_RH libsqlite3x-devel radiusclient-ng-devel portaudio-devel postgresql-devel libresample-devel neon-devel libical-devel" diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in index 07ea75249..52e96c08a 100644 --- a/include/asterisk/autoconfig.h.in +++ b/include/asterisk/autoconfig.h.in @@ -364,6 +364,9 @@ /* Define to 1 if you have the Jack Audio Connection Kit library. */ #undef HAVE_JACK +/* Define to 1 if you have the Jansson JSON library library. */ +#undef HAVE_JANSSON + /* Define to 1 if you have the `kevent64' function. */ #undef HAVE_KEVENT64 diff --git a/include/asterisk/json.h b/include/asterisk/json.h new file mode 100644 index 000000000..d287bd612 --- /dev/null +++ b/include/asterisk/json.h @@ -0,0 +1,762 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012 - 2013, Digium, Inc. + * + * David M. Lee, II + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +#ifndef _ASTERISK_JSON_H +#define _ASTERISK_JSON_H + +/*! \file + * + * \brief Asterisk JSON abstraction layer. + * \since 12.0.0 + * + * This is a very thin wrapper around the Jansson API. For more details on it, see its + * docs at http://www.digip.org/jansson/doc/2.4/apiref.html. + + * \author David M. Lee, II + */ + +/*!@{*/ + +/*! + * \brief Set custom allocators instead of the standard ast_malloc() and ast_free(). + * \since 12.0.0 + * + * This is used by the unit tests to do JSON specific memory leak detection. Since it + * affects all users of the JSON library, shouldn't normally be used. + * + * \param malloc_fn Custom allocation function. + * \param free_fn Matching free function. + */ +void ast_json_set_alloc_funcs(void *(*malloc_fn)(size_t), void (*free_fn)(void*)); + +/*! + * \brief Change alloc funcs back to the resource module defaults. + * \since 12.0.0 + * + * If you use ast_json_set_alloc_funcs() to temporarily change the allocator functions + * (i.e., from in a unit test), this function sets them back to ast_malloc() and + * ast_free(). + */ +void ast_json_reset_alloc_funcs(void); + +/*! + * \struct ast_json + * \brief Abstract JSON element (object, array, string, int, ...). + * \since 12.0.0 + */ +struct ast_json; + +/*! + * \brief Increase refcount on \a value. + * \since 12.0.0 + * + * \param value JSON value to reference. + * \return The given \a value. + */ +struct ast_json *ast_json_ref(struct ast_json *value); + +/*! + * \brief Decrease refcount on \a value. If refcount reaches zero, \a value is freed. + * \since 12.0.0 + */ +void ast_json_unref(struct ast_json *value); + +/*!@}*/ + +/*!@{*/ + +/*! + * \brief Valid types of a JSON element. + * \since 12.0.0 + */ +enum ast_json_type +{ + AST_JSON_OBJECT, + AST_JSON_ARRAY, + AST_JSON_STRING, + AST_JSON_INTEGER, + AST_JSON_REAL, + AST_JSON_TRUE, + AST_JSON_FALSE, + AST_JSON_NULL, +}; + +/*! + * \brief Get the type of \a value. + * \since 12.0.0 + * \param value Value to query. + * \return Type of \a value. + */ +enum ast_json_type ast_json_typeof(const struct ast_json *value); + +/*!@}*/ + +/*!@{*/ + +/*! + * \brief Get the JSON true value. + * \since 12.0.0 + * + * The returned value is a singleton, and does not need to be + * ast_json_unref()'ed. + * + * \return JSON true. + */ +struct ast_json *ast_json_true(void); + +/*! + * \brief Get the JSON false value. + * \since 12.0.0 + * + * The returned value is a singleton, and does not need to be + * ast_json_unref()'ed. + * + * \return JSON false. + */ +struct ast_json *ast_json_false(void); + +/*! + * \brief Get the JSON boolean corresponding to \a value. + * \since 12.0.0 + * \return JSON true if value is true (non-zero). + * \return JSON false if value is false (zero). + */ +struct ast_json *ast_json_boolean(int value); + +/*! + * \brief Get the JSON null value. + * \since 12.0.0 + * + * The returned value is a singleton, and does not need to be + * ast_json_unref()'ed. + * + * \return JSON null. + */ +struct ast_json *ast_json_null(void); + +/*! + * \brief Check if \a value is JSON true. + * \since 12.0.0 + * \return True (non-zero) if \a value == \ref ast_json_true(). + * \return False (zero) otherwise.. + */ +int ast_json_is_true(const struct ast_json *value); + +/*! + * \brief Check if \a value is JSON false. + * \since 12.0.0 + * \return True (non-zero) if \a value == \ref ast_json_false(). + * \return False (zero) otherwise. + */ +int ast_json_is_false(const struct ast_json *value); + +/*! + * \brief Check if \a value is JSON null. + * \since 12.0.0 + * \return True (non-zero) if \a value == \ref ast_json_false(). + * \return False (zero) otherwise. + */ +int ast_json_is_null(const struct ast_json *value); + +/*!@}*/ + +/*!@{*/ + +/*! + * \brief Construct a JSON string from \a value. + * \since 12.0.0 + * + * The given \a value must be a valid ASCII or UTF-8 encoded string. + * + * \param value Value of new JSON string. + * \return Newly constructed string element. + * \return \c NULL on error. + */ +struct ast_json *ast_json_string_create(const char *value); + +/*! + * \brief Get the value of a JSON string. + * \since 12.0.0 + * \param string JSON string. + * \return Value of the string. + * \return \c NULL on error. + */ +const char *ast_json_string_get(const struct ast_json *string); + +/*! + * \brief Change the value of a JSON string. + * \since 12.0.0 + * + * The given \a value must be a valid ASCII or UTF-8 encoded string. + * + * \param string JSON string to modify. + * \param value New value to store in \a string. + * \return 0 on success. + * \return -1 on error. + */ +int ast_json_string_set(struct ast_json *string, const char *value); + +/*! + * \brief Create a JSON string, printf style. + * \since 12.0.0 + * + * The formatted value must be a valid ASCII or UTF-8 encoded string. + * + * \param format \c printf style format string. + * \return Newly allocated string. + * \return \c NULL on error. + */ +struct ast_json *ast_json_stringf(const char *format, ...) __attribute__((format(printf, 1, 2))); + +/*! + * \brief Create a JSON string, vprintf style. + * \since 12.0.0 + * + * The formatted value must be a valid ASCII or UTF-8 encoded string. + * + * \param format \c printf style format string. + * \return Newly allocated string. + * \return \c NULL on error. + */ +struct ast_json *ast_json_vstringf(const char *format, va_list args) __attribute__((format(printf, 1, 0))); + +/*!@}*/ + +/*!@{*/ + +/*! + * \brief Create a JSON integer. + * \since 12.0.0 + * \param value Value of the new JSON integer. + * \return Newly allocated integer. + * \return \c NULL on error. + */ +struct ast_json *ast_json_integer_create(intmax_t value); + +/*! + * \brief Get the value from a JSON integer. + * \since 12.0.0 + * \param integer JSON integer. + * \return Value of a JSON integer. + * \return 0 if \a integer is not a JSON integer. + */ +intmax_t ast_json_integer_get(const struct ast_json *integer); + +/*! + * \brief Set the value of a JSON integer. + * \since 12.0.0 + * \param integer JSON integer to modify. + * \param value New value for \a integer. + * \return 0 on success. + * \return -1 on error. + */ +int ast_json_integer_set(struct ast_json *integer, intmax_t value); + +/*!@}*/ + +/*!@{*/ + +/*! + * \brief Create a empty JSON array. + * \since 12.0.0 + * \return Newly allocated array. + * \return \c NULL on error. + */ +struct ast_json *ast_json_array_create(void); + +/*! + * \brief Get the size of a JSON array. + * \since 12.0.0 + * \param array JSON array. + * \return Size of \a array. + * \return 0 if array is not a JSON array. + */ +size_t ast_json_array_size(const struct ast_json *array); + +/*! + * \brief Get an element from an array. + * \since 12.0.0 + * + * The returned element is a borrowed reference; use ast_json_ref() to safely keep a + * pointer to it. + * + * \param array JSON array. + * \param index Zero-based index into \a array. + * \return The specified element. + * \return \c NULL if \a array not an array. + * \return \c NULL if \a index is out of bounds. + */ +struct ast_json *ast_json_array_get(const struct ast_json *array, size_t index); + +/*! + * \brief Change an element in an array. + * \since 12.0.0 + * + * The \a array steals the \a value reference; use ast_json_ref() to safely keep a pointer + * to it. + * + * \param array JSON array to modify. + * \param index Zero-based index into array. + * \param value New JSON value to store in \a array at \a index. + * \return 0 on success. + * \return -1 on error. + */ +int ast_json_array_set(struct ast_json *array, size_t index, struct ast_json *value); + +/*! + * \brief Append to an array. + * \since 12.0.0 + * + * The array steals the \a value reference; use ast_json_ref() to safely keep a pointer + * to it. + * + * \param array JSON array to modify. + * \param value New JSON value to store at the end of \a array. + * \return 0 on success. + * \return -1 on error. + */ +int ast_json_array_append(struct ast_json *array, struct ast_json *value); + +/*! + * \brief Insert into an array. + * \since 12.0.0 + * + * The array steals the \a value reference; use ast_json_ref() to safely keep a pointer + * to it. + * + * \param array JSON array to modify. + * \param index Zero-based index into array. + * \param value New JSON value to store in \a array at \a index. + * \return 0 on success. + * \return -1 on error. + */ +int ast_json_array_insert(struct ast_json *array, size_t index, struct ast_json *value); + +/*! + * \brief Remove an element from an array. + * \since 12.0.0 + * \param array JSON array to modify. + * \param index Zero-based index into array. + * \return 0 on success. + * \return -1 on error. + */ +int ast_json_array_remove(struct ast_json *array, size_t index); + +/*! + * \brief Remove all elements from an array. + * \since 12.0.0 + * \param array JSON array to clear. + * \return 0 on success. + * \return -1 on error. + */ +int ast_json_array_clear(struct ast_json *array); + +/*! + * \brief Append all elements from \a tail to \a array. + * \since 12.0.0 + * + * The \a tail argument is not changed, so ast_json_unref() it when you are done with it. + * + * \param array JSON array to modify. + * \param tail JSON array with contents to append to \a array. + * \return 0 on success. + * \return -1 on error. + */ +int ast_json_array_extend(struct ast_json *array, struct ast_json *tail); + +/*!@}*/ + +/*!@{*/ + +/*! + * \brief Create a new JSON object. + * \since 12.0.0 + * \return Newly allocated object. + * \return \c NULL on error. + */ +struct ast_json *ast_json_object_create(void); + +/*! + * \brief Get size of JSON object. + * \since 12.0.0 + * \param object JSON object. + * \return Size of \a object. + * \return Zero of \a object is not a JSON object. + */ +size_t ast_json_object_size(struct ast_json *object); + +/*! + * \brief Get a field from a JSON object. + * \since 12.0.0 + * + * The returned element is a borrowed reference; use ast_json_ref() to safely keep a + * pointer to it. + * + * \param object JSON object. + * \param key Key of field to look up. + * \return Value with given \a key. + * \return \c NULL on error. + */ +struct ast_json *ast_json_object_get(struct ast_json *object, const char *key); + +/*! + * \brief Set a field in a JSON object. + * \since 12.0.0 + * + * The object steals the \a value reference; use ast_json_ref() to safely keep a pointer + * to it. + * + * \param object JSON object to modify. + * \param key Key of field to set. + * \param value JSON value to set for field. + * \return 0 on success. + * \return -1 on error. + */ +int ast_json_object_set(struct ast_json *object, const char *key, struct ast_json *value); + +/*! + * \brief Delete a field from a JSON object. + * \since 12.0.0 + * + * \param object JSON object to modify. + * \param key Key of field to delete. + * \return 0 on success, or -1 if key does not exist. + */ +int ast_json_object_del(struct ast_json *object, const char *key); + +/*! + * \brief Delete all elements from a JSON object. + * \since 12.0.0 + * \param object JSON object to clear. + * \return 0 on success. + * \return -1 on error. + */ +int ast_json_object_clear(struct ast_json *object); + +/*! + * \brief Update \a object with all of the fields of \a other. + * \since 12.0.0 + * + * All of the fields of \a other are copied into \a object, overwriting existing keys. + * The \a other object is not changed, so ast_json_unref() it when you are done with it. + * + * \param object JSON object to modify. + * \param other JSON object to copy into \a object. + * \return 0 on success. + * \return -1 on error. + */ +int ast_json_object_update(struct ast_json *object, struct ast_json *other); + +/*! + * \brief Update existing fields in \a object with the fields of \a other. + * \since 12.0.0 + * + * Like ast_json_object_update(), but only existing fields are updated. No new fields + * will get added. The \a other object is not changed, so ast_json_unref() it when you + * are done with it. + * + * \param object JSON object to modify. + * \param other JSON object to copy into \a object. + * \return 0 on success. + * \return -1 on error. + */ +int ast_json_object_update_existing(struct ast_json *object, struct ast_json *other); + +/*! + * \brief Add new fields to \a object with the fields of \a other. + * \since 12.0.0 + * + * Like ast_json_object_update(), but only missing fields are added. No existing fields + * will be modified. The \a other object is not changed, so ast_json_unref() it when you + * are done with it. + * + * \param object JSON object to modify. + * \param other JSON object to copy into \a object. + * \return 0 on success. + * \return -1 on error. + */ +int ast_json_object_update_missing(struct ast_json *object, struct ast_json *other); + +/*! + * \struct ast_json_iter + * \brief Iterator for JSON object key/values. + * \since 12.0.0 + * + * Note that iteration order is not specified, and may change as fields are added to + * and removed from the object. + */ +struct ast_json_iter; + +/*! + * \brief Get an iterator pointing to the first field in a JSON object. + * \since 12.0.0 + * + * The order of the fields in an object are not specified. However, iterating forward + * from this iterator will cover all fields in \a object. Adding or removing fields from + * \a object may invalidate its iterators. + * + * \param object JSON object. + * \return Iterator to the first field in \a object. + * \return \c NULL \a object is empty. + * \return \c NULL on error. + */ +struct ast_json_iter *ast_json_object_iter(struct ast_json *object); + +/*! + * \brief Get an iterator pointing to a specified \a key in \a object. + * \since 12.0.0 + * + * Iterating forward from this iterator may not to cover all elements in \a object. + * + * \param object JSON object to iterate. + * \param key Key of field to lookup. + * \return Iterator pointing to the field with the given \a key. + * \return \c NULL if \a key does not exist. + * \return \c NULL on error. + */ +struct ast_json_iter *ast_json_object_iter_at(struct ast_json *object, const char *key); + +/*! + * \brief Get the next iterator. + * \since 12.0.0 + * \param object JSON object \a iter was obtained from. + * \param iter JSON object iterator. + * \return Iterator to next field in \a object. + * \return \c NULL if \a iter was the last field. + */ +struct ast_json_iter *ast_json_object_iter_next(struct ast_json *object, struct ast_json_iter *iter); + +/*! + * \brief Get the key from an iterator. + * \since 12.0.0 + * \param iter JSON object iterator. + * \return Key of the field \a iter points to. + */ +const char *ast_json_object_iter_key(struct ast_json_iter *iter); + +/*! + * \brief Get the value from an iterator. + * \since 12.0.0 + * + * The returned element is a borrowed reference; use ast_json_ref() to safely + * keep a pointer to it. + * + * \param iter JSON object iterator. + * \return Value of the field \a iter points to. + */ +struct ast_json *ast_json_object_iter_value(struct ast_json_iter *iter); + +/*! + * \brief Set the value of the field pointed to by an iterator. + * \since 12.0.0 + * + * The array steals the value reference; use ast_json_ref() to safely keep a + * pointer to it. + * + * \param object JSON object \a iter was obtained from. + * \param iter JSON object iterator. + * \param value JSON value to store in \iter's field. + * \return 0 on success. + * \return -1 on error. + */ +int ast_json_object_iter_set(struct ast_json *object, struct ast_json_iter *iter, struct ast_json *value); + +/*!@}*/ + +/*!@{*/ + +/*! + * \brief Encode a JSON value to a string. + * \since 12.0.0 + * + * Returned string must be freed by calling ast_free(). + * + * \param JSON value. + * \return String encoding of \a root. + * \return \c NULL on error. + */ +char *ast_json_dump_string(struct ast_json *root); + +/*! + * \brief Encode a JSON value to an \ref ast_str. + * \since 12.0.0 + * + * If \a dst is too small, it will be grown as needed. + * + * \param root JSON value. + * \param dst \ref ast_str to store JSON encoding. + * \return 0 on success. + * \return -1 on error. The contents of \a dst are undefined. + */ +int ast_json_dump_str(struct ast_json *root, struct ast_str **dst); + +/*! + * \brief Encode a JSON value to a \c FILE. + * \since 12.0.0 + * + * \param root JSON value. + * \param output File to write JSON encoding to. + * \return 0 on success. + * \return -1 on error. The contents of \a output are undefined. + */ +int ast_json_dump_file(struct ast_json *root, FILE *output); + +/*! + * \brief Encode a JSON value to a file at the given location. + * \since 12.0.0 + * + * \param root JSON value. + * \param path Path to file to write JSON encoding to. + * \return 0 on success. + * \return -1 on error. The contents of \a output are undefined. + */ +int ast_json_dump_new_file(struct ast_json *root, const char *path); + +#define AST_JSON_ERROR_TEXT_LENGTH 160 +#define AST_JSON_ERROR_SOURCE_LENGTH 80 + +/*! + * \brief JSON parsing error information. + * \since 12.0.0 + */ +struct ast_json_error { + /*! Line number error occured on */ + int line; + /*! Character (not byte, can be different for UTF-8) column on which the error occurred. */ + int column; + /*! Position in bytes from start of input */ + int position; + /*! Error message */ + char text[AST_JSON_ERROR_TEXT_LENGTH]; + /*! Source of the error (filename or ) */ + char source[AST_JSON_ERROR_TEXT_LENGTH]; +}; + +/*! + * \brief Parse null terminated string into a JSON object or array. + * \since 12.0.0 + * \param input String to parse. + * \param[out] error Filled with information on error. + * \return Parsed JSON element. + * \return \c NULL on error. + */ +struct ast_json *ast_json_load_string(const char *input, struct ast_json_error *error); + +/*! + * \brief Parse \ref ast_str into a JSON object or array. + * \since 12.0.0 + * \param input \ref ast_str to parse. + * \param[out] error Filled with information on error. + * \return Parsed JSON element. + * \return \c NULL on error. + */ +struct ast_json *ast_json_load_str(const struct ast_str *input, struct ast_json_error *error); + +/*! + * \brief Parse buffer with known length into a JSON object or array. + * \since 12.0.0 + * \param buffer Buffer to parse. + * \param buflen Length of \a buffer. + * \param[out] error Filled with information on error. + * \return Parsed JSON element. + * \return \c NULL on error. + */ +struct ast_json *ast_json_load_buf(const char *buffer, size_t buflen, struct ast_json_error *error); + +/*! + * \brief Parse a \c FILE into JSON object or array. + * \since 12.0.0 + * \param input \c FILE to parse. + * \param[out] error Filled with information on error. + * \return Parsed JSON element. + * \return \c NULL on error. + */ +struct ast_json *ast_json_load_file(FILE *input, struct ast_json_error *error); + +/*! + * \brief Parse file at \a path into JSON object or array. + * \since 12.0.0 + * \param path Path of file to parse. + * \param[out] error Filled with information on error. + * \return Parsed JSON element. + * \return \c NULL on error. + */ +struct ast_json *ast_json_load_new_file(const char *path, struct ast_json_error *error); + +/*! + * \brief Helper for creating complex JSON values. + * \since 12.0.0 + * + * See original Jansson docs at http://www.digip.org/jansson/doc/2.4/apiref.html#apiref-pack + * for more details. + */ +struct ast_json *ast_json_pack(char const *format, ...); + +/*! + * \brief Helper for creating complex JSON values simply. + * \since 12.0.0 + * + * See original Jansson docs at http://www.digip.org/jansson/doc/2.4/apiref.html#apiref-pack + * for more details. + */ +struct ast_json *ast_json_vpack(char const *format, va_list ap); + +/*!@}*/ + +/*!@{*/ + +/*! + * \brief Compare two JSON objects. + * \since 12.0.0 + * + * Two JSON objects are equal if they are of the same type, and their contents are equal. + * + * \param lhs Value to compare. + * \param rhs Other value to compare. + * \return True (non-zero) if \a lhs and \a rhs are equal. + * \return False (zero) if they are not. + */ +int ast_json_equal(const struct ast_json *lhs, const struct ast_json *rhs); + +/*! + * \brief Copy a JSON value, but not its children. + * \since 12.0.0 + * + * If \a value is a JSON object or array, its children are shared with the returned copy. + * + * \param value JSON value to copy. + * \return Shallow copy of \a value. + * \return \c NULL on error. + */ +struct ast_json *ast_json_copy(const struct ast_json *value); + +/*! + * \brief Copy a JSON value, and its children. + * \since 12.0.0 + * + * If \a value is a JSON object or array, they are also copied. + * + * \param value JSON value to copy. + * \return Deep copy of \a value. + * \return \c NULL on error. + */ +struct ast_json *ast_json_deep_copy(const struct ast_json *value); + +/*!@}*/ + +#endif /* _ASTERISK_JSON_H */ diff --git a/include/asterisk/test.h b/include/asterisk/test.h index ea79385d8..5ad389f00 100644 --- a/include/asterisk/test.h +++ b/include/asterisk/test.h @@ -1,7 +1,7 @@ /* * Asterisk -- An open source telephony toolkit. * - * Copyright (C) 2009-2010, Digium, Inc. + * Copyright (C) 2009-2013, Digium, Inc. * * David Vossel * Russell Bryant @@ -283,5 +283,31 @@ int __ast_test_status_update(const char *file, const char *func, int line, struc */ #define ast_test_status_update(t, f, ...) __ast_test_status_update(__FILE__, __PRETTY_FUNCTION__, __LINE__, (t), (f), ## __VA_ARGS__) +/*! + * \brief Check a test condition, failing the test if it's not true. + * + * \since 12.0.0 + * + * This macro evaluates \a condition. If the condition evaluates to true (non-zero), + * nothing happens. If it evaluates to false (zero), then the failure is printed + * using \ref ast_test_status_update, and the current test is ended with AST_TEST_FAIL. + * + * Sadly, the name 'ast_test_assert' was already taken. + * + * Note that since this macro returns from the current test, there must not be any + * cleanup work to be done before returning. Use \ref RAII_VAR for test cleanup. + * + * \param \a test Currently executing test + * \param \a condition Boolean condition to check. + */ +#define ast_test_validate(test, condition) \ + do { \ + if (!(condition)) { \ + __ast_test_status_update(__FILE__, __PRETTY_FUNCTION__, __LINE__, (test), "Condition failed: %s\n", #condition); \ + return AST_TEST_FAIL; \ + } \ + } while(0) + + #endif /* TEST_FRAMEWORK */ #endif /* _AST_TEST_H */ diff --git a/main/Makefile b/main/Makefile index d0a2d0559..fbb885cf8 100644 --- a/main/Makefile +++ b/main/Makefile @@ -35,6 +35,7 @@ AST_LIBS+=$(BKTR_LIB) AST_LIBS+=$(LIBXML2_LIB) AST_LIBS+=$(SQLITE3_LIB) AST_LIBS+=$(ASTSSL_LIBS) +AST_LIBS+=$(JANSSON_LIB) ifneq ($(findstring $(OSARCH), linux-gnu uclinux linux-uclibc linux-gnueabi kfreebsd-gnu linux-gnueabihf),) ifneq ($(findstring LOADABLE_MODULES,$(MENUSELECT_CFLAGS)),) diff --git a/makeopts.in b/makeopts.in index 01cc70790..c10507fcb 100644 --- a/makeopts.in +++ b/makeopts.in @@ -169,6 +169,9 @@ IODBC_LIB=@IODBC_LIB@ JACK_INCLUDE=@JACK_INCLUDE@ JACK_LIB=@JACK_LIB@ +JANSSON_INCLUDE=@JANSSON_INCLUDE@ +JANSSON_LIB=@JANSSON_LIB@ + LDAP_INCLUDE=@LDAP_INCLUDE@ LDAP_LIB=@LDAP_LIB@ diff --git a/res/res_json.c b/res/res_json.c new file mode 100644 index 000000000..218c09be2 --- /dev/null +++ b/res/res_json.c @@ -0,0 +1,517 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012 - 2013, Digium, Inc. + * + * David M. Lee, II + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief JSON abstraction layer. + * + * This is a very thin wrapper around the Jansson API. For more details on it, see its + * docs at http://www.digip.org/jansson/doc/2.4/apiref.html. + * + * \author David M. Lee, II + */ + +/*** MODULEINFO + jansson + core + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/json.h" +#include "asterisk/module.h" +#include "asterisk/utils.h" + +#include + +/*! + * \brief Function wrapper around ast_malloc macro. + */ +static void *json_malloc(size_t size) +{ + return ast_malloc(size); +} + +/*! + * \brief Function wrapper around ast_free macro. + */ +static void json_free(void *p) +{ + ast_free(p); +} + +void ast_json_set_alloc_funcs(void *(*malloc_fn)(size_t), void (*free_fn)(void*)) +{ + json_set_alloc_funcs(malloc_fn, free_fn); +} + +void ast_json_reset_alloc_funcs(void) +{ + json_set_alloc_funcs(json_malloc, json_free); +} + +struct ast_json *ast_json_ref(struct ast_json *json) +{ + json_incref((json_t *)json); + return json; +} + +void ast_json_unref(struct ast_json *json) +{ + json_decref((json_t *)json); +} + +enum ast_json_type ast_json_typeof(const struct ast_json *json) +{ + int r = json_typeof((json_t*)json); + switch(r) { + case JSON_OBJECT: return AST_JSON_OBJECT; + case JSON_ARRAY: return AST_JSON_ARRAY; + case JSON_STRING: return AST_JSON_STRING; + case JSON_INTEGER: return AST_JSON_INTEGER; + case JSON_REAL: return AST_JSON_REAL; + case JSON_TRUE: return AST_JSON_TRUE; + case JSON_FALSE: return AST_JSON_FALSE; + case JSON_NULL: return AST_JSON_NULL; + } + ast_assert(0); /* Unexpect return from json_typeof */ + return r; +} + +struct ast_json *ast_json_true(void) +{ + return (struct ast_json *)json_true(); +} + +struct ast_json *ast_json_false(void) +{ + return (struct ast_json *)json_false(); +} + +struct ast_json *ast_json_boolean(int value) +{ +#if JANSSON_VERSION_HEX >= 0x020400 + return (struct ast_json *)json_boolean(value); +#else + return value ? ast_json_true() : ast_json_false(); +#endif +} + +struct ast_json *ast_json_null(void) +{ + return (struct ast_json *)json_null(); +} + +int ast_json_is_true(const struct ast_json *json) +{ + return json_is_true((const json_t *)json); +} + +int ast_json_is_false(const struct ast_json *json) +{ + return json_is_false((const json_t *)json); +} + +int ast_json_is_null(const struct ast_json *json) +{ + return json_is_null((const json_t *)json); +} + +struct ast_json *ast_json_string_create(const char *value) +{ + return (struct ast_json *)json_string(value); +} + +const char *ast_json_string_get(const struct ast_json *string) +{ + return json_string_value((json_t *)string); +} + +int ast_json_string_set(struct ast_json *string, const char *value) +{ + return json_string_set((json_t *)string, value); +} + +struct ast_json *ast_json_stringf(const char *format, ...) +{ + struct ast_json *ret; + va_list args; + va_start(args, format); + ret = ast_json_vstringf(format, args); + va_end(args); + return ret; +} + +struct ast_json *ast_json_vstringf(const char *format, va_list args) +{ + char *str = NULL; + json_t *ret = NULL; + + if (format) { + int err = vasprintf(&str, format, args); + if (err > 0) { + ret = json_string(str); + free(str); + } + } + return (struct ast_json *)ret; +} + +struct ast_json *ast_json_integer_create(intmax_t value) +{ + return (struct ast_json *)json_integer(value); +} + +intmax_t ast_json_integer_get(const struct ast_json *integer) +{ + return json_integer_value((json_t *)integer); +} + +int ast_json_integer_set(struct ast_json *integer, intmax_t value) +{ + return json_integer_set((json_t *)integer, value); +} + + +int ast_json_equal(const struct ast_json *lhs, const struct ast_json *rhs) +{ + return json_equal((json_t *)lhs, (json_t *)rhs); +} + +struct ast_json *ast_json_array_create(void) +{ + return (struct ast_json *)json_array(); +} +size_t ast_json_array_size(const struct ast_json *array) +{ + return json_array_size((json_t *)array); +} +struct ast_json *ast_json_array_get(const struct ast_json *array, size_t index) +{ + return (struct ast_json *)json_array_get((json_t *)array, index); +} +int ast_json_array_set(struct ast_json *array, size_t index, struct ast_json *value) +{ + return json_array_set_new((json_t *)array, index, (json_t *)value); +} +int ast_json_array_append(struct ast_json *array, struct ast_json *value) +{ + return json_array_append_new((json_t *)array, (json_t *)value); +} +int ast_json_array_insert(struct ast_json *array, size_t index, struct ast_json *value) +{ + return json_array_insert_new((json_t *)array, index, (json_t *)value); +} +int ast_json_array_remove(struct ast_json *array, size_t index) +{ + return json_array_remove((json_t *)array, index); +} +int ast_json_array_clear(struct ast_json *array) +{ + return json_array_clear((json_t *)array); +} +int ast_json_array_extend(struct ast_json *array, struct ast_json *tail) +{ + return json_array_extend((json_t *)array, (json_t *)tail); +} + +struct ast_json *ast_json_object_create(void) +{ + return (struct ast_json *)json_object(); +} +size_t ast_json_object_size(struct ast_json *object) +{ + return json_object_size((json_t *)object); +} +struct ast_json *ast_json_object_get(struct ast_json *object, const char *key) +{ + if (key) { + return (struct ast_json *)json_object_get((json_t *)object, key); + } + return NULL; +} +int ast_json_object_set(struct ast_json *object, const char *key, struct ast_json *value) +{ + return json_object_set_new((json_t *)object, key, (json_t *)value); +} +int ast_json_object_del(struct ast_json *object, const char *key) +{ + return json_object_del((json_t *)object, key); +} +int ast_json_object_clear(struct ast_json *object) +{ + return json_object_clear((json_t *)object); +} +int ast_json_object_update(struct ast_json *object, struct ast_json *other) +{ + return json_object_update((json_t *)object, (json_t *)other); +} +int ast_json_object_update_existing(struct ast_json *object, struct ast_json *other) +{ +#if JANSSON_VERSION_HEX >= 0x020300 + return json_object_update_existing((json_t *)object, (json_t *)other); +#else + struct ast_json_iter *iter = ast_json_object_iter(other); + int ret = 0; + + if (object == NULL || other == NULL) { + return -1; + } + + while (iter != NULL && ret == 0) { + const char *key = ast_json_object_iter_key(iter); + if (ast_json_object_get(object, key) != NULL) { + ret = ast_json_object_set(object, key, ast_json_object_iter_value(iter)); + } + iter = ast_json_object_iter_next(other, iter); + } + return ret; +#endif +} +int ast_json_object_update_missing(struct ast_json *object, struct ast_json *other) +{ +#if JANSSON_VERSION_HEX >= 0x020300 + return json_object_update_missing((json_t *)object, (json_t *)other); +#else + struct ast_json_iter *iter = ast_json_object_iter(other); + int ret = 0; + + if (object == NULL || other == NULL) { + return -1; + } + + while (iter != NULL && ret == 0) { + const char *key = ast_json_object_iter_key(iter); + if (ast_json_object_get(object, key) == NULL) { + ret = ast_json_object_set(object, key, ast_json_object_iter_value(iter)); + } + iter = ast_json_object_iter_next(other, iter); + } + return ret; +#endif +} + +struct ast_json_iter *ast_json_object_iter(struct ast_json *object) +{ + return json_object_iter((json_t *)object); +} +struct ast_json_iter *ast_json_object_iter_at(struct ast_json *object, const char *key) +{ + return json_object_iter_at((json_t *)object, key); +} +struct ast_json_iter *ast_json_object_iter_next(struct ast_json *object, struct ast_json_iter *iter) +{ + return json_object_iter_next((json_t *)object, iter); +} +const char *ast_json_object_iter_key(struct ast_json_iter *iter) +{ + return json_object_iter_key(iter); +} +struct ast_json *ast_json_object_iter_value(struct ast_json_iter *iter) +{ + return (struct ast_json *)json_object_iter_value(iter); +} +int ast_json_object_iter_set(struct ast_json *object, struct ast_json_iter *iter, struct ast_json *value) +{ + return json_object_iter_set_new((json_t *)object, iter, (json_t *)value); +} + +/*! + * \brief Default flags for JSON encoding. + */ +static size_t dump_flags(void) +{ + /* There's a chance this could become a runtime flag */ + int flags = JSON_COMPACT; +#ifdef AST_DEVMODE + /* In dev mode, write readable JSON */ + flags = JSON_INDENT(2) | JSON_PRESERVE_ORDER; +#endif + return flags; +} + +char *ast_json_dump_string(struct ast_json *root) +{ + return json_dumps((json_t *)root, dump_flags()); +} + +static int write_to_ast_str(const char *buffer, size_t size, void *data) +{ + struct ast_str **dst = data; + size_t str_size = ast_str_size(*dst); + size_t remaining = str_size - ast_str_strlen(*dst); + int ret; + + /* While ast_str_append will grow the ast_str, it won't report + * allocation errors. Fortunately, it's not that hard. + */ + + /* Remaining needs to be big enough for buffer, plus null char */ + while (remaining < size + 1) { + /* doubling the size of the buffer gives us 'amortized + * constant' time. + * See http://stackoverflow.com/a/249695/115478 for info. + */ + str_size *= 2; + remaining = str_size - ast_str_strlen(*dst); + } + + ret = ast_str_make_space(dst, str_size); + if (ret == -1) { + /* Could not alloc; fail */ + return -1; + } + + ast_str_append_substr(dst, -1, buffer, size); + return 0; +} + +int ast_json_dump_str(struct ast_json *root, struct ast_str **dst) +{ + return json_dump_callback((json_t *)root, write_to_ast_str, dst, dump_flags()); +} + + +int ast_json_dump_file(struct ast_json *root, FILE *output) +{ + if (root && output) { + return json_dumpf((json_t *)root, output, dump_flags()); + } + return -1; +} +int ast_json_dump_new_file(struct ast_json *root, const char *path) +{ + return json_dump_file((json_t *)root, path, dump_flags()); +} + +/*! + * \brief Copy Jansson error struct to ours. + */ +static void copy_error(struct ast_json_error *error, const json_error_t *jansson_error) +{ + if (error && jansson_error) { + error->line = jansson_error->line; + error->column = jansson_error->column; + error->position = jansson_error->position; + ast_copy_string(error->text, jansson_error->text, sizeof(error->text)); + ast_copy_string(error->source, jansson_error->source, sizeof(error->source)); + } + +} + +static void parse_error(struct ast_json_error *error, const char *text, const char *source) +{ + if (error != NULL) { + error->line = 0; + error->column = 0; + error->position = 0; + strncpy(error->text, text, sizeof(error->text)); + strncpy(error->source, source, sizeof(error->text)); + } +} + +struct ast_json *ast_json_load_string(const char *input, struct ast_json_error *error) +{ + json_error_t jansson_error = {}; + struct ast_json *r = NULL; + if (input != NULL) { + r = (struct ast_json *)json_loads(input, 0, &jansson_error); + copy_error(error, &jansson_error); + } else { + parse_error(error, "NULL input string", ""); + } + return r; +} + +struct ast_json *ast_json_load_str(const struct ast_str *input, struct ast_json_error *error) +{ + return ast_json_load_string(ast_str_buffer(input), error); +} + +struct ast_json *ast_json_load_buf(const char *buffer, size_t buflen, struct ast_json_error *error) +{ + json_error_t jansson_error = {}; + struct ast_json *r = (struct ast_json *)json_loadb(buffer, buflen, 0, &jansson_error); + copy_error(error, &jansson_error); + return r; +} +struct ast_json *ast_json_load_file(FILE *input, struct ast_json_error *error) +{ + json_error_t jansson_error = {}; + struct ast_json *r = NULL; + if (input != NULL) { + r = (struct ast_json *)json_loadf(input, 0, &jansson_error); + copy_error(error, &jansson_error); + } else { + parse_error(error, "NULL input file", ""); + } + return r; +} +struct ast_json *ast_json_load_new_file(const char *path, struct ast_json_error *error) +{ + json_error_t jansson_error = {}; + struct ast_json *r = (struct ast_json *)json_load_file(path, 0, &jansson_error); + copy_error(error, &jansson_error); + return r; +} + +struct ast_json *ast_json_pack(char const *format, ...) +{ + struct ast_json *ret; + va_list args; + va_start(args, format); + ret = ast_json_vpack(format, args); + va_end(args); + return ret; +} +struct ast_json *ast_json_vpack(char const *format, va_list ap) +{ + struct ast_json *r = NULL; + if (format) { + r = (struct ast_json *)json_vpack_ex(NULL, 0, format, ap); + } + return r; +} + +struct ast_json *ast_json_copy(const struct ast_json *value) +{ + return (struct ast_json *)json_copy((json_t *)value); +} +struct ast_json *ast_json_deep_copy(const struct ast_json *value) +{ + return (struct ast_json *)json_deep_copy((json_t *)value); +} + +static int unload_module(void) +{ + /* Nothing to do */ + return 0; +} + +static int load_module(void) +{ + /* Setup to use Asterisk custom allocators */ + ast_json_reset_alloc_funcs(); + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "JSON library", + .load = load_module, + .unload = unload_module); diff --git a/res/res_json.exports.in b/res/res_json.exports.in new file mode 100644 index 000000000..bc07da53c --- /dev/null +++ b/res/res_json.exports.in @@ -0,0 +1,6 @@ +{ + global: + LINKER_SYMBOL_PREFIXast_json_*; + local: + *; +}; diff --git a/tests/test_json.c b/tests/test_json.c new file mode 100644 index 000000000..f67e35284 --- /dev/null +++ b/tests/test_json.c @@ -0,0 +1,1701 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012 - 2013, Digium, Inc. + * + * David M. Lee, II + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file \brief Test JSON API. + * + * While some of these tests are actually testing our JSON library wrapper, the bulk of + * them are exploratory tests to determine what the behavior of the underlying JSON + * library is. This also gives us a good indicator if that behavior changes between + * Jansson revisions. + * + * \author\verbatim David M. Lee, II \endverbatim + * + * \ingroup tests + */ + +/*** MODULEINFO + TEST_FRAMEWORK + res_json + core + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") +#include "asterisk/json.h" +#include "asterisk/module.h" +#include "asterisk/test.h" + +/*! + * Number of allocations from JSON library that have not yet been freed. + */ +static size_t alloc_count; + +/*!@{*/ +/*! + * JSON library has its own reference counting, so we'll provide our own allocators to + * test that everything gets freed as expected. + */ +static void *json_debug_malloc(size_t size) +{ + void *p = ast_malloc(size); + if (p) { + ++alloc_count; + } + return p; +} + +static void json_debug_free(void *p) +{ + if (p) { + --alloc_count; + } + ast_free(p); +} + +static void *json_test_init(struct ast_test *test) +{ + ast_json_set_alloc_funcs(json_debug_malloc, json_debug_free); + alloc_count = 0; + return test; +} + +static void json_test_finish(void *test) +{ + struct ast_test *t = test; + ast_json_reset_alloc_funcs(); + if (0 != alloc_count) { + ast_test_status_update(t, "JSON test leaked %zd allocations!", alloc_count); + } +} + +/*!@}*/ + +AST_TEST_DEFINE(json_test_false) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "false"; + info->category = "/main/json/"; + info->summary = "Testing fundamental JSON false value."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + uut = ast_json_false(); + ast_test_validate(test, NULL != uut); + ast_test_validate(test, AST_JSON_FALSE == ast_json_typeof(uut)); + ast_test_validate(test, !ast_json_is_null(uut)); + ast_test_validate(test, !ast_json_is_true(uut)); + ast_test_validate(test, ast_json_is_false(uut)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_true) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "true"; + info->category = "/main/json/"; + info->summary = "Testing JSON true value."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + uut = ast_json_true(); + ast_test_validate(test, NULL != uut); + ast_test_validate(test, AST_JSON_TRUE == ast_json_typeof(uut)); + ast_test_validate(test, !ast_json_is_null(uut)); + ast_test_validate(test, ast_json_is_true(uut)); + ast_test_validate(test, !ast_json_is_false(uut)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_bool0) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "bool0"; + info->category = "/main/json/"; + info->summary = "Testing JSON boolean function (false)."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + uut = ast_json_boolean(0); + ast_test_validate(test, NULL != uut); + ast_test_validate(test, AST_JSON_FALSE == ast_json_typeof(uut)); + ast_test_validate(test, !ast_json_is_null(uut)); + ast_test_validate(test, !ast_json_is_true(uut)); + ast_test_validate(test, ast_json_is_false(uut)); + ast_test_validate(test, ast_json_equal(uut, ast_json_false())); + ast_test_validate(test, !ast_json_equal(uut, ast_json_true())); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_bool1) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "bool1"; + info->category = "/main/json/"; + info->summary = "Testing JSON boolean function (true)."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + uut = ast_json_boolean(1); + ast_test_validate(test, NULL != uut); + ast_test_validate(test, AST_JSON_TRUE == ast_json_typeof(uut)); + ast_test_validate(test, !ast_json_is_null(uut)); + ast_test_validate(test, ast_json_is_true(uut)); + ast_test_validate(test, !ast_json_is_false(uut)); + ast_test_validate(test, !ast_json_equal(uut, ast_json_false())); + ast_test_validate(test, ast_json_equal(uut, ast_json_true())); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_null) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "null"; + info->category = "/main/json/"; + info->summary = "Testing JSON null value."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + uut = ast_json_null(); + ast_test_validate(test, NULL != uut); + ast_test_validate(test, AST_JSON_NULL == ast_json_typeof(uut)); + ast_test_validate(test, ast_json_is_null(uut)); + ast_test_validate(test, !ast_json_is_true(uut)); + ast_test_validate(test, !ast_json_is_false(uut)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_null_val) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + switch (cmd) { + case TEST_INIT: + info->name = "null_val"; + info->category = "/main/json/"; + info->summary = "Testing JSON handling of NULL."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* NULL isn't null, true or false */ + ast_test_validate(test, !ast_json_is_null(NULL)); + ast_test_validate(test, !ast_json_is_false(NULL)); + ast_test_validate(test, !ast_json_is_true(NULL)); + + /* ref and unref should be NULL safe */ + ast_json_ref(NULL); + ast_json_unref(NULL); + /* no segfault; we're good. le sigh. */ + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_string) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "string"; + info->category = "/main/json/"; + info->summary = "Basic string tests."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + uut = ast_json_string_create("Hello, json"); + ast_test_validate(test, NULL != uut); + ast_test_validate(test, AST_JSON_STRING == ast_json_typeof(uut)); + ast_test_validate(test, 0 == strcmp("Hello, json", ast_json_string_get(uut))); + + uut_res = ast_json_string_set(uut, NULL); + ast_test_validate(test, -1 == uut_res); + ast_test_validate(test, 0 == strcmp("Hello, json", ast_json_string_get(uut))); + + uut_res = ast_json_string_set(uut, "Not UTF-8 - \xff"); + ast_test_validate(test, -1 == uut_res); + ast_test_validate(test, 0 == strcmp("Hello, json", ast_json_string_get(uut))); + + uut_res = ast_json_string_set(uut, "Is UTF-8 - \xE2\x98\xBA"); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, 0 == strcmp("Is UTF-8 - \xE2\x98\xBA", ast_json_string_get(uut))); + + uut_res = ast_json_string_set(uut, "Goodbye, json"); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, 0 == strcmp("Goodbye, json", ast_json_string_get(uut))); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_string_null) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "string_null"; + info->category = "/main/json/"; + info->summary = "JSON string NULL tests."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* NULL string */ + uut = ast_json_string_create(NULL); + ast_test_validate(test, NULL == uut); + + /* NULL JSON strings */ + ast_test_validate(test, NULL == ast_json_string_create(NULL)); + ast_test_validate(test, NULL == ast_json_string_get(NULL)); + ast_test_validate(test, -1 == ast_json_string_set(NULL, "not null")); + + /* string_value from non-string elements should return NULL */ + ast_test_validate(test, NULL == ast_json_string_get(ast_json_null())); + ast_test_validate(test, NULL == ast_json_string_get(ast_json_false())); + ast_test_validate(test, NULL == ast_json_string_get(ast_json_true())); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_stringf) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "stringf"; + info->category = "/main/json/"; + info->summary = "Basic string formatting tests."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* NULL format string */ + uut = ast_json_stringf(NULL); + ast_test_validate(test, NULL == uut); + + /* Non-UTF-8 strings are invalid */ + uut = ast_json_stringf("Not UTF-8 - %s", "\xff"); + ast_test_validate(test, NULL == uut); + + /* formatted string */ + uut = ast_json_stringf("Hello, %s", "json"); + expected = ast_json_string_create("Hello, json"); + ast_test_validate(test, NULL != uut); + ast_test_validate(test, ast_json_equal(expected, uut)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_int) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "int"; + info->category = "/main/json/"; + info->summary = "Basic JSON integer tests."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* Integer tests */ + uut = ast_json_integer_create(0); + ast_test_validate(test, NULL != uut); + ast_test_validate(test, AST_JSON_INTEGER == ast_json_typeof(uut)); + ast_test_validate(test, 0 == ast_json_integer_get(uut)); + + uut_res = ast_json_integer_set(uut, 1); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, 1 == ast_json_integer_get(uut)); + + uut_res = ast_json_integer_set(uut, -1); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, -1 == ast_json_integer_get(uut)); + + uut_res = ast_json_integer_set(uut, LLONG_MAX); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, LLONG_MAX == ast_json_integer_get(uut)); + + uut_res = ast_json_integer_set(uut, LLONG_MIN); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, LLONG_MIN == ast_json_integer_get(uut)); + + ast_json_unref(uut); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_non_int) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "non_int"; + info->category = "/main/json/"; + info->summary = "Testing integer functions with non-integer types."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* Non-ints return 0 integer value */ + ast_test_validate(test, 0 == ast_json_integer_get(ast_json_null())); + ast_test_validate(test, 0 == ast_json_integer_get(ast_json_true())); + ast_test_validate(test, 0 == ast_json_integer_get(ast_json_false())); + + /* JSON NULL integers */ + ast_test_validate(test, 0 == ast_json_integer_get(NULL)); + ast_test_validate(test, -1 == ast_json_integer_set(NULL, 911)); + ast_test_validate(test, 0 == ast_json_array_size(NULL)); + + /* No magical parsing of strings into ints */ + uut = ast_json_string_create("314"); + ast_test_validate(test, NULL != uut); + ast_test_validate(test, 0 == ast_json_integer_get(uut)); + + /* Or vice-versa */ + ast_json_unref(uut); + uut = ast_json_integer_create(314); + ast_test_validate(test, NULL == ast_json_string_get(uut)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_array_create) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "array_create"; + info->category = "/main/json/"; + info->summary = "Testing creating JSON arrays."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* array creation */ + uut = ast_json_array_create(); + ast_test_validate(test, NULL != uut); + ast_test_validate(test, AST_JSON_ARRAY == ast_json_typeof(uut)); + ast_test_validate(test, 0 == ast_json_array_size(uut)); + + ast_json_unref(uut); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_array_append) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "array_append"; + info->category = "/main/json/"; + info->summary = "Testing appending to JSON arrays."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* array append */ + uut = ast_json_array_create(); + uut_res = ast_json_array_append(uut, ast_json_string_create("one")); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, 1 == ast_json_array_size(uut)); + ast_test_validate(test, 0 == strcmp("one", ast_json_string_get(ast_json_array_get(uut, 0)))); + /* index out of range */ + ast_test_validate(test, NULL == ast_json_array_get(uut, 1)); + ast_test_validate(test, NULL == ast_json_array_get(uut, -1)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_array_inset) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "array_insert"; + info->category = "/main/json/"; + info->summary = "Testing inserting into JSON arrays."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* array insert */ + uut = ast_json_pack("[s]", "one"); + uut_res = ast_json_array_insert(uut, 0, ast_json_string_create("zero")); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, 2 == ast_json_array_size(uut)); + ast_test_validate(test, 0 == strcmp("zero", ast_json_string_get(ast_json_array_get(uut, 0)))); + ast_test_validate(test, 0 == strcmp("one", ast_json_string_get(ast_json_array_get(uut, 1)))); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_array_set) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "array_set"; + info->category = "/main/json/"; + info->summary = "Testing setting a value in JSON arrays."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* array set */ + uut = ast_json_pack("[s, s]", "zero", "one"); + uut_res = ast_json_array_set(uut, 1, ast_json_integer_create(1)); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, 2 == ast_json_array_size(uut)); + ast_test_validate(test, 0 == strcmp("zero", ast_json_string_get(ast_json_array_get(uut, 0)))); + ast_test_validate(test, 1 == ast_json_integer_get(ast_json_array_get(uut, 1))); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_array_remove) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "array_remove"; + info->category = "/main/json/"; + info->summary = "Testing removing a value from JSON arrays."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* array remove */ + uut = ast_json_pack("[s, i]", "zero", 1); + expected = ast_json_pack("[i]", 1); + uut_res = ast_json_array_remove(uut, 0); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, ast_json_equal(expected, uut)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_array_clear) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "array_clear"; + info->category = "/main/json/"; + info->summary = "Testing clearing JSON arrays."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* array clear */ + uut = ast_json_pack("[s, s]", "zero", "one"); + uut_res = ast_json_array_clear(uut); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, 0 == ast_json_array_size(uut)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_array_extend) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, tail, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "array_extend"; + info->category = "/main/json/"; + info->summary = "Testing extending JSON arrays."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* array extending */ + expected = ast_json_array_create(); + ast_json_array_append(expected, ast_json_string_create("a")); + ast_json_array_append(expected, ast_json_string_create("b")); + ast_json_array_append(expected, ast_json_string_create("c")); + ast_json_array_append(expected, ast_json_integer_create(1)); + ast_json_array_append(expected, ast_json_integer_create(2)); + ast_json_array_append(expected, ast_json_integer_create(3)); + + uut = ast_json_array_create(); + ast_json_array_append(uut, ast_json_string_create("a")); + ast_json_array_append(uut, ast_json_string_create("b")); + ast_json_array_append(uut, ast_json_string_create("c")); + + tail = ast_json_array_create(); + ast_json_array_append(tail, ast_json_integer_create(1)); + ast_json_array_append(tail, ast_json_integer_create(2)); + ast_json_array_append(tail, ast_json_integer_create(3)); + + uut_res = ast_json_array_extend(uut, tail); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, ast_json_equal(expected, uut)); + /* tail is preserved */ + ast_test_validate(test, 3 == ast_json_array_size(tail)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_array_null) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "array_null"; + info->category = "/main/json/"; + info->summary = "Testing NULL conditions for JSON arrays."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* array NULL checks */ + ast_test_validate(test, 0 == ast_json_array_size(NULL)); + ast_test_validate(test, NULL == ast_json_array_get(NULL, 0)); + ast_test_validate(test, -1 == ast_json_array_set(NULL, 0, ast_json_null())); + ast_test_validate(test, -1 == ast_json_array_append(NULL, ast_json_null())); + ast_test_validate(test, -1 == ast_json_array_insert(NULL, 0, ast_json_null())); + ast_test_validate(test, -1 == ast_json_array_remove(NULL, 0)); + ast_test_validate(test, -1 == ast_json_array_clear(NULL)); + uut = ast_json_array_create(); + ast_test_validate(test, -1 == ast_json_array_extend(uut, NULL)); + ast_test_validate(test, -1 == ast_json_array_extend(NULL, uut)); + ast_test_validate(test, -1 == ast_json_array_extend(NULL, NULL)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_object_alloc) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "object_alloc"; + info->category = "/main/json/"; + info->summary = "Testing creating JSON objects."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* object allocation */ + uut = ast_json_object_create(); + ast_test_validate(test, NULL != uut); + ast_test_validate(test, AST_JSON_OBJECT == ast_json_typeof(uut)); + ast_test_validate(test, 0 == ast_json_object_size(uut)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_object_set) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "object_set"; + info->category = "/main/json/"; + info->summary = "Testing setting values in JSON objects."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* object set */ + expected = ast_json_pack("{s: i, s: i, s: i}", "one", 1, "two", 2, "three", 3); + uut = ast_json_object_create(); + uut_res = ast_json_object_set(uut, "one", ast_json_integer_create(1)); + ast_test_validate(test, 0 == uut_res); + uut_res = ast_json_object_set(uut, "two", ast_json_integer_create(2)); + ast_test_validate(test, 0 == uut_res); + uut_res = ast_json_object_set(uut, "three", ast_json_integer_create(3)); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, ast_json_equal(expected, uut)); + ast_test_validate(test, NULL == ast_json_object_get(uut, "dne")); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_object_set_overwrite) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "object_set_overwriting"; + info->category = "/main/json/"; + info->summary = "Testing changing values in JSON objects."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* object set existing */ + uut = ast_json_pack("{s: i, s: i, s: i}", "one", 1, "two", 2, "three", 3); + uut_res = ast_json_object_set(uut, "two", ast_json_integer_create(-2)); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, -2 == ast_json_integer_get(ast_json_object_get(uut, "two"))); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_object_get) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "object_get"; + info->category = "/main/json/"; + info->summary = "Testing getting values from JSON objects."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* object get */ + uut = ast_json_pack("{s: i, s: i, s: i}", "one", 1, "two", 2, "three", 3); + ast_test_validate(test, 2 == ast_json_integer_get(ast_json_object_get(uut, "two"))); + ast_test_validate(test, NULL == ast_json_object_get(uut, "dne")); + ast_test_validate(test, NULL == ast_json_object_get(uut, NULL)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_object_del) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "object_del"; + info->category = "/main/json/"; + info->summary = "Testing deleting values from JSON objects."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* object del */ + expected = ast_json_object_create(); + uut = ast_json_pack("{s: i}", "one", 1); + uut_res = ast_json_object_del(uut, "one"); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, ast_json_equal(expected, uut)); + uut_res = ast_json_object_del(uut, "dne"); + ast_test_validate(test, -1 == uut_res); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_object_clear) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "object_clear"; + info->category = "/main/json/"; + info->summary = "Testing clearing values from JSON objects."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* object clear */ + uut = ast_json_object_create(); + ast_json_object_set(uut, "one", ast_json_integer_create(1)); + ast_json_object_set(uut, "two", ast_json_integer_create(2)); + ast_json_object_set(uut, "three", ast_json_integer_create(3)); + uut_res = ast_json_object_clear(uut); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, 0 == ast_json_object_size(uut)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_object_merge_all) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, merge, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "object_alloc"; + info->category = "/main/json/"; + info->summary = "Testing merging JSON objects."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* object merging - all */ + uut = ast_json_object_create(); + ast_json_object_set(uut, "one", ast_json_integer_create(1)); + ast_json_object_set(uut, "two", ast_json_integer_create(2)); + ast_json_object_set(uut, "three", ast_json_integer_create(3)); + + merge = ast_json_object_create(); + ast_json_object_set(merge, "three", ast_json_integer_create(-3)); + ast_json_object_set(merge, "four", ast_json_integer_create(-4)); + ast_json_object_set(merge, "five", ast_json_integer_create(-5)); + + expected = ast_json_object_create(); + ast_json_object_set(expected, "one", ast_json_integer_create(1)); + ast_json_object_set(expected, "two", ast_json_integer_create(2)); + ast_json_object_set(expected, "three", ast_json_integer_create(-3)); + ast_json_object_set(expected, "four", ast_json_integer_create(-4)); + ast_json_object_set(expected, "five", ast_json_integer_create(-5)); + + uut_res = ast_json_object_update(uut, merge); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, ast_json_equal(expected, uut)); + /* merge object is untouched */ + ast_test_validate(test, 3 == ast_json_object_size(merge)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_object_merge_existing) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, merge, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "object_alloc"; + info->category = "/main/json/"; + info->summary = "Testing merging JSON objects, updating only existing fields."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* object merging - existing */ + uut = ast_json_object_create(); + ast_json_object_set(uut, "one", ast_json_integer_create(1)); + ast_json_object_set(uut, "two", ast_json_integer_create(2)); + ast_json_object_set(uut, "three", ast_json_integer_create(3)); + + merge = ast_json_object_create(); + ast_json_object_set(merge, "three", ast_json_integer_create(-3)); + ast_json_object_set(merge, "four", ast_json_integer_create(-4)); + ast_json_object_set(merge, "five", ast_json_integer_create(-5)); + + expected = ast_json_object_create(); + ast_json_object_set(expected, "one", ast_json_integer_create(1)); + ast_json_object_set(expected, "two", ast_json_integer_create(2)); + ast_json_object_set(expected, "three", ast_json_integer_create(-3)); + + uut_res = ast_json_object_update_existing(uut, merge); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, ast_json_equal(expected, uut)); + /* merge object is untouched */ + ast_test_validate(test, 3 == ast_json_object_size(merge)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_object_merge_missing) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, merge, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "object_merge_missing"; + info->category = "/main/json/"; + info->summary = "Testing merging JSON objects, adding only missing fields."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* object merging - missing */ + uut = ast_json_object_create(); + ast_json_object_set(uut, "one", ast_json_integer_create(1)); + ast_json_object_set(uut, "two", ast_json_integer_create(2)); + ast_json_object_set(uut, "three", ast_json_integer_create(3)); + + merge = ast_json_object_create(); + ast_json_object_set(merge, "three", ast_json_integer_create(-3)); + ast_json_object_set(merge, "four", ast_json_integer_create(-4)); + ast_json_object_set(merge, "five", ast_json_integer_create(-5)); + + expected = ast_json_object_create(); + ast_json_object_set(expected, "one", ast_json_integer_create(1)); + ast_json_object_set(expected, "two", ast_json_integer_create(2)); + ast_json_object_set(expected, "three", ast_json_integer_create(3)); + ast_json_object_set(expected, "four", ast_json_integer_create(-4)); + ast_json_object_set(expected, "five", ast_json_integer_create(-5)); + + uut_res = ast_json_object_update_missing(uut, merge); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, ast_json_equal(expected, uut)); + /* merge object is untouched */ + ast_test_validate(test, 3 == ast_json_object_size(merge)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_object_null) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "object_null"; + info->category = "/main/json/"; + info->summary = "Testing JSON object NULL behavior."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* Object NULL testing */ + ast_test_validate(test, 0 == ast_json_object_size(NULL)); + ast_test_validate(test, NULL == ast_json_object_get(NULL, "not null")); + ast_test_validate(test, -1 == ast_json_object_set(NULL, "not null", ast_json_null())); + ast_test_validate(test, -1 == ast_json_object_del(NULL, "not null")); + ast_test_validate(test, -1 == ast_json_object_clear(NULL)); + uut = ast_json_object_create(); + ast_test_validate(test, -1 == ast_json_object_update(NULL, uut)); + ast_test_validate(test, -1 == ast_json_object_update(uut, NULL)); + ast_test_validate(test, -1 == ast_json_object_update(NULL, NULL)); + ast_test_validate(test, -1 == ast_json_object_update_existing(NULL, uut)); + ast_test_validate(test, -1 == ast_json_object_update_existing(uut, NULL)); + ast_test_validate(test, -1 == ast_json_object_update_existing(NULL, NULL)); + ast_test_validate(test, -1 == ast_json_object_update_missing(NULL, uut)); + ast_test_validate(test, -1 == ast_json_object_update_missing(uut, NULL)); + ast_test_validate(test, -1 == ast_json_object_update_missing(NULL, NULL)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_object_iter) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + struct ast_json_iter *iter; + int count; + int uut_res; + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "object_iter"; + info->category = "/main/json/"; + info->summary = "Testing iterating through JSON objects."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* Object iterator testing */ + uut = ast_json_pack("{s: i, s: i, s: i, s: i, s: i}", "one", 1, "two", 2, "three", 3, "four", 4, "five", 5); + + /* Iterate through the object; be aware that order isn't specified */ + iter = ast_json_object_iter(uut); + ast_test_validate(test, NULL != iter); + count = 0; + while (NULL != iter) { + if (0 == strcmp("one", ast_json_object_iter_key(iter))) { + ast_test_validate(test, 1 == ast_json_integer_get(ast_json_object_iter_value(iter))); + } else if (0 == strcmp("two", ast_json_object_iter_key(iter))) { + ast_test_validate(test, 2 == ast_json_integer_get(ast_json_object_iter_value(iter))); + } else if (0 == strcmp("three", ast_json_object_iter_key(iter))) { + ast_test_validate(test, 3 == ast_json_integer_get(ast_json_object_iter_value(iter))); + } else if (0 == strcmp("four", ast_json_object_iter_key(iter))) { + ast_test_validate(test, 4 == ast_json_integer_get(ast_json_object_iter_value(iter))); + } else if (0 == strcmp("five", ast_json_object_iter_key(iter))) { + ast_test_validate(test, 5 == ast_json_integer_get(ast_json_object_iter_value(iter))); + } else { + /* Unexpected key */ + ast_test_validate(test, 0); + } + iter = ast_json_object_iter_next(uut, iter); + ++count; + } + ast_test_validate(test, 5 == count); + + /* iterator non-existing key */ + iter = ast_json_object_iter_at(uut, "dne"); + ast_test_validate(test, NULL == iter); + + /* iterator specific key */ + iter = ast_json_object_iter_at(uut, "three"); + ast_test_validate(test, NULL != iter); + ast_test_validate(test, 3 == ast_json_integer_get(ast_json_object_iter_value(iter))); + + /* set via iter */ + iter = ast_json_object_iter_at(uut, "three"); + uut_res = ast_json_object_iter_set(uut, iter, ast_json_integer_create(-3)); + ast_test_validate(test, 0 == uut_res); + ast_test_validate(test, -3 == ast_json_integer_get(ast_json_object_get(uut, "three"))); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_object_iter_null) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "object_iter_null"; + info->category = "/main/json/"; + info->summary = "Testing JSON object iterator NULL testings."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* iterator NULL tests */ + uut = ast_json_object_create(); + ast_test_validate(test, NULL == ast_json_object_iter(NULL)); + ast_test_validate(test, NULL == ast_json_object_iter_at(NULL, "not null")); + ast_test_validate(test, NULL == ast_json_object_iter_next(NULL, NULL)); + ast_test_validate(test, NULL == ast_json_object_iter_next(uut, NULL)); + ast_test_validate(test, NULL == ast_json_object_iter_key(NULL)); + ast_test_validate(test, NULL == ast_json_object_iter_value(NULL)); + ast_test_validate(test, -1 == ast_json_object_iter_set(NULL, NULL, ast_json_null())); + ast_test_validate(test, -1 == ast_json_object_iter_set(uut, NULL, ast_json_null())); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_dump_load_string) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + RAII_VAR(char *, str, NULL, json_debug_free); + + switch (cmd) { + case TEST_INIT: + info->name = "dump_load_string"; + info->category = "/main/json/"; + info->summary = "Testing dumping strings from JSON."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + expected = ast_json_pack("{ s: i }", "one", 1); + str = ast_json_dump_string(expected); + ast_test_validate(test, NULL != str); + uut = ast_json_load_string(str, NULL); + ast_test_validate(test, NULL != uut); + ast_test_validate(test, ast_json_equal(expected, uut)); + + /* dump_string NULL */ + ast_test_validate(test, NULL == ast_json_dump_string(NULL)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_dump_load_str) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + RAII_VAR(struct ast_str *, astr, NULL, ast_free); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "dump_load_str"; + info->category = "/main/json/"; + info->summary = "Testing dumping ast_str from JSON."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* dump/load ast_str */ + expected = ast_json_pack("{ s: i }", "one", 1); + astr = ast_str_create(1); /* should expand to hold output */ + uut_res = ast_json_dump_str(expected, &astr); + ast_test_validate(test, 0 == uut_res); + uut = ast_json_load_str(astr, NULL); + ast_test_validate(test, NULL != uut); + ast_test_validate(test, ast_json_equal(expected, uut)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_dump_str_fail) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + struct ast_str *astr; + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "dump_str_fail"; + info->category = "/main/json/"; + info->summary = "Testing dumping to ast_str when it can't grow."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* dump ast_str growth failure */ + expected = ast_json_pack("{ s: i }", "one", 1); + astr = ast_str_alloca(1); /* cannot grow */ + uut_res = ast_json_dump_str(expected, &astr); + ast_test_validate(test, 0 != uut_res); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_load_buffer) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + const char *str; + + switch (cmd) { + case TEST_INIT: + info->name = "load_buffer"; + info->category = "/main/json/"; + info->summary = "Testing loading JSON from buffer."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* load buffer */ + str = "{ \"one\": 1 } trailing garbage"; + uut = ast_json_load_string(str, NULL); + ast_test_validate(test, NULL == uut); + uut = ast_json_load_buf(str, strlen("{ \"one\": 1 }"), NULL); + ast_test_validate(test, NULL != uut); + + return AST_TEST_PASS; +} + +/*! \brief \a fclose isn't NULL safe. */ +static int safe_fclose(FILE *f) +{ + if (f) { + return fclose(f); + } + return 0; +} + +AST_TEST_DEFINE(json_test_dump_load_file) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + RAII_VAR(char *, filename, NULL, free); + RAII_VAR(FILE *, file, NULL, safe_fclose); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "dump_load_file"; + info->category = "/main/json/"; + info->summary = "Testing dumping/loading JSON to/from file by FILE *."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* dump/load file */ + expected = ast_json_pack("{ s: i }", "one", 1); + filename = tempnam(NULL, "ast-json"); + file = fopen(filename, "w"); + uut_res = ast_json_dump_file(expected, file); + ast_test_validate(test, 0 == uut_res); + fclose(file); + file = fopen(filename, "r"); + uut = ast_json_load_file(file, NULL); + ast_test_validate(test, ast_json_equal(expected, uut)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_dump_load_new_file) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + RAII_VAR(char *, filename, NULL, free); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "dump_load_new_file"; + info->category = "/main/json/"; + info->summary = "Testing dumping/load JSON to/from file by filename."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* dump/load filename */ + expected = ast_json_pack("{ s: i }", "one", 1); + filename = tempnam(NULL, "ast-json"); + uut_res = ast_json_dump_new_file(expected, filename); + ast_test_validate(test, 0 == uut_res); + uut = ast_json_load_new_file(filename, NULL); + ast_test_validate(test, ast_json_equal(expected, uut)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_dump_load_null) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(char *, filename, NULL, free); + RAII_VAR(FILE *, file, NULL, safe_fclose); + + switch (cmd) { + case TEST_INIT: + info->name = "dump_load_null"; + info->category = "/main/json/"; + info->summary = "Testing NULL handling of dump/load functions."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* dump/load NULL tests */ + uut = ast_json_load_string("{ \"one\": 1 }", NULL); + ast_test_validate(test, NULL != uut); + filename = tempnam(NULL, "ast-json"); + file = fopen(filename, "w"); + ast_test_validate(test, NULL == ast_json_dump_string(NULL)); + ast_test_validate(test, -1 == ast_json_dump_file(NULL, file)); + ast_test_validate(test, -1 == ast_json_dump_file(uut, NULL)); + ast_test_validate(test, -1 == ast_json_dump_file(NULL, NULL)); + ast_test_validate(test, -1 == ast_json_dump_new_file(uut, NULL)); + ast_test_validate(test, -1 == ast_json_dump_new_file(NULL, filename)); + ast_test_validate(test, -1 == ast_json_dump_new_file(NULL, NULL)); + ast_test_validate(test, NULL == ast_json_load_string(NULL, NULL)); + ast_test_validate(test, NULL == ast_json_load_buf(NULL, 0, NULL)); + ast_test_validate(test, NULL == ast_json_load_file(NULL, NULL)); + ast_test_validate(test, NULL == ast_json_load_new_file(NULL, NULL)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_parse_errors) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "parse_errors"; + info->category = "/main/json/"; + info->summary = "Testing various parse errors."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* parse errors */ + ast_test_validate(test, NULL == ast_json_load_string("'singleton'", NULL)); + ast_test_validate(test, NULL == ast_json_load_string("{ no value }", NULL)); + ast_test_validate(test, NULL == ast_json_load_string("{ 'no': 'curly' ", NULL)); + ast_test_validate(test, NULL == ast_json_load_string("[ 'no', 'square'", NULL)); + ast_test_validate(test, NULL == ast_json_load_string("{ 1: 'int key' }", NULL)); + ast_test_validate(test, NULL == ast_json_load_string("", NULL)); + ast_test_validate(test, NULL == ast_json_load_string("{ 'missing' 'colon' }", NULL)); + ast_test_validate(test, NULL == ast_json_load_string("[ 'missing' 'comma' ]", NULL)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_pack) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "pack"; + info->category = "/main/json/"; + info->summary = "Testing json_pack function."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* pack test */ + expected = ast_json_array_create(); + ast_json_array_append(expected, ast_json_array_create()); + ast_json_array_append(expected, ast_json_object_create()); + ast_json_array_append(ast_json_array_get(expected, 0), ast_json_integer_create(1)); + ast_json_array_append(ast_json_array_get(expected, 0), ast_json_integer_create(2)); + ast_json_object_set(ast_json_array_get(expected, 1), "cool", ast_json_true()); + uut = ast_json_pack("[[i,i],{s:b}]", 1, 2, "cool", 1); + ast_test_validate(test, NULL != uut); + ast_test_validate(test, ast_json_equal(expected, uut)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_pack_errors) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "object_alloc"; + info->category = "/main/json/"; + info->summary = "Testing json_pack failure conditions."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* pack errors */ + ast_test_validate(test, NULL == ast_json_pack(NULL)); + ast_test_validate(test, NULL == ast_json_pack("{s:i", "no curly", 911)); + ast_test_validate(test, NULL == ast_json_pack("[s, s", "no", "square")); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_copy) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "copy"; + info->category = "/main/json/"; + info->summary = "Testing copying JSON."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* copy test */ + expected = ast_json_pack("{s: {s: i}}", "outer", "inner", 8675309); + uut = ast_json_copy(expected); + ast_test_validate(test, NULL != uut); + ast_test_validate(test, ast_json_equal(expected, uut)); + ast_test_validate(test, ast_json_object_get(expected, "outer") == ast_json_object_get(uut, "outer")); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_deep_copy) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, expected, NULL, ast_json_unref); + + switch (cmd) { + case TEST_INIT: + info->name = "deep_copy"; + info->category = "/main/json/"; + info->summary = "Testing deep copying of JSON."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* deep copy test */ + expected = ast_json_pack("{s: {s: i}}", "outer", "inner", 8675309); + uut = ast_json_deep_copy(expected); + ast_test_validate(test, NULL != uut); + ast_test_validate(test, ast_json_equal(expected, uut)); + ast_test_validate(test, ast_json_object_get(expected, "outer") != ast_json_object_get(uut, "outer")); + /* Changing the inner value of one should not change the other */ + ast_json_integer_set(ast_json_object_get(ast_json_object_get(uut, "outer"), "inner"), 411); + ast_test_validate(test, !ast_json_equal(expected, uut)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_copy_null) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + switch (cmd) { + case TEST_INIT: + info->name = "copy_null"; + info->category = "/main/json/"; + info->summary = "Testing NULL handling of copy functions."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* copy NULL */ + ast_test_validate(test, NULL == ast_json_copy(NULL)); + ast_test_validate(test, NULL == ast_json_deep_copy(NULL)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_circular_object) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "circular_object"; + info->category = "/main/json/"; + info->summary = "Object cannot be added to itself."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* circular reference testing */ + /* Cannot add self */ + uut = ast_json_object_create(); + uut_res = ast_json_object_set(uut, "myself", uut); + ast_test_validate(test, -1 == uut_res); + ast_test_validate(test, 0 == ast_json_object_size(uut)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_circular_array) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "circular_array"; + info->category = "/main/json/"; + info->summary = "Array cannot be added to itself."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + uut = ast_json_array_create(); + uut_res = ast_json_object_set(uut, "myself", uut); + ast_test_validate(test, -1 == uut_res); + ast_test_validate(test, 0 == ast_json_array_size(uut)); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(json_test_clever_circle) +{ + RAII_VAR(void *, alloc_debug, json_test_init(test), json_test_finish); + RAII_VAR(struct ast_json *, uut, NULL, ast_json_unref); + RAII_VAR(struct ast_json *, inner_child, NULL, ast_json_unref); + RAII_VAR(char *, str, NULL, json_debug_free); + int uut_res; + + switch (cmd) { + case TEST_INIT: + info->name = "clever_circle"; + info->category = "/main/json/"; + info->summary = "JSON with circular references cannot be encoded."; + info->description = "Test JSON abstraction library."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* can add to self if you're clever enough, but it should not encode */ + uut = ast_json_object_create(); + inner_child = ast_json_object_create(); + uut_res = ast_json_object_set(uut, "inner_child", ast_json_ref(inner_child)); /* incref to keep a reference */ + ast_test_validate(test, 0 == uut_res); + uut_res = ast_json_object_set(inner_child, "parent", ast_json_ref(uut)); /* incref to keep a reference */ + ast_test_validate(test, 0 == uut_res); + str = ast_json_dump_string(uut); + ast_test_validate(test, NULL == str); + /* Circular refs screw up reference counting, so break the cycle */ + ast_json_object_clear(inner_child); + + return AST_TEST_PASS; +} + +static int unload_module(void) +{ + AST_TEST_UNREGISTER(json_test_false); + AST_TEST_UNREGISTER(json_test_true); + AST_TEST_UNREGISTER(json_test_bool0); + AST_TEST_UNREGISTER(json_test_bool1); + AST_TEST_UNREGISTER(json_test_null); + AST_TEST_UNREGISTER(json_test_null_val); + AST_TEST_UNREGISTER(json_test_string); + AST_TEST_UNREGISTER(json_test_string_null); + AST_TEST_UNREGISTER(json_test_stringf); + AST_TEST_UNREGISTER(json_test_int); + AST_TEST_UNREGISTER(json_test_non_int); + AST_TEST_UNREGISTER(json_test_array_create); + AST_TEST_UNREGISTER(json_test_array_append); + AST_TEST_UNREGISTER(json_test_array_inset); + AST_TEST_UNREGISTER(json_test_array_set); + AST_TEST_UNREGISTER(json_test_array_remove); + AST_TEST_UNREGISTER(json_test_array_clear); + AST_TEST_UNREGISTER(json_test_array_extend); + AST_TEST_UNREGISTER(json_test_array_null); + AST_TEST_UNREGISTER(json_test_object_alloc); + AST_TEST_UNREGISTER(json_test_object_set); + AST_TEST_UNREGISTER(json_test_object_set_overwrite); + AST_TEST_UNREGISTER(json_test_object_get); + AST_TEST_UNREGISTER(json_test_object_del); + AST_TEST_UNREGISTER(json_test_object_clear); + AST_TEST_UNREGISTER(json_test_object_merge_all); + AST_TEST_UNREGISTER(json_test_object_merge_existing); + AST_TEST_UNREGISTER(json_test_object_merge_missing); + AST_TEST_UNREGISTER(json_test_object_null); + AST_TEST_UNREGISTER(json_test_object_iter); + AST_TEST_UNREGISTER(json_test_object_iter_null); + AST_TEST_UNREGISTER(json_test_dump_load_string); + AST_TEST_UNREGISTER(json_test_dump_load_str); + AST_TEST_UNREGISTER(json_test_dump_str_fail); + AST_TEST_UNREGISTER(json_test_load_buffer); + AST_TEST_UNREGISTER(json_test_dump_load_file); + AST_TEST_UNREGISTER(json_test_dump_load_new_file); + AST_TEST_UNREGISTER(json_test_dump_load_null); + AST_TEST_UNREGISTER(json_test_parse_errors); + AST_TEST_UNREGISTER(json_test_pack); + AST_TEST_UNREGISTER(json_test_pack_errors); + AST_TEST_UNREGISTER(json_test_copy); + AST_TEST_UNREGISTER(json_test_deep_copy); + AST_TEST_UNREGISTER(json_test_copy_null); + AST_TEST_UNREGISTER(json_test_circular_object); + AST_TEST_UNREGISTER(json_test_circular_array); + AST_TEST_UNREGISTER(json_test_clever_circle); + return 0; +} + +static int load_module(void) +{ + AST_TEST_REGISTER(json_test_false); + AST_TEST_REGISTER(json_test_true); + AST_TEST_REGISTER(json_test_bool0); + AST_TEST_REGISTER(json_test_bool1); + AST_TEST_REGISTER(json_test_null); + AST_TEST_REGISTER(json_test_null_val); + AST_TEST_REGISTER(json_test_string); + AST_TEST_REGISTER(json_test_string_null); + AST_TEST_REGISTER(json_test_stringf); + AST_TEST_REGISTER(json_test_int); + AST_TEST_REGISTER(json_test_non_int); + AST_TEST_REGISTER(json_test_array_create); + AST_TEST_REGISTER(json_test_array_append); + AST_TEST_REGISTER(json_test_array_inset); + AST_TEST_REGISTER(json_test_array_set); + AST_TEST_REGISTER(json_test_array_remove); + AST_TEST_REGISTER(json_test_array_clear); + AST_TEST_REGISTER(json_test_array_extend); + AST_TEST_REGISTER(json_test_array_null); + AST_TEST_REGISTER(json_test_object_alloc); + AST_TEST_REGISTER(json_test_object_set); + AST_TEST_REGISTER(json_test_object_set_overwrite); + AST_TEST_REGISTER(json_test_object_get); + AST_TEST_REGISTER(json_test_object_del); + AST_TEST_REGISTER(json_test_object_clear); + AST_TEST_REGISTER(json_test_object_merge_all); + AST_TEST_REGISTER(json_test_object_merge_existing); + AST_TEST_REGISTER(json_test_object_merge_missing); + AST_TEST_REGISTER(json_test_object_null); + AST_TEST_REGISTER(json_test_object_iter); + AST_TEST_REGISTER(json_test_object_iter_null); + AST_TEST_REGISTER(json_test_dump_load_string); + AST_TEST_REGISTER(json_test_dump_load_str); + AST_TEST_REGISTER(json_test_dump_str_fail); + AST_TEST_REGISTER(json_test_load_buffer); + AST_TEST_REGISTER(json_test_dump_load_file); + AST_TEST_REGISTER(json_test_dump_load_new_file); + AST_TEST_REGISTER(json_test_dump_load_null); + AST_TEST_REGISTER(json_test_parse_errors); + AST_TEST_REGISTER(json_test_pack); + AST_TEST_REGISTER(json_test_pack_errors); + AST_TEST_REGISTER(json_test_copy); + AST_TEST_REGISTER(json_test_deep_copy); + AST_TEST_REGISTER(json_test_copy_null); + AST_TEST_REGISTER(json_test_circular_object); + AST_TEST_REGISTER(json_test_circular_array); + AST_TEST_REGISTER(json_test_clever_circle); + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "JSON testing."); -- cgit v1.2.3