/*****************************************************************************\
 *  openapi.c - OpenAPI plugin handler
 *****************************************************************************
 *  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 "slurm/slurm_errno.h"
#include "src/common/data.h"
#include "src/common/http.h"
#include "src/common/plugrack.h"
#include "src/common/read_config.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"

#include "src/interfaces/serializer.h"
#include "src/slurmrestd/openapi.h"
#include "src/slurmrestd/operations.h"

#define OPENAPI_MAJOR_TYPE "openapi"
#define MAX_URL_PATH_DEPTH 1024

typedef struct {
	int (*init)(void);
	int (*fini)(void);
	int (*get_paths)(const openapi_path_binding_t **paths_ptr,
			 const openapi_resp_meta_t **meta_ptr);
} funcs_t;

/*
 * Must be synchronized with funcs_t above.
 */
static const char *syms[] = {
	"slurm_openapi_p_init",
	"slurm_openapi_p_fini",
	"slurm_openapi_p_get_paths",
};

typedef enum {
	OPENAPI_PATH_ENTRY_UNKNOWN = 0,
	OPENAPI_PATH_ENTRY_MATCH_STRING,
	OPENAPI_PATH_ENTRY_MATCH_PARAMETER,
	OPENAPI_PATH_ENTRY_MAX
} entry_type_t;

/*
 * This is a simplified entry since OAS allows combos of
 * parameters but we will only honor having a single parameter
 * as an dir entry for now
 */
typedef struct {
	char *entry;
	entry_type_t type;
	openapi_type_t parameter;
} entry_t;

typedef struct {
	const openapi_path_binding_method_t *bound;
	entry_t *entries;
	http_request_method_t method;
} entry_method_t;

#define MAGIC_PATH 0x0a0b09fd
typedef struct {
	int magic; /* MAGIC_PATH */
	char *path; /* path as string */
	const openapi_path_binding_t *bound;
	data_parser_t *parser;
	entry_method_t *methods;
	int tag;
} path_t;

typedef struct {
	const data_t *dpath;
	path_t *path;
	data_t *params;
	http_request_method_t method;
	entry_t *entry;
	int tag;
} match_path_from_data_t;

#define MAGIC_MERGE_PATH 0x22b2ae44
typedef struct {
	int magic; /* MAGIC_MERGE_PATH */
	data_t *paths;
	data_t *server_path;
} merge_path_t;

#define MAGIC_MERGE_ID_PATH 0x22b2aeae
typedef struct {
	int magic; /* MAGIC_MERGE_ID_PATH */
	data_t *server_path;
	char *operation;
	char *at;
	char *path;
	merge_path_t *merge_args;
} id_merge_path_t;

#define MAGIC_OAS 0x1218eeee
typedef struct {
	int magic; /* MAGIC_OAS */
	const char **mime_types;
	data_t *spec;
	data_t *tags;
	data_t *paths;
	data_t *components;
	data_t *components_schemas;
	data_t *security_schemas;
	data_t *info;
	data_t *contact;
	data_t *license;
	data_t *servers;
	data_t *security;
	data_t *x_slurm;
	/* tracked references per data_parser */
	void **references;
} openapi_spec_t;

#define URL_PARSE_ARGS_MAGIC 0x0abfccaa

typedef struct {
	int magic; /* URL_PARSE_ARGS_MAGIC */
	int count;
	entry_t *entry;
} url_parse_args_t;

#define URL_PARSE_OPERATION_IDARGS_MAGIC 0xaaffec0e

typedef struct {
	int magic; /* URL_PARSE_OPERATION_IDARGS_MAGIC */
	data_t *dpath;
	data_t *last;
} url_parse_operation_id_args_t;

static list_t *paths = NULL;
static int path_tag_counter = 0;
static plugins_t *plugins = NULL;
static data_parser_t **parsers = NULL; /* symlink to parser array */

static const struct {
	char *openapi_version;
	struct {
		char *title;
		char *desc;
		char *tos;
		struct {
			char *name;
			char *url;
			char *email;
		} contact;
		struct {
			char *name;
			char *url;
		} license;
	} info;
	struct {
		char *name;
		char *desc;
	} tags[4];
	struct {
		char *url;
	} servers[1];
	/* security is too complex for here */
	struct {
		struct security_scheme_s {
			char *key;
			char *type;
			char *desc;
			char *name;
			char *in;
			char *scheme;
			char *bearer_format;
		} security_schemes[3];
	} components;
} openapi_spec = {
	.openapi_version = "3.0.3",
	.info = {
		.title = "Slurm REST API",
		.desc = "API to access and control Slurm",
		.tos = "https://github.com/SchedMD/slurm/blob/master/DISCLAIMER",
		.contact = {
			.name = "SchedMD LLC",
			.url = "https://www.schedmd.com/",
			.email = "sales@schedmd.com",
		},
		.license = {
			.name = "Apache 2.0",
			.url = "https://www.apache.org/licenses/LICENSE-2.0.html",
		},
	},
	.tags = {
		{
			.name = "slurm",
			.desc = "methods that query slurmctld",
		},
		{
			.name = "slurmdb",
			.desc = "methods that query slurmdbd",
		},
		{
			.name = "util",
			.desc = "utilities available directly through slurmrestd",
		},
		{
			.name = "openapi",
			.desc = "methods that query for generated OpenAPI specifications",
		},
	},
	.servers = {
		{
			.url = "/",
		},
	},
	.components = {
		.security_schemes = {
			{
#define SEC_SCHEME_USER_INDEX 0
				.key = "user",
				.type = "apiKey",
				.desc = "User name",
				.name = "X-SLURM-USER-NAME",
				.in = "header",
			},
			{
#define SEC_SCHEME_TOKEN_INDEX 1
				.key = "token",
				.type = "apiKey",
				.desc = "User access token",
				.name = "X-SLURM-USER-TOKEN",
				.in = "header",
			},
			{
#define SEC_SCHEME_BEARER_INDEX 2
				.key = "bearerAuth",
				.type = "http",
				.desc = "Bearer Authentication",
				.scheme = "bearer",
				.bearer_format = "JWT",
			},
		},
	},
};

static const openapi_path_binding_method_t openapi_methods[] = {
	{
		.method = HTTP_REQUEST_GET,
		.tags = (const char*[]) { "openapi", NULL },
		.summary = "Retrieve OpenAPI Specification",
		.response = {
			.type = DATA_PARSER_OPENAPI_SHARES_RESP,
			.description = "OpenAPI Specification",
		},
		.parameters = DATA_PARSER_SHARES_REQ_MSG,
	},
	{0}
};

