/*****************************************************************************\
 *  data_t parser plugin interface
 ******************************************************************************
 *  Copyright (C) SchedMD LLC.
 *
 *  This file is part of Slurm, a resource management program.
 *  For details, see <https://slurm.schedmd.com/>.
 *  Please also read the included file: DISCLAIMER.
 *
 *  Slurm is free software; you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  In addition, as a special exception, the copyright holders give permission
 *  to link the code of portions of this program with the OpenSSL library under
 *  certain conditions as described in each individual source file, and
 *  distribute linked combinations including the two. You must obey the GNU
 *  General Public License in all respects for all of the code used other than
 *  OpenSSL. If you modify file(s) with this exception, you may extend this
 *  exception to your version of the file(s), but you are not obligated to do
 *  so. If you do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source files in
 *  the program, then also delete it here.
 *
 *  Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
 *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with Slurm; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
\*****************************************************************************/

#include "config.h"

#include "src/common/data.h"
#include "src/common/fd.h"
#include "src/common/list.h"
#include "src/common/log.h"
#include "src/common/openapi.h"
#include "src/common/read_config.h"
#include "src/common/timers.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"

#include "src/interfaces/data_parser.h"
#include "src/interfaces/serializer.h"

#define PARSE_MAJOR_TYPE "data_parser"
#define PARSE_MAGIC 0x0ea0b1be

struct data_parser_s {
	int magic;
	int plugin_offset;
	/* arg returned by plugin init() */
	void *arg;
	const char *plugin_type; /* ptr to plugin plugin_type - do not xfree */
	char *params; /* parameters from _new - must xfree */
	char *plugin_string; /* plugin_type+params - must xfree */
};

typedef struct {
	int (*parse)(void *arg, data_parser_type_t type, void *dst,
		     ssize_t dst_bytes, data_t *src, data_t *parent_path);
	int (*dump)(void *arg, data_parser_type_t type, void *src,
		    ssize_t src_bytes, data_t *dst);
	/* ptr returned to be handed to commands as arg */
	void *(*new)(data_parser_on_error_t on_parse_error,
		     data_parser_on_error_t on_dump_error,
		     data_parser_on_error_t on_query_error, void *error_arg,
		     data_parser_on_warn_t on_parse_warn,
		     data_parser_on_warn_t on_dump_warn,
		     data_parser_on_warn_t on_query_warn, void *warn,
		     char *params);
	void (*free)(void *arg);
	int (*assign)(void *arg, data_parser_attr_type_t type, void *obj);
	int (*specify)(void *arg, data_t *dst);
	openapi_type_t (*resolve_openapi_type)(void *arg,
					       data_parser_type_t type,
					       const char *field);
	const char *(*resolve_type_str)(void *arg, data_parser_type_t type);
	int (*inc_ref)(void *arg, data_parser_type_t type,
		       void **references_ptr);
	int (*populate_schema)(void *arg, data_parser_type_t type,
		       void **references_ptr, data_t *dst, data_t *schemas);
	int (*populate_parameters)(void *arg, data_parser_type_t parameter_type,
				   data_parser_type_t query_type,
				   void **references_ptr, data_t *dst,
				   data_t *schemas);
	void (*release_refs)(void *arg, void **references_ptr);
} parse_funcs_t;

typedef struct {
	char *plugin_type;
	char *params;
} plugin_param_t;

/*
 * Must be synchronized with parse_funcs_t above.
 */
static const char *parse_syms[] = {
	"data_parser_p_parse",
	"data_parser_p_dump",
	"data_parser_p_new",
	"data_parser_p_free",
	"data_parser_p_assign",
	"data_parser_p_specify",
	"data_parser_p_resolve_openapi_type",
	"data_parser_p_resolve_type_string",
	"data_parser_p_increment_reference",
	"data_parser_p_populate_schema",
	"data_parser_p_populate_parameters",
	"data_parser_p_release_references",
};

static plugins_t *plugins = NULL;
static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
static int active_parsers = 0;

