// 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