/* health endpoints to match slurmctld and slurmd */
static const openapi_path_binding_method_t health_methods[] = {
	{
		.method = HTTP_REQUEST_GET,
		.tags = (const char *[]){"health", NULL},
		.summary = "Check slurmrestd health status",
		.response =
			{
				.type = DATA_PARSER_BOOL,
				.description = "HTTP status check if service is online",
			},
	},
	{ 0 }
};

static int _op_handler_openapi(openapi_ctxt_t *ctxt);
static int _op_handler_health(openapi_ctxt_t *ctxt);

#define OP_FLAGS (OPENAPI_BIND_HIDDEN_OAS | OPENAPI_BIND_NO_SLURMDBD)

/*
 * Paths to generate OpenAPI specification
 */
static const openapi_path_binding_t openapi_paths[] = {
	{
		.path = "/openapi.json",
		.callback = _op_handler_openapi,
		.methods = openapi_methods,
		.flags = OP_FLAGS,
	},
	{
		.path = "/openapi.yaml",
		.callback = _op_handler_openapi,
		.methods = openapi_methods,
		.flags = OP_FLAGS,
	},
	{
		.path = "/openapi",
		.callback = _op_handler_openapi,
		.methods = openapi_methods,
		.flags = OP_FLAGS,
	},
	{
		.path = "/openapi/v3",
		.callback = _op_handler_openapi,
		.methods = openapi_methods,
		.flags = OP_FLAGS,
	},
	{
		.path = "/healthz",
		.callback = _op_handler_health,
		.methods = health_methods,
		.flags = OP_FLAGS,
	},
	{
		.path = "/readyz",
		.callback = _op_handler_health,
		.methods = health_methods,
		.flags = OP_FLAGS,
	},
	{
		.path = "/livez",
		.callback = _op_handler_health,
		.methods = health_methods,
		.flags = OP_FLAGS,
	},
	{ 0 }
};

static const http_status_code_t *response_status_codes = NULL;
/*
 * Default to "default" and 200 as openapi generator breaks with only "default"
 * response code.
 */
static const http_status_code_t default_response_status_codes[] = {
	HTTP_STATUS_CODE_SUCCESS_OK,
	HTTP_STATUS_CODE_DEFAULT,
	HTTP_STATUS_CODE_INVALID,
};

static char *_entry_to_string(entry_t *entry);

static const char *_get_entry_type_string(entry_type_t type)
{
	switch (type) {
	case OPENAPI_PATH_ENTRY_MATCH_STRING:
		return "string";
	case OPENAPI_PATH_ENTRY_MATCH_PARAMETER:
		return "parameter";
	default:
		return "invalid";
	}
}

static int _resolve_parser_index(data_parser_t *parser)
{
	for (int i = 0; parsers[i]; i++)
		if (parsers[i] == parser)
			return i;

	fatal_abort("Unable to find parser. This should never happen!");
}

static void _free_entry_list(entry_t *entry, int tag,
			     entry_method_t *method)
{
	entry_t *itr = entry;

	if (!entry)
		return;

	while (itr->type) {
		debug5("%s: remove path tag:%d method:%s entry:%s",
		       __func__, tag, (method ?
				       get_http_method_string(method->method) :
				       "N/A"), itr->entry);
		xfree(itr->entry);
		itr++;
	}

	xfree(entry);
}

static void _list_delete_path_t(void *x)
{
	entry_method_t *em;

	if (!x)
		return;

	path_t *path = x;
	xassert(path->magic == MAGIC_PATH);
	xassert(path->tag != -1);
	em = path->methods;

	while (em->entries) {
		debug5("%s: remove path tag:%d method:%s", __func__, path->tag,
		       get_http_method_string(em->method));

		_free_entry_list(em->entries, path->tag, em);
		em->entries = NULL;
		em++;
	}

	xfree(path->methods);
	xfree(path->path);
	path->magic = ~MAGIC_PATH;
	xfree(path);
}

static int _on_parse_url_entry(const char *token, bool template, void *arg)
{
	url_parse_args_t *args = arg;
	entry_t *entry = args->entry;

	xassert(args->magic == URL_PARSE_ARGS_MAGIC);

	if (!args->entry) {
		args->count++;
		return SLURM_SUCCESS;
	}

	entry->entry = xstrdup(token);

	if (!xstrcmp(token, ".") || !xstrcmp(token, "..")) {
		/*
		 * there should not be a .. or . in a path
		 * definition, it just doesn't make any sense
		 */
		error("%s: invalid %s at entry", __func__, token);
		return ESLURM_URL_INVALID_PATH;
	}

	if (template) {
		entry->type = OPENAPI_PATH_ENTRY_MATCH_PARAMETER;
		debug5("%s: parameter %s at entry", __func__, token);
	} else { /* not a variable */
		entry->type = OPENAPI_PATH_ENTRY_MATCH_STRING;
		debug5("%s: string match entry %s", __func__, token);
	}

	args->entry++;
	return SLURM_SUCCESS;
}

static entry_t *_parse_openapi_path(const char *str_path, int *count_ptr)
{
	url_parse_args_t args = {
		.magic = URL_PARSE_ARGS_MAGIC,
	};
	entry_t *entries = NULL;

	if (url_path_walk(str_path, true, _on_parse_url_entry, &args))
		fatal_abort("Unable to parse path format: %s", str_path);

	xassert(args.count > 0);
	xassert(args.count < MAX_URL_PATH_DEPTH);

	args.entry = entries = xcalloc((args.count + 1), sizeof(*entries));

	if (url_path_walk(str_path, true, _on_parse_url_entry, &args))
		fatal_abort("Unable to parse path: %s", str_path);

	xassert(args.entry <= (entries + args.count));

	if (count_ptr)
		*count_ptr = args.count;

	return entries;
}

static int _print_path_tag_methods(void *x, void *arg)
{
	path_t *path = (path_t *) x;
	int *tag = (int *) arg;

	xassert(path->magic == MAGIC_PATH);

	if (path->tag != *tag)
		return 0;

	if (!path->methods->entries)
		debug4("%s: no methods found in path tag %d",
		       __func__, path->tag);

	for (entry_method_t *em = path->methods; em->entries; em++) {
		char *path_str = _entry_to_string(em->entries);

		debug4("%s: path tag %d entry: %s %s",
		       __func__, path->tag, get_http_method_string(em->method),
		       path_str);

		xfree(path_str);
	}

	/*
	 * We found the (unique) tag, so return -1 to exit early. The item's
	 * index returned by list_for_each_ro() will be negative.
	 */
	return -1;
}

extern void print_path_tag_methods(int tag)
{
	if (get_log_level() < LOG_LEVEL_DEBUG4)
		return;

	if (list_for_each_ro(paths, _print_path_tag_methods, &tag) >= 0)
		error("%s: Tag %d not found in paths", __func__, tag);
}