static const char *_get_plugin_version(const char *plugin_type);

extern int data_parser_g_parse(data_parser_t *parser, data_parser_type_t type,
			       void *dst, ssize_t dst_bytes, data_t *src,
			       data_t *parent_path)
{
	const parse_funcs_t *funcs;
	DEF_TIMERS;
	int rc;

	if (!parser)
		return ESLURM_DATA_INVALID_PARSER;

	funcs = plugins->functions[parser->plugin_offset];

	if (!src || (data_get_type(src) == DATA_TYPE_NONE))
		return ESLURM_DATA_PARSE_NOTHING;

	xassert(type > DATA_PARSER_TYPE_INVALID);
	xassert(type < DATA_PARSER_TYPE_MAX);
	xassert(parser->magic == PARSE_MAGIC);
	xassert(data_get_type(parent_path) == DATA_TYPE_LIST);
	xassert(plugins && (plugins->magic == PLUGINS_MAGIC));
	xassert(parser->plugin_offset < plugins->count);
	xassert(plugins->functions[parser->plugin_offset]);

	START_TIMER;
	rc = funcs->parse(parser->arg, type, dst, dst_bytes, src, parent_path);
	END_TIMER2(__func__);

	return rc;
}

extern int data_parser_g_dump(data_parser_t *parser, data_parser_type_t type,
			      void *src, ssize_t src_bytes, data_t *dst)
{
	DEF_TIMERS;
	int rc;
	const parse_funcs_t *funcs;

	if (!parser)
		return ESLURM_DATA_INVALID_PARSER;

	funcs = plugins->functions[parser->plugin_offset];

	xassert(data_get_type(dst));
	xassert(type > DATA_PARSER_TYPE_INVALID);
	xassert(type < DATA_PARSER_TYPE_MAX);
	xassert(parser->magic == PARSE_MAGIC);
	xassert(plugins && (plugins->magic == PLUGINS_MAGIC));
	xassert(parser->plugin_offset < plugins->count);
	xassert(plugins->functions[parser->plugin_offset]);

	START_TIMER;
	rc = funcs->dump(parser->arg, type, src, src_bytes, dst);
	END_TIMER2(__func__);

	return rc;
}

/* takes ownership of params */
static data_parser_t *_new_parser(data_parser_on_error_t on_parse_error,
				  data_parser_on_error_t on_dump_error,
				  data_parser_on_error_t on_query_error,
				  void *error_arg,
				  data_parser_on_warn_t on_parse_warn,
				  data_parser_on_warn_t on_dump_warn,
				  data_parser_on_warn_t on_query_warn,
				  void *warn_arg, int plugin_index,
				  char *params)
{
	DEF_TIMERS;
	const parse_funcs_t *funcs;
	data_parser_t *parser = xmalloc(sizeof(*parser));

	parser->magic = PARSE_MAGIC;

	parser->plugin_offset = plugin_index;
	parser->plugin_type = plugins->types[plugin_index];
	parser->params = params;

	START_TIMER;
	funcs = plugins->functions[plugin_index];
	parser->arg = funcs->new(on_parse_error, on_dump_error, on_query_error,
				 error_arg, on_parse_warn, on_dump_warn,
				 on_query_warn, warn_arg, params);
	END_TIMER2(__func__);

	slurm_mutex_lock(&init_mutex);
	xassert(active_parsers >= 0);
	active_parsers++;
	slurm_mutex_unlock(&init_mutex);

	return parser;
}

static plugin_param_t *_parse_plugin_type(const char *plugin_type)
{
	char *type, *last = NULL, *pl;
	plugin_param_t *pparams = NULL;
	int count = 0;

	if (!plugin_type)
		return NULL;

	pl = xstrdup(plugin_type);
	type = strtok_r(pl, ",", &last);
	while (type) {
		char *pl;
		plugin_param_t *p;

		xrecalloc(pparams, (count + 2), sizeof(*pparams));

		p = &pparams[count];

		if ((pl = xstrstr(type,
				  SLURM_DATA_PARSER_PLUGIN_PARAMS_CHAR))) {
			p->plugin_type = xstrndup(type, (pl - type));
			p->params = xstrdup(pl);
		} else {
			p->plugin_type = xstrdup(type);
		}

		log_flag(DATA, "%s: plugin=%s params=%s",
		       __func__, p->plugin_type, p->params);

		count++;

		type = strtok_r(NULL, ",", &last);
	}

	xfree(pl);
	return pparams;
}

