Packages
glazer
0.5.14
0.5.17
0.5.16
0.5.15
0.5.14
retired
0.5.13
retired
0.5.12
retired
0.5.11
retired
0.5.10
retired
0.5.9
retired
0.5.8
retired
0.5.7
retired
0.5.6
retired
0.5.5
retired
0.5.4
retired
0.5.3
retired
0.5.2
retired
0.5.1
retired
0.5.0
retired
0.3.2
retired
0.3.0
retired
0.2.6
retired
0.2.5
retired
0.2.4
retired
0.2.3
retired
0.2.2
retired
0.2.1
retired
0.2.0
retired
0.1.7
retired
0.1.6
retired
0.1.5
retired
0.1.4
retired
Erlang NIF JSON encoder/decoder using the glaze C++ library
Retired package: Deprecated - Deprecated
Current section
Files
Jump to
Current section
Files
c_src/glazer_jq.hpp
// vim:ts=2:sw=2:et
//-----------------------------------------------------------------------------
// Optional jq-filter support for glazer:json_query/2.
//
// When libjq (https://github.com/jqlang/jq) and its headers are available at
// build time (GLAZER_HAVE_JQ defined by c_src/Makefile), this implements a
// jq filter over a JSON document: the input is parsed by libjq's `jv_parse`,
// the filter program is compiled and run via `jq_compile`/`jq_start`/
// `jq_next`, and each output value is serialized back to JSON text via
// `jv_dump_string` and re-decoded into an Erlang term using glazer's own
// JSON decoder (so decode options/null handling stay consistent with
// json_decode/2).
//
// When libjq is unavailable, json_query/2 returns {error, jq_not_available}.
//-----------------------------------------------------------------------------
#pragma once
#include <memory>
#include <string>
#include <erl_nif.h>
#include "glazer_atoms.hpp"
#include "glazer_json.hpp"
#ifdef GLAZER_HAVE_JQ
#include <jq.h>
#include <jv.h>
namespace glz {
struct JqSmartPtr : std::unique_ptr<jq_state, void(*)(jq_state*)> {
JqSmartPtr() noexcept
: unique_ptr(jq_init(), [](jq_state* jq) { jq_teardown(&jq); }) {}
};
// RAII wrapper around jv's refcounted value type: calls jv_free on
// destruction, and jv_copy when a copy is taken (jv's own copy semantics
// are refcount-based, so this mirrors how raw jv values are normally used).
class JVGuard {
public:
// v is passed by value because jv itself is a small value type — it's a
// struct wrapping a tagged pointer/refcounted handle (defined in jv.h),
// not a heap-allocated object accessed through a pointer.
// libjq's API passes and returns jv by value everywhere.
explicit JVGuard(jv v) noexcept : m_v(v) {}
JVGuard(const JVGuard& o) noexcept : m_v(jv_copy(o.m_v)) {}
JVGuard(JVGuard&& o) noexcept : m_v(o.m_v) { o.m_v = jv_invalid(); }
~JVGuard() { jv_free(m_v); }
JVGuard& operator=(const JVGuard&) = delete;
JVGuard& operator=(JVGuard&& o) noexcept {
if (this != &o) {
jv_free(m_v);
m_v = o.m_v;
o.m_v = jv_invalid();
}
return *this;
}
jv get() const noexcept { return m_v; }
jv release() noexcept { jv val = m_v; m_v = jv_invalid(); return val; }
bool valid() const noexcept { return jv_is_valid(m_v); }
private:
jv m_v;
};
inline ERL_NIF_TERM jq_query(ErlNifEnv* env, const ErlNifBinary& input,
const ErlNifBinary& filter, const JSONDecodeOpts& opts)
{
JqSmartPtr jq;
if (!jq) [[unlikely]]
return enif_make_tuple2(env, AM_ERROR, AM_ENOMEM);
std::string err_msg;
jq_set_error_cb(jq.get(), [](void* data, jv msg) {
auto* out = static_cast<std::string*>(data);
if (jv_get_kind(msg) != JV_KIND_STRING)
msg = jv_dump_string(msg, 0);
if (!out->empty())
*out += "; ";
*out += jv_string_value(msg);
jv_free(msg);
}, &err_msg);
std::string filter_str(reinterpret_cast<const char*>(filter.data), filter.size);
if (!jq_compile(jq.get(), filter_str.c_str()))
return enif_make_tuple2(env, AM_ERROR,
enif_make_tuple2(env, AM_JQ_COMPILE_ERROR, make_binary(env, err_msg)));
std::string input_str(reinterpret_cast<const char*>(input.data), input.size);
JVGuard input_val(jv_parse(input_str.c_str()));
if (!input_val.valid())
return enif_make_tuple2(env, AM_ERROR, AM_INVALID_INPUT);
jq_start(jq.get(), input_val.release(), 0);
std::vector<ERL_NIF_TERM> results;
for (;;) {
JVGuard result(jq_next(jq.get()));
if (!result.valid()) {
if (jv_invalid_has_msg(jv_copy(result.get()))) {
JVGuard msg(jv_invalid_get_msg(result.release()));
if (jv_get_kind(msg.get()) != JV_KIND_STRING)
msg = JVGuard(jv_dump_string(msg.release(), 0));
if (!err_msg.empty())
err_msg += "; ";
err_msg += jv_string_value(msg.get());
}
break;
}
JVGuard text(jv_dump_string(result.release(), 0));
std::string_view sv(jv_string_value(text.get()), jv_string_length_bytes(jv_copy(text.get())));
JSONDecoder dec(env, opts, sv.data(), sv.size());
auto [success, decoded] = dec.decode(sv.data(), sv.size());
// jq's own JSON output should always be valid JSON; if re-decoding
// ever fails, surface it rather than silently dropping the value.
if (!success) [[unlikely]]
return enif_make_tuple2(env, AM_ERROR, AM_JQ_DECODE_ERROR);
results.push_back(decoded);
}
if (!err_msg.empty())
return enif_make_tuple2(env, AM_ERROR, make_binary(env, err_msg));
return enif_make_tuple2(env, AM_OK,
enif_make_list_from_array(env, results.data(), static_cast<unsigned>(results.size())));
}
} // namespace glz
#else // !GLAZER_HAVE_JQ
namespace glz {
inline ERL_NIF_TERM jq_query(ErlNifEnv* env, const ErlNifBinary&, const ErlNifBinary&, const JSONDecodeOpts&)
{
return enif_make_tuple2(env, AM_ERROR, AM_JQ_NOT_AVAILABLE);
}
} // namespace glz
#endif // GLAZER_HAVE_JQ