static void _clone_entries(entry_t **dst_ptr, entry_t *src, int count)
{
	entry_t *dst = xcalloc((count + 1), sizeof(*dst));

	xassert(!*dst_ptr);
	xassert(count > 0);

	*dst_ptr = dst;

	for (; src->type; src++, dst++) {
		dst->entry = xstrdup(src->entry);
		dst->type = src->type;
		dst->parameter = src->parameter;
	}
}

static void _check_openapi_path_binding(const openapi_path_binding_t *op_path)
{
#ifndef NDEBUG
	xassert(op_path->path);
	xassert(op_path->callback);
	xassert((op_path->flags == OPENAPI_BIND_NONE) ||
		((op_path->flags > OPENAPI_BIND_NONE) &&
		 (op_path->flags < OPENAPI_BIND_INVALID_MAX)));

	for (int i = 0;; i++) {
		const openapi_path_binding_method_t *method =
			&op_path->methods[i];

		if (method->method == HTTP_REQUEST_INVALID)
			break;

		xassert(method->summary && method->summary[0]);
		xassert(method->response.description &&
			method->response.description[0]);
		xassert(method->method > HTTP_REQUEST_INVALID);
		xassert(method->method < HTTP_REQUEST_INVALID_MAX);
		xassert(method->tags && method->tags[0]);
		xassert(method->response.type > DATA_PARSER_TYPE_INVALID);
		xassert(method->response.type < DATA_PARSER_TYPE_MAX);
		xassert(method->parameters >= DATA_PARSER_TYPE_INVALID);
		xassert(method->parameters < DATA_PARSER_TYPE_MAX);
		xassert(method->query >= DATA_PARSER_TYPE_INVALID);
		xassert(method->query < DATA_PARSER_TYPE_MAX);
		xassert(method->body.type >= DATA_PARSER_TYPE_INVALID);
		xassert(method->body.type < DATA_PARSER_TYPE_MAX);
	}
#endif /* !NDEBUG */
}

static bool _data_parser_supports_type(data_parser_t *parser,
				       data_parser_type_t type)
{
	openapi_type_t oapi_type;

	oapi_type = data_parser_g_resolve_openapi_type(parser, type, NULL);
	xassert(oapi_type >= OPENAPI_TYPE_INVALID);
	xassert(oapi_type < OPENAPI_TYPE_MAX);

	return (oapi_type != OPENAPI_TYPE_INVALID);
}

static bool _data_parser_supports_method(data_parser_t *parser,
					 const openapi_path_binding_method_t *m)
{
	/* check that parser supports each possible type if set */

	if ((m->response.type != DATA_PARSER_TYPE_INVALID) &&
	    !_data_parser_supports_type(parser, m->response.type))
		return false;

	if ((m->parameters != DATA_PARSER_TYPE_INVALID) &&
	    !_data_parser_supports_type(parser, m->parameters))
		return false;

	if ((m->query != DATA_PARSER_TYPE_INVALID) &&
	    !_data_parser_supports_type(parser, m->query))
		return false;

	if ((m->body.type != DATA_PARSER_TYPE_INVALID) &&
	    !_data_parser_supports_type(parser, m->body.type))
		return false;

	return true;
}

extern int register_path_binding(const char *in_path,
				 const openapi_path_binding_t *op_path,
				 const openapi_resp_meta_t *meta,
				 data_parser_t *parser, int *tag_ptr)
{
	entry_t *entries = NULL;
	int tag = -1, methods_count = 0, entries_count = 0;
	path_t *p = NULL;
	const char *path = (in_path ? in_path : op_path->path);

	debug4("%s: attempting to bind %s with %s",
	       __func__, (parser ? data_parser_get_plugin_version(parser) :
			  "data_parser/none"), path);

	xassert(!!in_path == !!(op_path->flags & OPENAPI_BIND_DATA_PARSER));
	_check_openapi_path_binding(op_path);

	if (!(entries = _parse_openapi_path(path, &entries_count)))
		fatal("%s: parse_openapi_path(%s) failed", __func__, path);

	for (int i = 0; op_path->methods[i].method != HTTP_REQUEST_INVALID;
	     i++) {
		if (!_data_parser_supports_method(parser, &op_path->methods[i]))
			continue;

		methods_count++;
	}

	if (!methods_count) {
		debug5("%s: skip binding %s with %s",
		       __func__, path, data_parser_get_plugin(parser));
		_free_entry_list(entries, -1, NULL);
		return ESLURM_NOT_SUPPORTED;
	}

	tag = path_tag_counter++;

	p = xmalloc(sizeof(*p));
	p->magic = MAGIC_PATH;
	p->methods = xcalloc((methods_count + 1), sizeof(*p->methods));
	p->tag = tag;
	p->bound = op_path;
	p->parser = parser;
	p->path = xstrdup(path);

	for (int i = 0, mi = 0;; i++) {
		const openapi_path_binding_method_t *m = &op_path->methods[i];
		entry_method_t *t = &p->methods[mi];
		entry_t *e;

		if (m->method == HTTP_REQUEST_INVALID)
			break;

		/*
		 * Skip method if data_parser does not support any of the in/out
		 * types which would just cause slurmrestd to abort() later.
		 */
		if (!_data_parser_supports_method(parser, m)) {
			debug5("%s: skip binding \"%s %s\" with %s",
			       __func__, get_http_method_string(m->method),
			       path, data_parser_get_plugin(parser));
			continue;
		}

		t->method = m->method;
		t->bound = m;

		if (i != 0) {
			_clone_entries(&t->entries, entries, entries_count);
			e = t->entries;
		} else {
			p->methods[0].entries = e = entries;
		}

		for (; e->type; e++) {
			if (e->type == OPENAPI_PATH_ENTRY_MATCH_PARAMETER)
				e->parameter =
					data_parser_g_resolve_openapi_type(
						parser, m->parameters,
						e->entry);

			debug5("%s: add binded path %s entry: method=%s tag=%d entry=%s parameter=%s entry_type=%s",
			       __func__, path,
			       get_http_method_string(m->method), tag, e->entry,
			       openapi_type_to_string(e->parameter),
			       _get_entry_type_string(e->type));
		}

		/* only move to next method if populated */
		mi++;
	}

	list_append(paths, p);
	*tag_ptr = tag;
	return SLURM_SUCCESS;
}

/*
 * Check if the entry matches based on the OAS type
 * and if it does, then add that matched parameter
 */