static int _load_plugins(plugin_param_t *pparams, plugrack_foreach_t listf,
			 bool skip_loading)
{
	int rc = SLURM_SUCCESS;

	if (skip_loading)
		return rc;

	slurm_mutex_lock(&init_mutex);

	if ((rc = serializer_g_init(MIME_TYPE_JSON_PLUGIN, NULL)))
		fatal("JSON plugin loading failed: %s", slurm_strerror(rc));

	xassert(sizeof(parse_funcs_t) ==
		(sizeof(void *) * ARRAY_SIZE(parse_syms)));

	if (!pparams) {
		rc = load_plugins(&plugins, PARSE_MAJOR_TYPE, NULL, listf,
				  parse_syms, ARRAY_SIZE(parse_syms));
	} else {
		for (int i = 0; !rc && pparams[i].plugin_type; i++)
			rc = load_plugins(&plugins, PARSE_MAJOR_TYPE,
					  pparams[i].plugin_type, listf,
					  parse_syms, ARRAY_SIZE(parse_syms));
	}

	xassert(rc || plugins);

	slurm_mutex_unlock(&init_mutex);

	return rc;
}

static int _find_plugin_by_type(const char *plugin_type)
{
	if (!plugin_type)
		return -1;

	/* quick match by pointer address */
	for (int i = 0; i < plugins->count; i++) {
		if (plugin_type == plugins->types[i])
			return i;
	}

	/* match by full string */
	for (int i = 0; i < plugins->count; i++) {
		if (!xstrcasecmp(plugin_type, plugins->types[i]))
			return i;
	}

	/* match by string without "data_parser/" */
	for (int i = 0; i < plugins->count; i++) {
		if (!xstrcasecmp(plugin_type,
				 _get_plugin_version(plugins->types[i])))
			return i;
	}

	return -1;
}

extern data_parser_t *data_parser_g_new(data_parser_on_error_t on_parse_error,
					data_parser_on_error_t on_dump_error,
					data_parser_on_error_t on_query_error,
					void *error_arg,
					data_parser_on_warn_t on_parse_warn,
					data_parser_on_warn_t on_dump_warn,
					data_parser_on_warn_t on_query_warn,
					void *warn_arg, const char *plugin_type,
					plugrack_foreach_t listf,
					bool skip_loading)
{
	int rc, index;
	char *params = NULL;
	data_parser_t *parser = NULL;
	plugin_param_t *pparams;

	if (!xstrcasecmp(plugin_type, "list")) {
		xassert(listf);
		load_plugins(&plugins, PARSE_MAJOR_TYPE, plugin_type, listf,
			     parse_syms, ARRAY_SIZE(parse_syms));
		return NULL;
	}

	pparams = _parse_plugin_type(plugin_type);

	if (!pparams || !pparams[0].plugin_type) {
		error("%s: invalid plugin %s", __func__, plugin_type);
		goto cleanup;
	}

	if (pparams[1].plugin_type) {
		error("%s: rejecting ambiguous plugin %s",
		      __func__, plugin_type);
		goto cleanup;
	}

	if ((rc = _load_plugins(pparams, listf, skip_loading))) {
		error("%s: failure loading plugins: %s",
		      __func__, slurm_strerror(rc));
		goto cleanup;
	}

	if ((index = _find_plugin_by_type(pparams[0].plugin_type)) < 0) {
		error("%s: unable to find plugin %s", __func__,
		      pparams[0].plugin_type);
		goto cleanup;
	}

	SWAP(params, pparams[0].params);

	parser = _new_parser(on_parse_error, on_dump_error, on_query_error,
			     error_arg, on_parse_warn, on_dump_warn,
			     on_query_warn, warn_arg, index, params);
cleanup:
	if (pparams) {
		for (int i = 0; pparams[i].plugin_type; i++) {
			xfree(pparams[i].plugin_type);
			xfree(pparams[i].params);
		}
		xfree(pparams);
	}

	return parser;
}

extern data_parser_t **data_parser_g_new_array(
	data_parser_on_error_t on_parse_error,
	data_parser_on_error_t on_dump_error,
	data_parser_on_error_t on_query_error,
	void *error_arg,
	data_parser_on_warn_t on_parse_warn,
	data_parser_on_warn_t on_dump_warn,
	data_parser_on_warn_t on_query_warn,
	void *warn_arg,
	const char *plugin_type,
	plugrack_foreach_t listf,
	bool skip_loading)
{
	int rc, i = 0;
	data_parser_t **parsers = NULL;
	plugin_param_t *pparams;

	if (!xstrcasecmp(plugin_type, "list")) {
		xassert(listf);
		load_plugins(&plugins, PARSE_MAJOR_TYPE, plugin_type, listf,
			     parse_syms, ARRAY_SIZE(parse_syms));
		return NULL;
	}

	pparams = _parse_plugin_type(plugin_type);

	if ((rc = _load_plugins(pparams, listf, skip_loading))) {
		error("%s: failure loading plugins: %s",
		      __func__, slurm_strerror(rc));
		goto cleanup;
	}

	/* always allocate for all possible plugins */
	parsers = xcalloc((plugins->count + 1), sizeof(*parsers));

	if (pparams) {
		for (; pparams[i].plugin_type; i++) {
			int index =
				_find_plugin_by_type(pparams[i].plugin_type);

			if (index < 0) {
				error("%s: unable to find plugin %s",
				      __func__, pparams[i].plugin_type);
				goto cleanup;
			}

			parsers[i] = _new_parser(on_parse_error, on_dump_error,
						 on_query_error, error_arg,
						 on_parse_warn, on_dump_warn,
						 on_query_warn, warn_arg, index,
						 pparams[i].params);

			pparams[i].params = NULL;
			xfree(pparams[i].plugin_type);
		}
	} else {
		for (; i < plugins->count; i++) {
			parsers[i] = _new_parser(on_parse_error, on_dump_error,
						 on_query_error, error_arg,
						 on_parse_warn, on_dump_warn,
						 on_query_warn, warn_arg, i,
						 NULL);
		}
	}

	xfree(pparams);

	return parsers;
cleanup:
	if (pparams) {
		for (; pparams[i].plugin_type; i++) {
			xfree(pparams[i].plugin_type);
			xfree(pparams[i].params);
		}
		xfree(pparams);
	}

	if (plugins)
		for (int j = 0; j < plugins->count; j++)
			FREE_NULL_DATA_PARSER(parsers[j]);
	xfree(parsers);

	return NULL;
}

extern const char *data_parser_get_plugin(data_parser_t *parser)
{
	if (!parser)
		return NULL;

	xassert(parser->magic == PARSE_MAGIC);

	/*
	 * Generate string as requested using full plugin type where the
	 * original request may not having included data_parser/
	 */
	if (!parser->plugin_string)
		xstrfmtcat(parser->plugin_string, "%s%s", parser->plugin_type,
			   (parser->params ? parser->params : ""));

	return parser->plugin_string;
}

static const char *_get_plugin_version(const char *plugin_type)
{
	static const char prefix[] = PARSE_MAJOR_TYPE "/";

#ifndef NDEBUG
	/* catch if the prefix ever changes in an unexpected way */
	const char *match;
	xassert(plugin_type);
	match = xstrstr(plugin_type, prefix);
	xassert(match);
#endif

	return plugin_type + strlen(prefix);
}

extern const char *data_parser_get_plugin_version(data_parser_t *parser)
{
	xassert(!parser || parser->magic == PARSE_MAGIC);

	if (!parser)
		return NULL;

	return _get_plugin_version(parser->plugin_type);
}