static bool _match_param(const data_t *data, match_path_from_data_t *args)
{
	bool matched = false;
	entry_t *entry = args->entry;
	data_t *params = args->params;
	data_t *match = data_new();

	data_copy(match, data);

	switch (entry->parameter) {
	case OPENAPI_TYPE_NUMBER:
	{
		if (data_convert_type(match, DATA_TYPE_FLOAT) ==
		    DATA_TYPE_FLOAT) {
			data_set_float(data_key_set(params, entry->entry),
				       data_get_float(match));
			matched = true;
		}
		break;
	}
	case OPENAPI_TYPE_INTEGER:
	{
		if (data_convert_type(match, DATA_TYPE_INT_64) ==
		    DATA_TYPE_INT_64) {
			data_set_int(data_key_set(params, entry->entry),
				     data_get_int(match));
			matched = true;
		}
		break;
	}
	default: /* assume string */
		debug("%s: unknown parameter type %s",
		      __func__, openapi_type_to_string(entry->parameter));
		/* fall through */
	case OPENAPI_TYPE_STRING:
	{
		if (data_convert_type(match, DATA_TYPE_STRING) ==
		    DATA_TYPE_STRING) {
			data_set_string(data_key_set(params, entry->entry),
					data_get_string(match));
			matched = true;
		}
		break;
	}
	}

	if (get_log_level() >= LOG_LEVEL_DEBUG5) {
		char *str = NULL;
		data_get_string_converted(data, &str);

		debug5("%s: parameter %s[%s]->%s[%s] result=%s",
		       __func__, entry->entry,
		       openapi_type_to_string(entry->parameter),
		       str, data_get_type_string(data),
		       (matched ? "matched" : "failed"));

		xfree(str);
	}

	FREE_NULL_DATA(match);
	return matched;
}

static data_for_each_cmd_t _match_path(const data_t *data, void *y)
{
	match_path_from_data_t *args = y;
	entry_t *entry = args->entry;

	if (!entry->type)
		return DATA_FOR_EACH_FAIL;

	if (entry->type == OPENAPI_PATH_ENTRY_MATCH_STRING) {
		bool match;

		if (data_get_type(data) != DATA_TYPE_STRING)
			return DATA_FOR_EACH_FAIL;

		match = !xstrcmp(data_get_string(data), entry->entry);

		debug5("%s: string attempt match %s to %s: %s",
		       __func__, entry->entry, data_get_string(data),
		       (match ? "SUCCESS" : "FAILURE"));

		if (!match)
			return DATA_FOR_EACH_FAIL;
	} else if (entry->type == OPENAPI_PATH_ENTRY_MATCH_PARAMETER) {
		if (!_match_param(data, args))
			return DATA_FOR_EACH_FAIL;
	} else
		fatal_abort("%s: unknown OAS path entry match type",
			    __func__);

	args->entry++;
	return DATA_FOR_EACH_CONT;
}

static char *_entry_to_string(entry_t *entry)
{
	char *path = NULL;
	data_t *d = data_set_list(data_new());

	for (; entry->type; entry++) {
		switch (entry->type) {
		case OPENAPI_PATH_ENTRY_MATCH_STRING:
			data_set_string(data_list_append(d), entry->entry);
			break;
		case OPENAPI_PATH_ENTRY_MATCH_PARAMETER:
			data_set_string_fmt(data_list_append(d), "{%s}",
					    entry->entry);
			break;
		case OPENAPI_PATH_ENTRY_UNKNOWN:
		case OPENAPI_PATH_ENTRY_MAX:
			fatal_abort("invalid entry type");
		}
	}

	serialize_g_data_to_string(&path, NULL, d, MIME_TYPE_JSON,
				   SER_FLAGS_COMPACT);

	FREE_NULL_DATA(d);
	return path;
}

static int _match_path_from_data(void *x, void *key)
{
	char *dst_path = NULL, *src_path = NULL;
	match_path_from_data_t *args = key;
	path_t *path = x;
	entry_method_t *method;
	bool matched = false;

	xassert(path->magic == MAGIC_PATH);

	if (get_log_level() >= LOG_LEVEL_DEBUG5) {
		serialize_g_data_to_string(&dst_path, NULL, args->dpath,
					   MIME_TYPE_JSON, SER_FLAGS_COMPACT);
	}

	args->path = path;
	for (method = path->methods; method->entries; method++) {
		int entries = 0;

		if (get_log_level() >= LOG_LEVEL_DEBUG5) {
			xfree(src_path);
			src_path = _entry_to_string(method->entries);
		}

		if (args->method != method->method) {
			debug5("%s: method skip for %s(%d, %s != %s) to %s(0x%"PRIXPTR")",
			       __func__, src_path, args->path->tag,
			       get_http_method_string(args->method),
			       get_http_method_string(method->method),
			       dst_path, (uintptr_t) args->dpath);
			continue;
		}

		for (args->entry = method->entries; args->entry->type;
		     entries++, args->entry++)
			/* do nothing */;

		if (data_get_list_length(args->dpath) != entries) {
			debug5("%s: skip non-matching subdirectories: registered=%u requested=%zu ",
			       __func__, entries,
			       data_get_list_length(args->dpath));
			continue;
		}

		args->entry = method->entries;
		if (data_list_for_each_const(args->dpath, _match_path,
					     args) < 0) {
			debug5("%s: match failed %s",
			       __func__, args->entry->entry);
			continue;
		}

		/*
		 * The list is NULL terminated, so if entry->type is not NULL
		 * we didn't match the whole list, but we already don't have
		 * anything to compare in request.
		 */
		if (!args->entry->type) {
			args->tag = path->tag;
			matched = true;
			break;
		}
	}

	debug5("%s: match %s for %s(%d, %s) to %s(0x%"PRIXPTR")",
	       __func__,
	       matched ? "successful" : "failed",
	       src_path,
	       args->path->tag,
	       get_http_method_string(args->method),
	       dst_path,
	       (uintptr_t) args->dpath);

	xfree(src_path);
	xfree(dst_path);

	return matched;
}

extern int find_path_tag(const data_t *dpath, data_t *params,
			 http_request_method_t method)
{
	match_path_from_data_t args = {
		.params = params,
		.dpath = dpath,
		.method = method,
		.tag = -1,
	};

	xassert(data_get_type(params) == DATA_TYPE_DICT);

	(void) list_find_first(paths, _match_path_from_data, &args);

	return args.tag;
}

static int _bind_paths(const openapi_path_binding_t *paths,
		       const openapi_resp_meta_t *meta)
{
	int rc = SLURM_SUCCESS;

	for (int i = 0; paths[i].path; i++) {
		const openapi_path_binding_t *op_path = &paths[i];

		if ((rc = bind_operation_path(op_path, meta)))
			break;
	}

	return rc;
}