extern const char *data_parser_get_plugin_params(data_parser_t *parser)
{
	xassert(!parser || parser->magic == PARSE_MAGIC);

	if (!parser)
		return NULL;

	return parser->params;
}

extern void data_parser_g_free(data_parser_t *parser, bool skip_unloading)
{
	DEF_TIMERS;
	const parse_funcs_t *funcs;

	if (!parser)
		return;

	funcs = plugins->functions[parser->plugin_offset];

	if (plugins) {
		xassert(plugins->magic == PLUGINS_MAGIC);
		xassert(plugins->functions[parser->plugin_offset]);
		xassert(parser->magic == PARSE_MAGIC);
		xassert(parser->plugin_offset < plugins->count);
	}

	START_TIMER;
	if (plugins)
		funcs->free(parser->arg);
	END_TIMER2(__func__);

	xfree(parser->params);
	xfree(parser->plugin_string);
	parser->arg = NULL;
	parser->plugin_offset = -1;
	parser->magic = ~PARSE_MAGIC;
	xfree(parser);

	slurm_mutex_lock(&init_mutex);
	xassert(active_parsers >= 0);
	active_parsers--;
	xassert(active_parsers >= 0);

	if (!skip_unloading && !active_parsers)
		FREE_NULL_PLUGINS(plugins);
	slurm_mutex_unlock(&init_mutex);
}

extern void data_parser_g_array_free(data_parser_t **ptr, bool skip_unloading)
{
	if (!ptr)
		return;

	for (int i = 0; ptr[i]; i++)
		data_parser_g_free(ptr[i], skip_unloading);

	xfree(ptr);
}

extern int data_parser_g_assign(data_parser_t *parser,
				data_parser_attr_type_t type, void *obj)
{
	int rc;
	DEF_TIMERS;
	const parse_funcs_t *funcs;

	if (!parser)
		return ESLURM_DATA_INVALID_PARSER;

	funcs = plugins->functions[parser->plugin_offset];

	xassert(parser);
	xassert(plugins);
	xassert(parser->magic == PARSE_MAGIC);
	xassert(parser->plugin_offset < plugins->count);
	xassert(type > DATA_PARSER_ATTR_INVALID);
	xassert(type < DATA_PARSER_ATTR_MAX);

	START_TIMER;
	rc = funcs->assign(parser->arg, type, obj);
	END_TIMER2(__func__);

	return rc;
}

extern openapi_resp_meta_t *data_parser_cli_meta(int argc, char **argv,
						 const char *mime_type,
						 const char *data_parser)
{
	openapi_resp_meta_t *meta = xmalloc_nz(sizeof(*meta));
	int tty;
	char *parser = NULL;
	char **argvnt = NULL;

	/* need a new array with a NULL terminator */
	if (argc > 0) {
		argvnt = xcalloc(argc, sizeof(*argv));
		memcpy(argvnt, argv, (sizeof(*argv) * (argc - 1)));
	}

	if (isatty(STDIN_FILENO))
		tty = STDIN_FILENO;
	else if (isatty(STDOUT_FILENO))
		tty = STDOUT_FILENO;
	else if (isatty(STDERR_FILENO))
		tty = STDERR_FILENO;
	else
		tty = -1;

	if (data_parser)
		parser = xstrdup(data_parser);

	*meta = (openapi_resp_meta_t) {
		.plugin = {
			.data_parser = parser,
			.accounting_storage =
				slurm_conf.accounting_storage_type,
		},
		.command = argvnt,
		.client = {
			.source = ((tty != -1) ? fd_resolve_path(tty) :
				   NULL),
			.uid = getuid(),
			.gid = getgid(),
		},
		.slurm = {
			.version = {
				.major = xstrdup(SLURM_MAJOR),
				.micro = xstrdup(SLURM_MICRO),
				.minor = xstrdup(SLURM_MINOR),
			},
			.release = xstrdup(SLURM_VERSION_STRING),
			.cluster = xstrdup(slurm_conf.cluster_name),
		}
	};

	return meta;
}

static void _plugrack_foreach_list(const char *full_type, const char *fq_path,
				   const plugin_handle_t id, void *arg)
{
	info("%s", full_type);
}

static bool _on_error(void *arg, data_parser_type_t type, int error_code,
		      const char *source, const char *why, ...)
{
	va_list ap;
	char *str;
	data_parser_dump_cli_ctxt_t *ctxt = arg;
	openapi_resp_error_t *e = NULL;

	if (ctxt) {
		xassert(ctxt->magic == DATA_PARSER_DUMP_CLI_CTXT_MAGIC);
		xassert(ctxt->errors);

		if (!ctxt->errors)
			return false;

		e = xmalloc(sizeof(*e));
	}

	va_start(ap, why);
	str = vxstrfmt(why, ap);
	va_end(ap);

	if (str) {
		error("%s: parser=%s rc[%d]=%s -> %s",
		      (source ? source : __func__),
		      (!ctxt ? "DEFAULT" : ctxt->data_parser),
		      error_code, slurm_strerror(error_code), str);

		if (e)
			e->description = str;
	}

	if (error_code) {
		if (e)
			e->num = error_code;

		if (ctxt && !ctxt->rc)
			ctxt->rc = error_code;
	}

	if (source && ctxt)
		e->source = xstrdup(source);

	if (ctxt)
		list_append(ctxt->errors, e);

	return false;
}

static void _on_warn(void *arg, data_parser_type_t type, const char *source,
		     const char *why, ...)
{
	va_list ap;
	char *str;
	data_parser_dump_cli_ctxt_t *ctxt = arg;
	openapi_resp_warning_t *w = NULL;

	if (ctxt) {
		xassert(ctxt->magic == DATA_PARSER_DUMP_CLI_CTXT_MAGIC);
		xassert(ctxt->warnings);

		if (!ctxt->warnings)
			return;

		w = xmalloc(sizeof(*w));
	}

	va_start(ap, why);
	str = vxstrfmt(why, ap);
	va_end(ap);

	if (str) {
		debug("%s: parser=%s WARNING: %s",
		      (source ? source : __func__),
		      (!ctxt ? "DEFAULT" : ctxt->data_parser), str);

		if (ctxt)
			w->description = str;
	}

	if (source && ctxt)
		w->source = xstrdup(source);

	if (ctxt)
		list_append(ctxt->warnings, w);
}

extern int data_parser_dump_cli_stdout(data_parser_type_t type, void *obj,
				       int obj_bytes, void *acct_db_conn,
				       const char *mime_type,
				       const char *data_parser,
				       data_parser_dump_cli_ctxt_t *ctxt,
				       openapi_resp_meta_t *meta)
{
	int rc = SLURM_SUCCESS;
	data_t *dresp = NULL;
	data_parser_t *parser;
	char *out = NULL;

	if (!xstrcasecmp(data_parser, "list")) {
		info("Possible data_parser plugins:");
		parser = data_parser_g_new(NULL, NULL, NULL, NULL, NULL, NULL,
					   NULL, NULL, "list",
					   _plugrack_foreach_list, false);
		FREE_NULL_DATA_PARSER(parser);
		return SLURM_SUCCESS;
	}

	if (!(parser = data_parser_cli_parser(data_parser, ctxt))) {
		rc = ESLURM_DATA_INVALID_PARSER;
		error("%s output not supported by %s",
		      mime_type, SLURM_DATA_PARSER_VERSION);
		goto cleanup;
	}

	if (acct_db_conn)
		data_parser_g_assign(parser, DATA_PARSER_ATTR_DBCONN_PTR,
				     acct_db_conn);

	if (!meta->plugin.data_parser)
		meta->plugin.data_parser =
			xstrdup(data_parser_get_plugin(parser));

	dresp = data_new();

	if (!data_parser_g_dump(parser, type, obj, obj_bytes, dresp) &&
	    (data_get_type(dresp) != DATA_TYPE_NULL)) {
		serialize_g_data_to_string(&out, NULL, dresp, mime_type,
					   SER_FLAGS_PRETTY);
	}

	if (out && out[0])
		printf("%s\n", out);
	else
		debug("No output generated");

cleanup:
	xfree(out);
	FREE_NULL_DATA(dresp);
	FREE_NULL_DATA_PARSER(parser);

	return rc;
}