extern int init_openapi(const char *plugin_list, plugrack_foreach_t listf,
			data_parser_t **parsers_ptr,
			const http_status_code_t *resp_status_codes)
{
	int rc;

	if (paths)
		fatal("%s called twice", __func__);

	if (resp_status_codes)
		response_status_codes = resp_status_codes;
	else
		response_status_codes = default_response_status_codes;

	paths = list_create(_list_delete_path_t);

	/* must have JSON plugin to parse the openapi.json */
	serializer_required(MIME_TYPE_JSON);

	if ((rc = _bind_paths(openapi_paths, NULL)))
		fatal("Unable to bind openapi specification paths: %s",
		      slurm_strerror(rc));

	rc = load_plugins(&plugins, OPENAPI_MAJOR_TYPE, plugin_list, listf,
			  syms, ARRAY_SIZE(syms));

	if (!xstrcasecmp("list", plugin_list))
		return SLURM_SUCCESS;

	if (rc)
		fatal("Loading OpenAPI plugins failed: %s", slurm_strerror(rc));

	if ((rc = load_plugins(&plugins, OPENAPI_MAJOR_TYPE, plugin_list, listf,
			       syms, ARRAY_SIZE(syms))))
		fatal("Loading OpenAPI plugins failed: %s", slurm_strerror(rc));

	if (!plugins->count)
		fatal("No OpenAPI plugins loaded.");

	parsers = parsers_ptr;

	for (size_t i = 0; i < plugins->count; i++) {
		const funcs_t *funcs = plugins->functions[i];
		const openapi_path_binding_t *paths;
		const openapi_resp_meta_t *meta;

		if ((rc = funcs->get_paths(&paths, &meta)))
			fatal("Failure loading plugin path bindings: %s",
			      slurm_strerror(rc));

		if ((rc = _bind_paths(paths, meta)))
			fatal("Unable to bind openapi specification paths: %s",
			      slurm_strerror(rc));
	}

	/* Call init() after all plugins are fully loaded */
	for (size_t i = 0; i < plugins->count; i++) {
		const funcs_t *funcs = plugins->functions[i];
		funcs->init();
	}

	return rc;
}

extern void destroy_openapi(void)
{
	if (!paths)
		return;

	for (size_t i = 0; i < plugins->count; i++) {
		const funcs_t *funcs = plugins->functions[i];
		funcs->fini();
	}

	FREE_NULL_PLUGINS(plugins);
	FREE_NULL_LIST(paths);
}

static data_for_each_cmd_t _merge_operationId_strings(data_t *data, void *arg)
{
	id_merge_path_t *args = arg;
	char *p;

	xassert(args->magic == MAGIC_MERGE_ID_PATH);
	xassert(args->merge_args->magic == MAGIC_MERGE_PATH);

	if (data_convert_type(data, DATA_TYPE_STRING) != DATA_TYPE_STRING)
		return DATA_FOR_EACH_FAIL;

	p = xstrdup(data_get_string(data));

	/* sub out '.' for '_' to avoid breaking compilers */
	for (int s = strlen(p), i = 0; i < s; i++)
		if ((p[i] == '.') || (p[i] == '{') || (p[i] == '}'))
			p[i] = '_';

	xstrfmtcatat(args->operation, &args->at, "%s%s",
		     (args->operation ? "_" : ""), data_get_string(data));

	xfree(p);

	return DATA_FOR_EACH_CONT;
}

static int _add_operation_path(const char *entry, bool template, void *arg)
{
	url_parse_operation_id_args_t *args = arg;
	data_t *dpath = args->dpath;
	data_t *last = args->last;
	data_t *c = NULL;

	xassert(args->magic == URL_PARSE_OPERATION_IDARGS_MAGIC);

	if (!xstrcasecmp(entry, ".") || !xstrcasecmp(entry, ".."))
		fatal_abort("OpenAPI path must not include . or ..");

	if (template && last && !xstrcmp(data_get_string(last), entry)) {
		/*
		 * Last entry is the same as the parameter name which means that
		 * the entry is uncountable and we need to set last entry as
		 * "single" to avoid ambiguous operationIds
		 */
		data_set_string(data_list_append(dpath), entry);
		c = data_set_string(last, "single");
	} else if (template) {
		/* skip including template names in operationId */
	} else {
		char *e = xstrdup(entry);

		/* Strip out all '.' in entry */
		xstrsubstituteall(e, ".", "");

		c = data_set_string_own(data_list_append(dpath), e);
	}

	args->last = c;
	return SLURM_SUCCESS;
}

/* Caller must xfree() returned string */
static char *_get_method_operationId(openapi_spec_t *spec, path_t *path,
				     const openapi_path_binding_method_t
					     *method)
{
	data_t *merge[10] = { 0 }, *dpath = NULL, *merged = NULL;
	const char *method_str = get_http_method_string_lc(method->method);
	int i = 0;
	merge_path_t merge_args = {
		.magic = MAGIC_MERGE_PATH,
		.paths = spec->paths,
	};
	id_merge_path_t merge_id_args = {
		.magic = MAGIC_MERGE_ID_PATH,
		.merge_args = &merge_args,
	};
	url_parse_operation_id_args_t url_args = {
		.magic = URL_PARSE_OPERATION_IDARGS_MAGIC,
	};

	url_args.dpath = dpath = data_set_list(data_new());
	(void) url_path_walk(path->path, true, _add_operation_path, &url_args);

	xassert((data_get_list_length(dpath) + 1) < (ARRAY_SIZE(merge) - 1));

	if (data_get_list_length(dpath) < 3) {
		/* unversioned paths */
		merge[i++] = data_set_string(data_new(), method_str);
	} else {
		merge[i++] = data_list_dequeue(dpath); /* slurm vs slurmdb */
		merge[i++] = data_list_dequeue(dpath); /* v0.0.XX */
		merge[i++] = data_set_string(data_new(), method_str);
	}

	while (data_get_list_length(dpath) && (i < (ARRAY_SIZE(merge) - 1)))
		merge[i++] = data_list_dequeue(dpath);

	merged = data_list_join((const data_t **) merge, true);
	if (data_list_for_each(merged, _merge_operationId_strings,
			       &merge_id_args) < 0)
		fatal_abort("_merge_operationId_strings() failed which should never happen");

	for (i = 0; i < ARRAY_SIZE(merge); i++)
		FREE_NULL_DATA(merge[i]);
	FREE_NULL_DATA(merged);
	FREE_NULL_DATA(dpath);

	debug5("%s: [%s %s] setting OperationId: %s",
	       __func__, method_str, path->path, merge_id_args.operation);

	return merge_id_args.operation;
}