/*
 * Dump object using data_parser/v0.0.39.
 * Dump meta data and errors/warnings with SLURM_DATA_PARSER_VERSION
 */
extern int data_parser_dump_cli_stdout_v39(data_parser_type_t type, void *obj,
					   int obj_bytes, const char *key,
					   void *acct_db_conn,
					   const char *mime_type,
					   data_parser_dump_cli_ctxt_t *ctxt,
					   openapi_resp_meta_t *meta)
{
	data_t *dresp = data_set_dict(data_new());
	data_t *dout = data_key_set(dresp, key);
	data_t *dmeta = data_key_set(dresp, "meta");
	data_t *dwarning = data_key_set(dresp, "warnings");
	data_t *derror = data_key_set(dresp, "errors");
	data_parser_t *parser = NULL;
	data_parser_t *meta_parser = NULL;
	char *out = NULL;
	int rc = SLURM_SUCCESS;

	if (!(parser = data_parser_cli_parser(
			SLURM_DATA_PARSER_VERSION_DEPRECATED, ctxt))) {
		rc = ESLURM_DATA_INVALID_PARSER;
		error("%s output not supported by %s",
		      mime_type, SLURM_DATA_PARSER_VERSION);
		goto cleanup;
	}

	if (!(meta_parser = data_parser_cli_parser(SLURM_DATA_PARSER_VERSION,
						   ctxt))) {
		rc = ESLURM_DATA_INVALID_PARSER;
		error("%s output not supported by %s",
		      mime_type, SLURM_DATA_PARSER_VERSION);
		goto cleanup;
	}

	if (acct_db_conn)
		data_parser_g_assign(parser, DATA_PARSER_ATTR_DBCONN_PTR,
				     acct_db_conn);

	if (!meta->plugin.data_parser)
		meta->plugin.data_parser =
			xstrdup(data_parser_get_plugin(parser));

	if ((!data_parser_g_dump(parser, type, obj, obj_bytes, dout) &&
	    (data_get_type(dout) != DATA_TYPE_NULL)) &&
	    (!data_parser_g_dump(meta_parser, DATA_PARSER_OPENAPI_META, meta,
				 sizeof(*meta), dmeta) &&
	    (data_get_type(dmeta) != DATA_TYPE_NULL)) &&
	    (!data_parser_g_dump(meta_parser, DATA_PARSER_OPENAPI_ERRORS,
				 &ctxt->errors, sizeof(ctxt->errors), derror) &&
	    (data_get_type(derror) != DATA_TYPE_NULL)) &&
	    (!data_parser_g_dump(meta_parser, DATA_PARSER_OPENAPI_WARNINGS,
				 &ctxt->warnings, sizeof(ctxt->warnings),
				 dwarning) &&
	    (data_get_type(dwarning) != DATA_TYPE_NULL))) {
		serialize_g_data_to_string(&out, NULL, dresp, mime_type,
					   SER_FLAGS_PRETTY);
	}

	if (out && out[0])
		printf("%s\n", out);
	else
		debug("No output generated");

cleanup:
	xfree(out);
	FREE_NULL_DATA(dresp);
	FREE_NULL_DATA_PARSER(parser);
	FREE_NULL_DATA_PARSER(meta_parser);

	return rc;
}

extern bool is_data_parser_deprecated(const char *data_parser)
{
	char *full_name = "data_parser/" SLURM_DATA_PARSER_VERSION_DEPRECATED;
	return !xstrcmp(data_parser, SLURM_DATA_PARSER_VERSION_DEPRECATED) ||
		!xstrcmp(data_parser, full_name);
}

extern int data_parser_g_specify(data_parser_t *parser, data_t *dst)
{
	int rc;
	DEF_TIMERS;
	const parse_funcs_t *funcs;

	if (!parser)
		return ESLURM_DATA_INVALID_PARSER;

	funcs = plugins->functions[parser->plugin_offset];

	xassert(parser);
	xassert(plugins);
	xassert(parser->magic == PARSE_MAGIC);
	xassert(parser->plugin_offset < plugins->count);

	START_TIMER;
	rc = funcs->specify(parser->arg, dst);
	END_TIMER2(__func__);

	return rc;
}

extern data_parser_t *data_parser_cli_parser(const char *data_parser, void *arg)
{
	return data_parser_g_new(_on_error, _on_error, _on_error, arg, _on_warn,
				 _on_warn, _on_warn, arg,
				 (data_parser ?  data_parser :
				  SLURM_DATA_PARSER_VERSION), NULL, false);
}

extern openapi_type_t data_parser_g_resolve_openapi_type(
	data_parser_t *parser,
	data_parser_type_t type,
	const char *field)
{
	const parse_funcs_t *funcs;

	if (!parser)
		return OPENAPI_TYPE_INVALID;

	funcs = plugins->functions[parser->plugin_offset];

	xassert(parser);
	xassert(plugins);
	xassert(parser->magic == PARSE_MAGIC);
	xassert(parser->plugin_offset < plugins->count);

	return funcs->resolve_openapi_type(parser->arg, type, field);
}

extern const char *data_parser_g_resolve_type_string(data_parser_t *parser,
						     data_parser_type_t type)
{
	const parse_funcs_t *funcs;

	if (!parser)
		return NULL;

	funcs = plugins->functions[parser->plugin_offset];

	xassert(plugins);
	xassert(parser->magic == PARSE_MAGIC);
	xassert(parser->plugin_offset < plugins->count);

	return funcs->resolve_type_str(parser->arg, type);
}

extern int data_parser_g_increment_reference(data_parser_t *parser,
					     data_parser_type_t type,
					     void **references_ptr)
{
	const parse_funcs_t *funcs;

	if (!parser)
		return EINVAL;

	funcs = plugins->functions[parser->plugin_offset];

	xassert(parser->magic == PARSE_MAGIC);
	xassert(parser->plugin_offset < plugins->count);

	return funcs->inc_ref(parser->arg, type, references_ptr);
}

extern int data_parser_g_populate_schema(data_parser_t *parser,
					 data_parser_type_t type,
					 void **references_ptr, data_t *dst,
					 data_t *schemas)
{
	const parse_funcs_t *funcs;

	if (!parser)
		return EINVAL;

	funcs = plugins->functions[parser->plugin_offset];

	xassert(parser->magic == PARSE_MAGIC);
	xassert(parser->plugin_offset < plugins->count);

	return funcs->populate_schema(parser->arg, type, references_ptr, dst,
				      schemas);
}

extern int data_parser_g_populate_parameters(data_parser_t *parser,
					     data_parser_type_t parameter_type,
					     data_parser_type_t query_type,
					     void **references_ptr, data_t *dst,
					     data_t *schemas)
{
	const parse_funcs_t *funcs;

	if (!parser)
		return EINVAL;

	funcs = plugins->functions[parser->plugin_offset];

	xassert(parser->magic == PARSE_MAGIC);
	xassert(parser->plugin_offset < plugins->count);

	return funcs->populate_parameters(parser->arg, parameter_type,
					  query_type, references_ptr, dst,
					  schemas);
}

extern void data_parser_g_release_references(data_parser_t *parser,
					     void **references_ptr)
{
	const parse_funcs_t *funcs;

	if (!parser)
		return;

	funcs = plugins->functions[parser->plugin_offset];

	xassert(parser->magic == PARSE_MAGIC);
	xassert(parser->plugin_offset < plugins->count);

	return funcs->release_refs(parser->arg, references_ptr);
}