static int _populate_method(path_t *path, openapi_spec_t *spec, data_t *dpath,
			    const openapi_path_binding_method_t *method)
{
	const char **mime_types = spec->mime_types;
	void *refs = &spec->references[_resolve_parser_index(path->parser)];
	data_t *dmethod = data_set_dict(data_key_set(dpath,
		get_http_method_string_lc(method->method)));
	data_t *dtags = data_set_list(data_key_set(dmethod, "tags"));

	for (int i = 0; method->tags[i]; i++)
		data_set_string(data_list_append(dtags), method->tags[i]);

	if (method->summary)
		data_set_string(data_key_set(dmethod, "summary"),
				method->summary);
	if (method->description)
		data_set_string(data_key_set(dmethod, "description"),
				method->description);

	if (data_parser_g_is_deprecated(path->parser))
		data_set_bool(data_key_set(dmethod, "deprecated"), true);

	{
		char *opid = _get_method_operationId(spec, path, method);
		data_set_string_own(data_key_set(dmethod, "operationId"), opid);
	}

	if (method->parameters || method->query) {
		/*
		 * Use existing replacements
		 */
		data_t *dst = data_key_set(dmethod, "parameters");

		if (data_parser_g_populate_parameters(path->parser,
						      method->parameters,
						      method->query, refs, dst,
						      spec->components_schemas))
			fatal_abort("data_parser_g_populate_parameters() failed");
	}

	if (method->response.type) {
		data_t *dresp = data_set_dict(data_key_set(dmethod, "responses"));
		data_t *resp_code = data_set_dict(data_new());
		data_t *cnt = data_set_dict(data_key_set(resp_code, "content"));

		if (method->response.description)
			data_set_string(data_set_dict(data_key_set(resp_code,
				"description")), method->response.description);

		for (int i = 0; mime_types[i]; i++) {
			data_t *dtype, *dschema;

			/*
			 * Never return URL encoded mimetype as it is only for
			 * HTTP query
			 */
			if (!xstrcmp(mime_types[i], MIME_TYPE_URL_ENCODED))
				continue;

			dtype = data_set_dict(data_key_set(cnt, mime_types[i]));
			dschema = data_set_dict(data_key_set(dtype, "schema"));

			if (data_parser_g_populate_schema(path->parser,
				method->response.type, refs, dschema,
				spec->components_schemas))
				fatal_abort("data_parser_g_populate_schema() failed");
		}

		for (int i = 0; response_status_codes[i]; i++) {
			const http_status_code_t code =
				response_status_codes[i];
			char str[64];

			if (code == HTTP_STATUS_CODE_DEFAULT)
				snprintf(str, sizeof(str), "%s",
					 get_http_status_code_string(code));
			else
				snprintf(str, sizeof(str), "%u", code);

			data_copy(data_key_set(dresp, str), resp_code);
		}

		FREE_NULL_DATA(resp_code);
	}

	if (method->body.type) {
		data_t *dbody = data_set_dict(data_key_set(dmethod,
							   "requestBody"));
		data_t *cnt = data_set_dict(data_key_set(dbody, "content"));

		if (method->body.description)
			data_set_string(data_set_dict(data_key_set(dbody,
				"description")), method->body.description);

		for (int i = 0; mime_types[i]; i++) {
			data_t *dtype, *dschema;

			/*
			 * Never return URL encoded mimetype as it is only for
			 * HTTP query
			 */
			if (!xstrcmp(mime_types[i], MIME_TYPE_URL_ENCODED))
				continue;

			dtype = data_set_dict(data_key_set(cnt, mime_types[i]));
			dschema = data_set_dict(data_key_set(dtype, "schema"));

			if (data_parser_g_populate_schema(path->parser,
				method->body.type, refs, dschema,
				spec->components_schemas))
				fatal_abort("data_parser_g_populate_schema() failed");
		}

	}

	return SLURM_SUCCESS;
}

static int _foreach_add_path(void *x, void *arg)
{
	int rc = SLURM_SUCCESS;
	path_t *path = x;
	openapi_spec_t *spec = arg;
	const openapi_path_binding_t *bound = path->bound;
	data_t *dpath;

	xassert(spec->magic == MAGIC_OAS);
	xassert(path->magic == MAGIC_PATH);

	if (!bound)
		return SLURM_SUCCESS;

	if (bound->flags & OPENAPI_BIND_HIDDEN_OAS)
		return SLURM_SUCCESS;

	xassert(!data_key_get(spec->paths, path->path));
	dpath = data_set_dict(data_key_set(spec->paths, path->path));

	for (int i = 0; !rc && path->methods[i].method; i++)
		rc = _populate_method(path, spec, dpath,
				      path->methods[i].bound);

	return rc;
}

static int _foreach_count_path(void *x, void *arg)
{
	path_t *path = x;
	openapi_spec_t *spec = arg;
	const char **mime_types = spec->mime_types;
	const openapi_path_binding_t *bound = path->bound;
	void *refs;

	xassert(spec->magic == MAGIC_OAS);
	xassert(path->magic == MAGIC_PATH);

	if (!bound)
		return SLURM_SUCCESS;

	refs = &spec->references[_resolve_parser_index(path->parser)];

	for (int i = 0; path->methods[i].method; i++) {
		const openapi_path_binding_method_t *method =
			path->methods[i].bound;

		if (method->parameters &&
		    data_parser_g_increment_reference(path->parser,
						      method->parameters, refs))
			fatal_abort("data_parser_g_increment_reference() failed");

		if (method->query &&
		    data_parser_g_increment_reference(path->parser,
						      method->query, refs))
			fatal_abort("data_parser_g_increment_reference() failed");

		if (method->body.type) {
			/*
			 * Need to add 1 reference per mime type that will get
			 * dumped
			 */
			for (int i = 0; mime_types[i]; i++)
				if (data_parser_g_increment_reference(
					path->parser, method->body.type, refs))
					fatal_abort("data_parser_g_increment_reference() failed");
		}

		if (method->response.type) {
			/*
			 * Need to add 1 reference per mime type that will get
			 * dumped
			 */
			for (int i = 0; mime_types[i]; i++)
				if (data_parser_g_increment_reference(
					path->parser, method->response.type,
					refs))
					fatal_abort("data_parser_g_increment_reference() failed");
		}
	}

	return SLURM_SUCCESS;
}

extern int generate_spec(data_t *dst, const char **mime_types)
{
	openapi_spec_t spec = {
		.magic = MAGIC_OAS,
		.spec = dst,
		.mime_types = mime_types,
	};
	data_t *security1, *security2, *security3;
	data_t *openapi_plugins, *data_parsers, *slurm_version;
	char *version = xstrdup_printf("Slurm-%s", SLURM_VERSION_STRING);
	int parsers_count;

	/* count the parsers present to allocate refs counts */
	for (parsers_count = 0; parsers[parsers_count]; parsers_count++);
	spec.references = xcalloc(parsers_count, sizeof(*spec.references));

	data_set_dict(spec.spec);
	spec.tags = data_set_list(data_key_set(spec.spec, "tags"));
	spec.paths = data_set_dict(data_key_set(spec.spec, "paths"));
	spec.components = data_set_dict(data_key_set(spec.spec, "components"));
	spec.components_schemas = data_set_dict(data_key_set(spec.components,
							     "schemas"));
	spec.security_schemas = data_set_dict(data_key_set(spec.components,
							   "securitySchemes"));
	spec.info = data_set_dict(data_key_set(spec.spec, "info"));
	spec.contact = data_set_dict(data_key_set(spec.info, "contact"));
	spec.license = data_set_dict(data_key_set(spec.info, "license"));
	spec.x_slurm = data_set_dict(data_key_set(spec.info, "x-slurm"));
	spec.servers = data_set_list(data_key_set(spec.spec, "servers"));
	spec.security = data_set_list(data_key_set(spec.spec, "security"));
	security1 = data_set_dict(data_list_append(spec.security));
	security2 = data_set_dict(data_list_append(spec.security));
	security3 = data_set_dict(data_list_append(spec.security));
	data_parsers =
		data_set_list(data_key_set(spec.x_slurm, "data_parsers"));
	openapi_plugins = data_set_list(data_key_set(spec.x_slurm, "openapi"));
	slurm_version = data_set_dict(data_key_set(spec.x_slurm, "version"));

	data_set_string(data_key_set(spec.spec, "openapi"),
			openapi_spec.openapi_version);
	data_set_string(data_key_set(spec.info, "title"), openapi_spec.info.title);
	data_set_string(data_key_set(spec.info, "description"),
			openapi_spec.info.desc);
	data_set_string(data_key_set(spec.info, "termsOfService"),
			openapi_spec.info.tos);
	data_set_string_own(data_key_set(spec.info, "version"), version);

	/* Populate info spec extension x-slurm with data_parsers and flags */
	for (int i = 0; i < parsers_count; i++) {
		data_t *tmp_data = data_list_append(data_parsers);

		data_set_dict(tmp_data);
		data_set_string(data_key_set(tmp_data, "plugin"),
				data_parser_get_plugin_version(parsers[i]));
		if (data_parser_g_dump_flags(parsers[i],
					     data_key_set(tmp_data, "flags")))
			fatal_abort("data_parser_g_dump_flags() failed");
	}

	/* Populate info spec extension x-slurm with openapi plugins */
	for (int i = 0; i < plugins->count; i++)
		data_set_string(data_list_append(openapi_plugins),
				plugins->types[i]);

	data_set_string(data_key_set(slurm_version, "major"), SLURM_MAJOR);
	data_set_string(data_key_set(slurm_version, "micro"), SLURM_MICRO);
	data_set_string(data_key_set(slurm_version, "minor"), SLURM_MINOR);
	data_set_string(data_key_set(spec.x_slurm, "release"),
			SLURM_VERSION_STRING);
	data_set_string(data_key_set(spec.contact, "name"),
			openapi_spec.info.contact.name);
	data_set_string(data_key_set(spec.contact, "url"),
			openapi_spec.info.contact.url);
	data_set_string(data_key_set(spec.contact, "email"),
			openapi_spec.info.contact.email);
	data_set_string(data_key_set(spec.license, "name"),
			openapi_spec.info.license.name);
	data_set_string(data_key_set(spec.license, "url"),
			openapi_spec.info.license.url);

	for (int i = 0; i < ARRAY_SIZE(openapi_spec.tags); i++) {
		data_t *tag = data_set_dict(data_list_append(spec.tags));
		data_set_string(data_key_set(tag, "name"),
				openapi_spec.tags[i].name);
		data_set_string(data_key_set(tag, "description"),
				openapi_spec.tags[i].desc);
	}
	for (int i = 0; i < ARRAY_SIZE(openapi_spec.servers); i++) {
		data_t *server = data_set_dict(data_list_append(spec.servers));
		data_set_string(data_key_set(server, "url"),
				openapi_spec.servers[i].url);
	}

	/* Add default of no auth required */
	data_set_dict(data_list_append(spec.security));
	/* Add user and token auth */
	data_set_list(data_key_set(
		security1,
		openapi_spec.components.security_schemes[SEC_SCHEME_USER_INDEX]
			.key));
	data_set_list(data_key_set(
		security1,
		openapi_spec.components.security_schemes[SEC_SCHEME_TOKEN_INDEX]
			.key));
	/* Add only token auth */
	data_set_list(data_key_set(
		security2,
		openapi_spec.components.security_schemes[SEC_SCHEME_TOKEN_INDEX]
			.key));
	/* Add only bearer */
	data_set_list(data_key_set(
		security3, openapi_spec.components
				   .security_schemes[SEC_SCHEME_BEARER_INDEX]
				   .key));

	for (int i = 0;
	     i < ARRAY_SIZE(openapi_spec.components.security_schemes); i++) {
		const struct security_scheme_s *s =
			&openapi_spec.components.security_schemes[i];
		data_t *schema =
			data_set_dict(data_key_set(spec.security_schemas,
						   s->key));
		data_set_string(data_key_set(schema, "type"), s->type);
		data_set_string(data_key_set(schema, "description"), s->desc);

		if (s->name)
			data_set_string(data_key_set(schema, "name"), s->name);
		if (s->in)
			data_set_string(data_key_set(schema, "in"), s->in);
		if (s->scheme)
			data_set_string(data_key_set(schema, "scheme"),
					s->scheme);
		if (s->bearer_format)
			data_set_string(data_key_set(schema, "bearerFormat"),
					s->bearer_format);
	}

	(void) list_for_each(paths, _foreach_count_path, &spec);

	/* Add generated paths */
	(void) list_for_each(paths, _foreach_add_path, &spec);

	for (int i = 0; parsers[i]; i++)
		if (spec.references[i])
			data_parser_g_release_references(parsers[i],
							 &spec.references[i]);
	xfree(spec.references);

	/*
	 * We currently fatal instead of returning failure since openapi are
	 * compile time static and we should not be failing to serve the specs
	 * out
	 */
	return SLURM_SUCCESS;
}

static int _op_handler_openapi(openapi_ctxt_t *ctxt)
{
	return generate_spec(ctxt->resp, get_mime_type_array());
}

static int _op_handler_health(openapi_ctxt_t *ctxt)
{
	return ESLURM_REST_EMPTY_RESULT;
}

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;
	openapi_ctxt_t *ctxt = arg;

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

	openapi_resp_error(ctxt, error_code, source, "%s", str);

	xfree(str);

	return false;
}

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

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

	openapi_resp_warn(ctxt, source, "%s", str);

	xfree(str);
}

extern int openapi_resp_error(openapi_ctxt_t *ctxt, int error_code,
			      const char *source, const char *why, ...)
{
	openapi_resp_error_t *e;

	xassert(ctxt->errors);

	if (!ctxt->errors)
		return error_code;

	e = xmalloc(sizeof(*e));

	if (why) {
		va_list ap;
		char *str;

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

		error("%s: [%s] parser=%s rc[%d]=%s -> %s",
		      (source ? source : __func__), ctxt->id,
		      data_parser_get_plugin(ctxt->parser), error_code,
		      slurm_strerror(error_code), str);

		e->description = str;
	}

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

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

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

	list_append(ctxt->errors, e);

	return error_code;
}

extern void openapi_resp_warn(openapi_ctxt_t *ctxt, const char *source,
			      const char *why, ...)
{
	openapi_resp_warning_t *w;

	xassert(ctxt->warnings);

	if (!ctxt->warnings)
		return;

	w = xmalloc(sizeof(*w));

	if (why) {
		va_list ap;
		char *str;

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

		debug("%s: [%s] parser=%s WARNING: %s",
		      (source ? source : __func__), ctxt->id,
		      data_parser_get_plugin(ctxt->parser), str);

		w->description = str;
	}

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

	list_append(ctxt->warnings, w);
}

static void _populate_openapi_results(openapi_ctxt_t *ctxt,
				      openapi_resp_meta_t *query_meta)
{
	data_t *errors, *warnings, *meta;
	int rc;

	/* need to populate meta, errors and warnings */
	errors = data_key_set(ctxt->resp,
		XSTRINGIFY(OPENAPI_RESP_STRUCT_ERRORS_FIELD_NAME));
	warnings = data_key_set(ctxt->resp,
		XSTRINGIFY(OPENAPI_RESP_STRUCT_WARNINGS_FIELD_NAME));
	meta = data_key_set(ctxt->resp,
		XSTRINGIFY(OPENAPI_RESP_STRUCT_META_FIELD_NAME));

	if (data_get_type(meta) == DATA_TYPE_NULL)
		DATA_DUMP(ctxt->parser, OPENAPI_META_PTR, query_meta, meta);

	if (data_get_type(errors) == DATA_TYPE_LIST) {
		if (!data_get_list_length(errors))
			data_set_null(errors);
		else
			xassert(list_is_empty(ctxt->errors));
	}

	if ((data_get_type(errors) == DATA_TYPE_NULL) &&
	    ((rc = DATA_DUMP(ctxt->parser, OPENAPI_ERRORS, ctxt->errors,
			     errors)))) {
		/* data_parser doesn't support OPENAPI_ERRORS parser */
		data_t *e =
			data_set_dict(data_list_append(data_set_list(errors)));
		data_set_string(data_key_set(e, "description"),
				"Requested data_parser plugin does not support OpenAPI plugin");
		data_set_int(data_key_set(e, "error_number"),
			     ESLURM_NOT_SUPPORTED);
		data_set_string(data_key_set(e, "error"),
				slurm_strerror(ESLURM_NOT_SUPPORTED));
	}

	if (data_get_type(warnings) == DATA_TYPE_LIST) {
		if (!data_get_list_length(warnings))
			data_set_null(warnings);
		else
			xassert(list_is_empty(ctxt->warnings));
	}

	if (data_get_type(warnings) == DATA_TYPE_NULL)
		DATA_DUMP(ctxt->parser, OPENAPI_WARNINGS, ctxt->warnings,
			  warnings);
}

extern int wrap_openapi_ctxt_callback(const char *context_id,
				      http_request_method_t method,
				      data_t *parameters, data_t *query,
				      int tag, data_t *resp, void *db_conn,
				      data_parser_t *parser,
				      const openapi_path_binding_t *op_path,
				      const openapi_resp_meta_t *plugin_meta)
{
	int rc = SLURM_SUCCESS;
	openapi_ctxt_t ctxt = {
		.id = context_id,
		.method = method,
		.parameters = parameters,
		.query = query,
		.resp = resp,
		.tag = tag,
	};
	openapi_resp_meta_t query_meta = {{0}};
	openapi_ctxt_handler_t callback = op_path->callback;

	if (plugin_meta)
		query_meta = *plugin_meta;

	query_meta.plugin.data_parser = (char *) data_parser_get_plugin(parser);
	query_meta.plugin.accounting_storage =
		(char *) slurm_conf.accounting_storage_type;
	query_meta.client.source = (char *) context_id;
	query_meta.slurm.cluster = slurm_conf.cluster_name;

	ctxt.parent_path = data_set_list(data_new());
	ctxt.errors = list_create(free_openapi_resp_error);
	ctxt.warnings = list_create(free_openapi_resp_warning);
	ctxt.parser = data_parser_g_new(_on_error, _on_error, _on_error, &ctxt,
					_on_warn, _on_warn, _on_warn, &ctxt,
					data_parser_get_plugin(parser), NULL,
					true);

	debug("%s: [%s] %s using %s",
	      __func__, context_id, get_http_method_string(method),
	      data_parser_get_plugin(ctxt.parser));

	if (op_path->flags & OPENAPI_BIND_NO_SLURMDBD) {
		; /* Do not attempt to open a connection to slurmdbd */
	} else if (!(ctxt.db_conn = db_conn)) {
		if (op_path->flags & OPENAPI_BIND_REQUIRE_SLURMDBD)
			openapi_resp_error(
				&ctxt, (rc = ESLURM_DB_CONNECTION),
				XSTRINGIFY(openapi_get_db_conn),
					"Failed to open slurmdbd connection");
		else
			openapi_resp_warn(
				&ctxt,
				XSTRINGIFY(openapi_get_db_conn),
					"Failed to open connection to slurmdbd. Response fields may not be fully populated or empty.");
	} else {
		rc = data_parser_g_assign(ctxt.parser,
					  DATA_PARSER_ATTR_DBCONN_PTR,
					  ctxt.db_conn);
	}

	if (!rc)
		rc = callback(&ctxt);

	if (data_get_type(ctxt.resp) == DATA_TYPE_NULL)
		data_set_dict(ctxt.resp);

	if (op_path->flags & OPENAPI_BIND_OPENAPI_RESP_FMT)
		_populate_openapi_results(&ctxt, &query_meta);

	if (!rc)
		rc = ctxt.rc;

	FREE_NULL_LIST(ctxt.errors);
	FREE_NULL_LIST(ctxt.warnings);
	FREE_NULL_DATA_PARSER(ctxt.parser);
	FREE_NULL_DATA(ctxt.parent_path);

	return rc;
}

extern bool is_spec_generation_only(bool set)
{
	static bool is_spec_only = false;

	if (set)
		is_spec_only = true;

	return is_spec_only;
}
