/*****************************************************************************\
 *  parse.c - Slurm REST API parsing handlers
 *****************************************************************************
 *  Copyright (C) 2019-2020 SchedMD LLC.
 *  Written by Nathan Rini <nate@schedmd.com>
 *
 *  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 <math.h>
#include <unistd.h>

#include "slurm/slurm.h"

#include "src/common/data.h"
#include "src/common/log.h"
#include "src/common/node_select.h"
#include "src/common/read_config.h"
#include "src/common/slurm_protocol_api.h"
#include "src/common/slurmdbd_defs.h"
#include "src/common/uid.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"

#include "src/slurmrestd/plugins/openapi/dbv0.0.36/api.h"

/*
 * WARNING: parser uses a ton of macros to avoid massive amounts of copy and
 * pasta of nearly identicaly code. Make sure to check the macros first before
 * adding code.
 *
 * The database structures have many cross calls and it was found just handling
 * them all in one location was much cleaner than having to copy and paste the
 * same code many times along with all the possible errors that will introduce.
 *
 * This code is basically equivent to the pack/unpack code used for the Slurm
 * internal protocol but is designed to be used against data_t for slurmrestd.
 * The code is not version checked as the plugins are locked to a specific
 * version instead.
 *
 * Each struct to be parsed/dumped is defined with an array of parser_t entries.
 * Macros are used to fill out the contents of each entry since they are highly
 * repetitive. Each struct should directly mirror the order and structure of the
 * original structure for simplicity. Through the use of offsetof() macro, we
 * are able to directly record the location of each struct member and then work
 * them directly while using a generic structure for the parser itself. At the
 * top of every parser/dumper, we put the pointer math to keep everything simple
 * in the function when using the offsets. The field can be a nested dictionary
 * and any "/" will automatically be expanded into dictionary entries to make
 * writting these definitions easy.
 *
 * Every struct type and primitive type is defined in parser_type_t along with a
 * description of what is being transcribed. With most types, there is a dumper
 * and parser function that needs to be defined in funcs[]. It is fully expected
 * that these functions are *symmetric* to make it easy to dump and load the
 * structs.
 *
 * Flags and quasi bool fields have special handlers to avoid needing to write
 * function to handle piles of flags. Even if a struct uses an individual bool
 * field for a flag, we try to place all of them in a flags array anyway.
 *
 * Stand alone structs that will be parsed/dumped need to be added to parsers to
 * allow external calls to easily dump/parse the structs.
 *
 * When structs are not symmetric, we use more complex types to make it look
 * symmetric to the users and handle the dirty work here instead of expecting
 * the user to do it.
 *
 * How to use:
 * 	1. Add all types to parser_type_t
 * 	2. Add parser and dumper function following prototypes to funcs[].
 * 	3. Add full structures to parsers (if needed).
 * 	4. Call dumper/parser where needed, if only a field is being modified,
 * 	this step can be skipped.
 *
 */

typedef struct {
	enum {
		PARSER_ENUM_FLAG_INVALID = 0,
		PARSER_ENUM_FLAG_BIT, /* set a single bit */
		PARSER_ENUM_FLAG_BOOL, /* set a bool using offset */
	} type;
	uint64_t flag;
	size_t size;
	const char *string;
	size_t field_offset;
} parser_enum_t;

typedef struct {
	size_t field_offset_count;
	size_t field_offset_node;
	size_t field_offset_task;
	size_t field_offset_nodes; /* char* node list */
} parser_tres_t;

typedef struct {
	const parser_enum_t *list;
	size_t count;
} parser_flags_t;

/* QOS preemption list uses one field to list and one field to set */
typedef struct {
	size_t field_offset_preempt_bitstr;
	size_t field_offset_preempt_list;
} parser_qos_preempt_t;

typedef struct {
	parser_type_t type;
	bool required;
	size_t field_offset;
	char *key;
	union {
		parser_flags_t flags;
		parser_tres_t tres;
		parser_qos_preempt_t qos_preempt;
	} per_type;
} parser_t;

/* templates for read and write functions */
typedef int (*parse_rfunc_t)(const parser_t *const parse, void *dst,
			     data_t *src, data_t *errors,
			     const parser_env_t *penv);
typedef int (*parse_wfunc_t)(const parser_t *const parse, void *src,
			     data_t *dst, const parser_env_t *penv);

typedef struct {
	parser_type_t type;
	const parser_t *const parse;
	const size_t parse_member_count;
} parsers_t;

#define add_parser(stype, mtype, req, field, path)                            \
	{                                                                     \
		.field_offset = offsetof(stype, field),                       \
		.key = path,                                                  \
		.required = req,                                              \
		.type = PARSE_##mtype,                                        \
	}
#define add_parser_qos_preempt(stype, req, field_bitstr, field_list, path)    \
	{                                                                     \
		.key = path,                                                  \
		.per_type.qos_preempt.field_offset_preempt_bitstr =           \
			offsetof(stype, field_bitstr),                        \
		.per_type.qos_preempt.field_offset_preempt_list =             \
			offsetof(stype, field_list),                          \
		.required = req,                                              \
		.type = PARSE_QOS_PREEMPT_LIST,                               \
	}
#define add_parser_tres(stype, req, field_count, field_node, field_task,      \
			field_nodes, path)                                    \
	{                                                                     \
		.key = path,                                                  \
		.per_type.tres.field_offset_count =                           \
			offsetof(stype, field_count),                         \
		.per_type.tres.field_offset_node =                            \
			offsetof(stype, field_node),                          \
		.per_type.tres.field_offset_nodes =                           \
			offsetof(stype, field_nodes),                         \
		.per_type.tres.field_offset_task =                            \
			offsetof(stype, field_task),                          \
		.required = req,                                              \
		.type = PARSE_TRES_NODE_COUNT_TASK,                           \
	}
#define add_parser_flags(flags_array, stype, req, field, path)                \
	{                                                                     \
		.field_offset = offsetof(stype, field),                       \
		.key = path,                                                  \
		.per_type.flags.list = flags_array,                           \
		.per_type.flags.count = ARRAY_SIZE(flags_array),              \
		.required = req,                                              \
		.type = PARSE_FLAGS,                                          \
	}
#define add_parser_enum_flag(stype, field, flagv, stringv)                    \
	{                                                                     \
		.flag = flagv,                                                \
		.size = sizeof(((stype *) 0)->field),                         \
		.string = stringv,                                            \
		.type = PARSER_ENUM_FLAG_BIT,                                 \
	}
/* will never set to FALSE, only will set to TRUE if matched  */
#define add_parse_enum_bool(stype, field, stringv)                            \
	{                                                                     \
		.field_offset = offsetof(stype, field),                       \
		.size = sizeof(((stype *) 0)->field),                         \
		.string = stringv,                                            \
		.type = PARSER_ENUM_FLAG_BOOL,                                \
	}

static int _parser_run(void *obj, const parser_t *const parse,
		       const size_t parse_member_count, data_t *data,
		       data_t *errors, const parser_env_t *penv);
static int _parser_dump(void *obj, const parser_t *const parse,
			const size_t parse_member_count, data_t *data,
			const parser_env_t *penv);

#define _add_parse(mtype, field, path) \
	add_parser(slurmdb_assoc_rec_t, mtype, false, field, path)
#define _add_parse_req(mtype, field, path) \
	add_parser(slurmdb_assoc_rec_t, mtype, true, field, path)

static const parser_enum_t parser_assoc_flags[] = {
	add_parser_enum_flag(slurmdb_assoc_rec_t, flags, ASSOC_FLAG_DELETED,
			     "DELETED"),
	add_parse_enum_bool(slurmdb_assoc_rec_t, is_def, "DEFAULT"),
};

static const parser_t parse_assoc_short[] = {
	/* Identifiers required for any given association */
	_add_parse_req(STRING, acct, "account"),
	_add_parse(STRING, cluster, "cluster"),
	_add_parse(STRING, partition, "partition"),
	_add_parse_req(STRING, user, "user"),
};

/* should mirror the structure of slurmdb_assoc_rec_t */
static const parser_t parse_assoc[] = {
	/* skipping accounting_list */
	_add_parse_req(STRING, acct, "account"),
	/* skipping assoc_next */
	/* skipping assoc_next_id */
	/* skipping bf_usage (not packed) */
	_add_parse(STRING, cluster, "cluster"),
	_add_parse(QOS_ID, def_qos_id, "default/qos"),
	add_parser_flags(parser_assoc_flags, slurmdb_assoc_rec_t, false, flags,
			 "flags"),
	/* skip lft */
	_add_parse(UINT32, grp_jobs, "max/jobs/per/count"),
	_add_parse(UINT32, grp_jobs_accrue, "max/jobs/per/accruing"),
	_add_parse(UINT32, grp_submit_jobs, "max/jobs/per/submitted"),
	_add_parse(TRES_LIST, grp_tres, "max/tres/total"),
	/* skipping gres_tres_ctld  (not packed) */
	_add_parse(TRES_LIST, max_tres_mins_pj, "max/tres/minutes/per/job"),
	/* skipping max_tres_mins_ctld */
	_add_parse(TRES_LIST, max_tres_run_mins, "max/tres/minutes/total"),
	/* skipping grp_tres_run_mins_ctld (not packed) */
	_add_parse(UINT32, grp_wall, "max/per/account/wall_clock"),
	_add_parse(TRES_LIST, max_tres_pj, "max/tres/per/job"),
	/* skipping max_tres_ctld */
	_add_parse(TRES_LIST, max_tres_pn, "max/tres/per/node"),
	/* skipping max_tres_pn_ctld */
	_add_parse(UINT32, max_wall_pj, "max/jobs/per/wall_clock"),
	_add_parse(UINT32, min_prio_thresh, "min/priority_threshold"),
	_add_parse(STRING, parent_acct, "parent_account"),
	/* skip parent_id */
	_add_parse(STRING, partition, "partition"),
	_add_parse(UINT32, priority, "priority"),
	_add_parse(QOS_STR_LIST, qos_list, "qos"),
	/* skip rgt */
	_add_parse(UINT32, shares_raw, "shares_raw"),
	/* slurmdbd should never set uid - it should always be zero */
	_add_parse(ASSOC_USAGE, usage, "usage"),
	_add_parse_req(STRING, user, "user"),
	/* skipping user_rec (not packed) */
};
#undef _add_parse
#undef _add_parse_req

#define _add_parse(mtype, field, path) \
	add_parser(slurmdb_user_rec_t, mtype, false, field, path)
#define _add_parse_req(mtype, field, path) \
	add_parser(slurmdb_user_rec_t, mtype, true, field, path)
/* should mirror the structure of slurmdb_user_rec */
static const parser_t parse_user[] = {
	_add_parse(ADMIN_LVL, admin_level, "administrator_level"),
	_add_parse(ASSOC_SHORT_LIST, assoc_list, "associations"),
	_add_parse(COORD_LIST, coord_accts, "coordinators"),
	_add_parse(STRING, default_acct, "default/account"),
	_add_parse(STRING, default_wckey, "default/wckey"),
	_add_parse_req(STRING, name, "name"),
	/* skipping old_name */
	/* skipping uid (should always be 0) */
};
#undef _add_parse
#undef _add_parse_req

#define _add_flag(flagn, flagv) \
	add_parser_enum_flag(slurmdb_job_rec_t, flags, flagn, flagv)
static const parser_enum_t parser_job_flags[] = {
	_add_flag(SLURMDB_JOB_CLEAR_SCHED, "CLEAR_SCHEDULING"),
	_add_flag(SLURMDB_JOB_FLAG_NOTSET, "NOT_SET"),
	_add_flag(SLURMDB_JOB_FLAG_SUBMIT, "STARTED_ON_SUBMIT"),
	_add_flag(SLURMDB_JOB_FLAG_SCHED, "STARTED_ON_SCHEDULE"),
	_add_flag(SLURMDB_JOB_FLAG_BACKFILL, "STARTED_ON_BACKFILL"),
};
#undef _add_flag

#define _add_parse(mtype, field, path) \
	add_parser(slurmdb_job_rec_t, mtype, false, field, path)
/* should mirror the structure of slurmdb_job_rec_t  */
static const parser_t parse_job[] = {
	_add_parse(STRING, account, "account"),
	_add_parse(STRING, admin_comment, "comment/administrator"),
	_add_parse(UINT32, alloc_nodes, "allocation_nodes"),
	_add_parse(UINT32, array_job_id, "array/job_id"),
	_add_parse(UINT32, array_max_tasks, "array/limits/max/running/tasks"),
	_add_parse(STRING, array_task_str, "array/task"),
	_add_parse(UINT32, array_task_id, "array/task_id"),
	_add_parse(ASSOC_ID, associd, "association"),
	/* skip blockid (deprecated bluegene) */
	_add_parse(STRING, cluster, "cluster"),
	_add_parse(STRING, constraints, "constraints"),
	/* skip db_index */
	_add_parse(JOB_EXIT_CODE, derived_ec, "derived_exit_code"),
	_add_parse(STRING, derived_es, "comment/job"),
	_add_parse(UINT32, elapsed, "time/elapsed"),

	_add_parse(UINT32, eligible, "time/eligible"),
	_add_parse(UINT32, end, "time/end"),
	_add_parse(JOB_EXIT_CODE, exitcode, "exit_code"),
	add_parser_flags(parser_job_flags, slurmdb_job_rec_t, false, flags,
			 "flags"),
	/* skipping first_step_ptr (already added in steps) */
	_add_parse(GROUP_ID, gid, "group"),
	_add_parse(UINT32, het_job_id, "het/job_id"),
	_add_parse(UINT32, het_job_offset, "het/job_offset"),
	_add_parse(UINT32, jobid, "job_id"),
	_add_parse(STRING, jobname, "name"),
	/* skip lft */
	_add_parse(STRING, mcs_label, "mcs/label"),
	_add_parse(STRING, nodes, "nodes"),
	_add_parse(STRING, partition, "partition"),
	_add_parse(UINT32, priority, "priority"),
	_add_parse(QOS_ID, qosid, "qos"),
	_add_parse(UINT32, req_cpus, "required/CPUs"),
	_add_parse(UINT32, req_mem, "required/memory"),
	_add_parse(USER_ID, requid, "kill_request_user"),
	_add_parse(UINT32, resvid, "reservation/id"),
	_add_parse(UINT32, resv_name, "reservation/name"),
	/* skipping show_full */
	_add_parse(UINT32, eligible, "time/start"),
	_add_parse(JOB_REASON, state, "state/current"),
	_add_parse(JOB_REASON, state_reason_prev, "state/previous"),
	_add_parse(UINT32, submit, "time/submission"),
	_add_parse(JOB_STEPS, steps, "steps"),
	_add_parse(UINT32, suspended, "time/suspended"),
	_add_parse(STRING, system_comment, "comment/system"),
	_add_parse(UINT32, sys_cpu_sec, "time/system/seconds"),
	_add_parse(UINT32, sys_cpu_usec, "time/system/microseconds"),
	_add_parse(UINT32, timelimit, "time/limit"),
	_add_parse(UINT32, tot_cpu_sec, "time/total/seconds"),
	_add_parse(UINT32, tot_cpu_usec, "time/total/microseconds"),
	/* skipping track steps */
	_add_parse(TRES_LIST, tres_alloc_str, "tres/allocated"),
	_add_parse(TRES_LIST, tres_req_str, "tres/requested"),
	/* skipping uid (dup with user below) */
	/* skipping alloc_gres (dup with TRES) */
	/* skipping uid */
	_add_parse(STRING, user, "user"),
	_add_parse(UINT32, user_cpu_sec, "time/user/seconds"),
	_add_parse(UINT32, user_cpu_usec, "time/user/microseconds"),
	_add_parse(WCKEY_TAG, wckey, "wckey"),
	/* skipping wckeyid (redundant) */
	_add_parse(STRING, work_dir, "working_directory"),
};
#undef _add_parse

static const parser_enum_t parser_acct_flags[] = {
	add_parser_enum_flag(slurmdb_account_rec_t, flags,
			     SLURMDB_ACCT_FLAG_DELETED, "DELETED"),
};

#define _add_parse(mtype, field, path) \
	add_parser(slurmdb_account_rec_t, mtype, false, field, path)
/* should mirror the structure of slurmdb_account_rec_t */
static const parser_t parse_acct[] = {
	_add_parse(ASSOC_SHORT_LIST, assoc_list, "associations"),
	_add_parse(COORD_LIST, coordinators, "coordinators"),
	_add_parse(STRING, description, "description"),
	_add_parse(STRING, name, "name"),
	_add_parse(STRING, organization, "organization"),
	add_parser_flags(parser_acct_flags, slurmdb_account_rec_t, false, flags,
			 "flags"),
};
#undef _add_parse

#define _add_parse(mtype, field, path) \
	add_parser(slurmdb_coord_rec_t, mtype, false, field, path)
#define _add_parse_req(mtype, field, path) \
	add_parser(slurmdb_coord_rec_t, mtype, true, field, path)
/* should mirror the structure of slurmdb_coord_rec_t  */
static const parser_t parse_coord[] = {
	_add_parse_req(STRING, name, "name"),
	_add_parse(UINT16, direct, "direct"),
};
#undef _add_parse
#undef _add_parse_req

static const parser_enum_t parser_wckey_flags[] = {
	add_parser_enum_flag(slurmdb_wckey_rec_t, flags,
			     SLURMDB_WCKEY_FLAG_DELETED, "DELETED"),
	add_parse_enum_bool(slurmdb_wckey_rec_t, is_def, "DEFAULT"),
};

#define _add_parse(mtype, field, path) \
	add_parser(slurmdb_wckey_rec_t, mtype, false, field, path)
#define _add_parse_req(mtype, field, path) \
	add_parser(slurmdb_wckey_rec_t, mtype, true, field, path)
/* should mirror the structure of slurmdb_wckey_rec_t */
static const parser_t parse_wckey[] = {
	_add_parse(ACCOUNT_LIST, accounting_list, "accounts"),
	_add_parse_req(STRING, cluster, "cluster"),
	_add_parse_req(UINT32, id, "id"),
	_add_parse_req(STRING, name, "name"),
	_add_parse_req(STRING, user, "user"),
	/* skipping uid */
	add_parser_flags(parser_wckey_flags, slurmdb_wckey_rec_t, false, flags,
			 "flags"),
};
#undef _add_parse
#undef _add_parse_req

#define _add_parse(mtype, field, path) \
	add_parser(slurmdb_tres_rec_t, mtype, false, field, path)
#define _add_parse_req(mtype, field, path) \
	add_parser(slurmdb_tres_rec_t, mtype, true, field, path)
/* should mirror the structure of slurmdb_tres_rec_t  */
static const parser_t parse_tres[] = {
	/* skip alloc_secs (sreport func) */
	/* skip rec_count (not packed) */
	_add_parse_req(STRING, type, "type"),
	_add_parse(STRING, name, "name"),
	_add_parse(UINT32, id, "id"),
	_add_parse(INT64, count, "count"),
};
#undef _add_parse
#undef _add_parse_req

#define _add_flag(flagn, flagstr) \
	add_parser_enum_flag(slurmdb_qos_rec_t, flags, flagn, flagstr)
static const parser_enum_t parser_qos_flags[] = {
	/* skipping QOS_FLAG_BASE */
	/* skipping QOS_FLAG_NOTSET */
	/* skipping QOS_FLAG_ADD */
	/* skipping QOS_FLAG_REMOVE */
	_add_flag(QOS_FLAG_PART_MIN_NODE, "PARTITION_MINIMUM_NODE"),
	_add_flag(QOS_FLAG_PART_MAX_NODE, "PARTITION_MAXIMUM_NODE"),
	_add_flag(QOS_FLAG_PART_TIME_LIMIT, "PARTITION_TIME_LIMIT"),
	_add_flag(QOS_FLAG_ENFORCE_USAGE_THRES, "ENFORCE_USAGE_THRESHOLD"),
	_add_flag(QOS_FLAG_NO_RESERVE, "NO_RESERVE"),
	_add_flag(QOS_FLAG_REQ_RESV, "REQUIRED_RESERVATION"),
	_add_flag(QOS_FLAG_DENY_LIMIT, "DENY_LIMIT"),
	_add_flag(QOS_FLAG_OVER_PART_QOS, "OVERRIDE_PARTITION_QOS"),
	_add_flag(QOS_FLAG_NO_DECAY, "NO_DECAY"),
	_add_flag(QOS_FLAG_USAGE_FACTOR_SAFE, "USAGE_FACTOR_SAFE"),
};
#undef _add_flag

#define _add_flag(flagn, flagstr) \
	add_parser_enum_flag(slurmdb_qos_rec_t, preempt_mode, flagn, flagstr)
static const parser_enum_t parser_qos_preempt_flags[] = {
	_add_flag(PREEMPT_MODE_SUSPEND, "SUSPEND"),
	_add_flag(PREEMPT_MODE_REQUEUE, "REQUEUE"),
	_add_flag(PREEMPT_MODE_CANCEL, "CANCEL"),
	_add_flag(PREEMPT_MODE_GANG, "GANG"),
	/* skip PREEMPT_MODE_OFF (it is implied by empty list) */
};
#undef _add_flag

#define _add_parse(mtype, field, path) \
	add_parser(slurmdb_qos_rec_t, mtype, false, field, path)
#define _add_parse_req(mtype, field, path) \
	add_parser(slurmdb_qos_rec_t, mtype, true, field, path)
/* should mirror the structure of slurmdb_qos_rec_t */
static const parser_t parse_qos[] = {
	/* skipping accounting_list */
	_add_parse(STRING, description, "description"),
	add_parser_flags(parser_qos_flags, slurmdb_qos_rec_t, false, flags,
			 "flags"),
	_add_parse(UINT32, id, "id"),
	_add_parse(UINT32, grace_time, "limits/grace_time"),
	_add_parse(UINT32, grp_jobs_accrue, "limits/max/active_jobs/accruing"),
	_add_parse(UINT32, grp_jobs, "limits/max/active_jobs/count"),
	_add_parse(TRES_LIST, grp_tres, "limits/max/tres/total"),
	/* skipping grp_tres_ctld (not packed) */
	_add_parse(TRES_LIST, grp_tres_run_mins,
		   "limits/max/tres/minutes/per/qos"),
	/* skipping grp_tres_run_mins_ctld (not packed) */
	_add_parse_req(STRING, name, "name"),
	_add_parse(UINT32, grp_wall, "limits/max/wall_clock/per/qos"),
	_add_parse(UINT32, max_jobs_pa, "limits/max/jobs/per/account"),
	_add_parse(UINT32, max_jobs_pu, "limits/max/jobs/per/user"),
	_add_parse(UINT32, max_jobs_accrue_pa,
		   "limits/max/accruing/per/account"),
	_add_parse(UINT32, max_jobs_accrue_pu, "limits/max/accruing/per/user"),
	_add_parse(TRES_LIST, max_tres_mins_pj,
		   "limits/max/tres/minutes/per/job"),
	/* skipping max_tres_mins_pj_ctld (not packed) */
	_add_parse(TRES_LIST, max_tres_pa, "limits/max/tres/per/account"),
	/* skipping max_tres_pa_ctld (not packed) */
	_add_parse(TRES_LIST, max_tres_pj, "limits/max/tres/per/job"),
	/* skipping max_tres_pj_ctld (not packed) */
	_add_parse(TRES_LIST, max_tres_pn, "limits/max/tres/per/node"),
	/* skipping max_tres_pn_ctld (not packed) */
	_add_parse(TRES_LIST, max_tres_pu, "limits/max/tres/per/user"),
	/* skipping max_tres_pu_ctld (not packed) */
	_add_parse(TRES_LIST, max_tres_run_mins_pa,
		   "limits/max/tres/minutes/per/account"),
	/* skipping max_tres_run_mins_pa_ctld (not packed) */
	_add_parse(TRES_LIST, max_tres_run_mins_pu,
		   "limits/max/tres/minutes/per/user"),
	/* skipping max_tres_run_mins_pu_ctld (not packed) */
	_add_parse(UINT32, max_wall_pj, "limits/max/wall_clock/per/job"),
	_add_parse(UINT32, min_prio_thresh, "limits/min/priority_threshold"),
	_add_parse(TRES_LIST, min_tres_pj, "limits/min/tres/per/job"),
	/* skipping min_tres_pj_ctld (not packed) */
	add_parser_qos_preempt(slurmdb_qos_rec_t, false, preempt_bitstr,
			       preempt_list, "preempt/list"),
	/* skip preempt_list (only for ops) */
	add_parser_flags(parser_qos_preempt_flags, slurmdb_qos_rec_t, false,
			 preempt_mode, "preempt/mode"),
	_add_parse(UINT32, preempt_exempt_time, "preempt/exempt_time"),
	_add_parse(UINT32, priority, "priority"),
	/* skip usage (not packed) */
	_add_parse(FLOAT64, usage_factor, "usage_factor"),
	_add_parse(FLOAT64, usage_thres, "usage_threshold"),
	/* skip blocked_until (not packed) */
};
#undef _add_parse
#undef _add_parse_req

#define _add_flag(flagn, flagv) \
	add_parser_enum_flag(slurmdb_step_rec_t, req_cpufreq_gov, flagn, flagv)
static const parser_enum_t parse_job_step_cpu_freq_flags[] = {
	_add_flag(CPU_FREQ_CONSERVATIVE, "Conservative"),
	_add_flag(CPU_FREQ_PERFORMANCE, "Performance"),
	_add_flag(CPU_FREQ_POWERSAVE, "PowerSave"),
	_add_flag(CPU_FREQ_ONDEMAND, "OnDemand"),
	_add_flag(CPU_FREQ_USERSPACE, "UserSpace"),
};
#undef _add_flag

#define _add_parse(mtype, field, path) \
	add_parser(slurmdb_step_rec_t, mtype, false, field, path)
#define _tres3(count, node, task, path)                                      \
	add_parser_tres(slurmdb_step_rec_t, false, count, node, task, nodes, \
			path)
/* should mirror the structure of slurmdb_step_rec_t   */
static const parser_t parse_job_step[] = {
	_add_parse(UINT32, elapsed, "time/elapsed"),
	_add_parse(UINT32, end, "time/end"),
	_add_parse(JOB_EXIT_CODE, exitcode, "exit_code"),
	/* skipping job_ptr (redundant here) */
	_add_parse(UINT32, nnodes, "nodes/count"),
	_add_parse(STRING, nodes, "nodes/range"),
	_add_parse(UINT32, ntasks, "tasks/count"),
	_add_parse(STRING, pid_str, "pid"),
	_add_parse(UINT32, req_cpufreq_min, "CPU/requested_frequency/min"),
	_add_parse(UINT32, req_cpufreq_max, "CPU/requested_frequency/max"),
	add_parser_flags(parse_job_step_cpu_freq_flags, slurmdb_step_rec_t,
			 false, req_cpufreq_gov, "CPU/governor"),
	_add_parse(USER_ID, requid, "kill_request_user"),
	_add_parse(UINT32, start, "time/start"),
	_add_parse(JOB_REASON, state, "state"),
	_add_parse(UINT32, stats.act_cpufreq,
		   "statistics/CPU/actual_frequency"),
	_add_parse(UINT32, stats.consumed_energy, "statistics/energy/consumed"),
	_add_parse(UINT32, step_id.job_id, "step/job_id"),
	_add_parse(UINT32, step_id.step_het_comp, "step/het/component"),
	_add_parse(STEP_ID, step_id.step_id, "step/id"),
	_add_parse(STRING, stepname, "step/name"),
	_add_parse(UINT32, suspended, "time/suspended"),
	_add_parse(UINT32, sys_cpu_sec, "time/system/seconds"),
	_add_parse(UINT32, sys_cpu_usec, "time/system/microseconds"),
	_add_parse(TASK_DISTRIBUTION, task_dist, "task/distribution"),
	_add_parse(UINT32, tot_cpu_sec, "time/total/seconds"),
	_add_parse(UINT32, tot_cpu_usec, "time/total/microseconds"),
	_add_parse(UINT32, user_cpu_sec, "time/user/seconds"),
	_add_parse(UINT32, user_cpu_usec, "time/user/microseconds"),

	_add_parse(TRES_LIST, stats.tres_usage_in_ave,
		   "tres/requested/average"),
	_tres3(stats.tres_usage_in_max, stats.tres_usage_in_max_nodeid,
	       stats.tres_usage_in_max_taskid, "tres/requested/max"),
	_tres3(stats.tres_usage_in_min, stats.tres_usage_in_min_nodeid,
	       stats.tres_usage_in_min_taskid, "tres/requested/min"),
	_add_parse(TRES_LIST, stats.tres_usage_in_tot, "tres/requested/total"),
	_add_parse(TRES_LIST, stats.tres_usage_out_ave,
		   "tres/consumed/average"),
	_tres3(stats.tres_usage_out_max, stats.tres_usage_out_max_nodeid,
	       stats.tres_usage_out_max_taskid, "tres/consumed/max"),
	_tres3(stats.tres_usage_out_min, stats.tres_usage_out_min_nodeid,
	       stats.tres_usage_out_min_taskid, "tres/consumed/min"),
	_add_parse(TRES_LIST, stats.tres_usage_out_tot, "tres/consumed/total"),
	_add_parse(TRES_LIST, tres_alloc_str, "tres/allocated"),
};
#undef _add_parse
#undef _tres3

#define _add_parse(mtype, field, path) \
	add_parser(slurmdb_stats_rec_t, mtype, false, field, path)
/* should mirror the structure of slurmdb_stats_rec_t */
static const parser_t parse_stats_rec[] = {
	_add_parse(UINT32, time_start, "time_start"),
	_add_parse(STATS_REC_ARRAY, dbd_rollup_stats, "rollups"),
	_add_parse(STATS_RPC_LIST, rpc_list, "RPCs"),
	_add_parse(STATS_USER_LIST, user_list, "users"),
};
#undef _add_parse

#define _add_parse(mtype, field, path) \
	add_parser(slurmdb_rpc_obj_t, mtype, false, field, path)
/* should mirror the structure of slurmdb_rpc_obj_t */
static const parser_t parse_stats_user_rpcs[] = {
	_add_parse(USER_ID, id, "user"),
	_add_parse(UINT32, cnt, "count"),
	_add_parse(UINT64, time_ave, "time/average"),
	_add_parse(UINT64, time, "time/total"),
};
#undef _add_parse

#define _add_parse(mtype, field, path) \
	add_parser(slurmdb_rpc_obj_t, mtype, false, field, path)
/* should mirror the structure of slurmdb_rpc_obj_t */
static const parser_t parse_stats_rpcs[] = {
	_add_parse(RPC_ID, id, "rpc"),
	_add_parse(UINT32, cnt, "count"),
	_add_parse(UINT64, time_ave, "time/average"),
	_add_parse(UINT64, time, "time/total"),
};
#undef _add_parse

#define _add_flag(flagn, flagstr) \
	add_parser_enum_flag(slurmdb_cluster_rec_t, flags, flagn, flagstr)
static const parser_enum_t parse_cluster_rec_flags[] = {
	_add_flag(CLUSTER_FLAG_MULTSD, "MULTIPLE_SLURMD"),
	_add_flag(CLUSTER_FLAG_FE, "FRONT_END"),
	_add_flag(CLUSTER_FLAG_CRAY_N, "CRAY_NATIVE"),
	_add_flag(CLUSTER_FLAG_FED, "FEDERATION"),
	_add_flag(CLUSTER_FLAG_EXT, "EXTERNAL"),
};
#undef _add_flag

#define _add_parse(mtype, field, path) \
	add_parser(slurmdb_cluster_rec_t, mtype, false, field, path)
/* should mirror the structure of slurmdb_cluster_rec_t */
static const parser_t parse_cluster_rec[] = {
	/* skip accounting (sreport func only) */
	/* skip classification (to be deprecated) */
	/* skip comm_fail_time (not packed) */
	/* skip control_addr (not packed) */
	_add_parse(STRING, control_host, "controller/host"),
	_add_parse(UINT32, control_port, "controller/port"),
	/* skip dim_size (BG deprecated) */
	/* skip fed[eration] support */
	add_parser_flags(parse_cluster_rec_flags, slurmdb_cluster_rec_t, false,
			 flags, "flags"),
	/* skip lock (not packed) */
	_add_parse(STRING, name, "name"),
	_add_parse(STRING, nodes, "nodes"),
	_add_parse(SELECT_PLUGIN_ID, plugin_id_select, "select_plugin"),
	_add_parse(ASSOC_SHORT, root_assoc, "associations/root"),
	_add_parse(UINT16, rpc_version, "rpc_version"),
	/* skip send_rpc (not packed) */
	_add_parse(TRES_LIST, tres_str, "tres"),
};
#undef _add_parse

#define _add_parse(mtype, field, path) \
	add_parser(slurmdb_cluster_accounting_rec_t, mtype, false, field, path)
/* should mirror the structure of slurmdb_cluster_accounting_rec_t */
static const parser_t parse_cluster_accounting_rec[] = {
	_add_parse(UINT64, alloc_secs, "time/allocated"),
	_add_parse(UINT64, down_secs, "time/down"),
	_add_parse(UINT64, idle_secs, "time/idle"),
	_add_parse(UINT64, over_secs, "time/overcommitted"),
	_add_parse(UINT64, pdown_secs, "time/planned_down"),
	_add_parse(UINT64, period_start, "time/start"),
	_add_parse(UINT64, period_start, "time/reserved"),
	_add_parse(STRING, tres_rec.name, "tres/name"),
	_add_parse(STRING, tres_rec.type, "tres/type"),
	_add_parse(UINT32, tres_rec.id, "tres/id"),
	_add_parse(UINT64, tres_rec.count, "tres/count"),
};
#undef _add_parse

static int _parse_to_string(const parser_t *const parse, void *obj, data_t *str,
			    data_t *errors, const parser_env_t *penv)
{
	int rc = SLURM_SUCCESS;
	char **dst = (((void *)obj) + parse->field_offset);

	if (data_get_type(str) == DATA_TYPE_NULL) {
		xfree(*dst);
	} else if (data_convert_type(str, DATA_TYPE_STRING) ==
		   DATA_TYPE_STRING) {
		xfree(*dst);
		*dst = xstrdup(data_get_string(str));
	} else {
		rc = ESLURM_DATA_CONV_FAILED;
	}

	debug5("%s: string %s rc[%d]=%s", __func__, *dst, rc,
	       slurm_strerror(rc));

	return rc;
}

static int _dump_to_string(const parser_t *const parse, void *obj, data_t *data,
			   const parser_env_t *penv)
{
	int rc = SLURM_SUCCESS;
	char **src = (((void *)obj) + parse->field_offset);

	if (*src)
		data_set_string(data, *src);
	else
		data_set_null(data);

	return rc;
}

static int _parse_to_float128(const parser_t *const parse, void *obj,
			      data_t *str, data_t *errors,
			      const parser_env_t *penv)
{
	long double *dst = (((void *)obj) + parse->field_offset);
	int rc = SLURM_SUCCESS;

	xassert(sizeof(long double) * 8 == 128);

	if (data_get_type(str) == DATA_TYPE_NULL)
		*dst = (double)NO_VAL;
	else if (data_convert_type(str, DATA_TYPE_FLOAT) == DATA_TYPE_FLOAT)
		*dst = data_get_float(str);
	else
		rc = ESLURM_DATA_CONV_FAILED;

	log_flag(DATA, "%s: string %Lf rc[%d]=%s", __func__, *dst, rc,
		 slurm_strerror(rc));

	return rc;
}

static int _dump_to_float128(const parser_t *const parse, void *obj,
			     data_t *dst, const parser_env_t *penv)
{
	long double *src = (((void *)obj) + parse->field_offset);

	xassert(data_get_type(dst) == DATA_TYPE_NULL);

	/* see bug#9674 */
	if (((uint32_t)*src == INFINITE) || ((uint32_t)*src == NO_VAL))
		data_set_null(dst);
	else
		(void)data_set_float(dst, *src);

	return SLURM_SUCCESS;
}

static int _parse_to_float64(const parser_t *const parse, void *obj,
			     data_t *str, data_t *errors,
			     const parser_env_t *penv)
{
	double *dst = (((void *)obj) + parse->field_offset);
	int rc = SLURM_SUCCESS;

	xassert(sizeof(double) * 8 == 64);

	if (data_get_type(str) == DATA_TYPE_NULL)
		*dst = (double)NO_VAL;
	else if (data_convert_type(str, DATA_TYPE_FLOAT) == DATA_TYPE_FLOAT)
		*dst = data_get_float(str);
	else
		rc = ESLURM_DATA_CONV_FAILED;

	log_flag(DATA, "%s: string %f rc[%d]=%s", __func__, *dst, rc,
		 slurm_strerror(rc));

	return rc;
}

static int _dump_to_float64(const parser_t *const parse, void *obj, data_t *dst,
			    const parser_env_t *penv)
{
	double *src = (((void *)obj) + parse->field_offset);

	xassert(data_get_type(dst) == DATA_TYPE_NULL);

	/* see bug#9674 */
	if (((uint32_t)*src == INFINITE) || ((uint32_t)*src == NO_VAL))
		(void)data_set_null(dst);
	else
		(void)data_set_float(dst, *src);

	return SLURM_SUCCESS;
}

static int _parse_to_int64(const parser_t *const parse, void *obj, data_t *str,
			   data_t *errors, const parser_env_t *penv)
{
	int64_t *dst = (((void *)obj) + parse->field_offset);
	int rc = SLURM_SUCCESS;

	if (data_get_type(str) == DATA_TYPE_NULL)
		*dst = (double)NO_VAL;
	else if (data_convert_type(str, DATA_TYPE_FLOAT) == DATA_TYPE_FLOAT)
		*dst = data_get_float(str);
	else
		rc = ESLURM_DATA_CONV_FAILED;

	log_flag(DATA, "%s: string %zd rc[%d]=%s", __func__, *dst, rc,
		 slurm_strerror(rc));

	return rc;
}

static int _dump_to_int64(const parser_t *const parse, void *obj, data_t *dst,
			  const parser_env_t *penv)
{
	int64_t *src = (((void *)obj) + parse->field_offset);

	xassert(data_get_type(dst) == DATA_TYPE_NULL);

	/* Never set values of INF or NO_VAL */
	if ((*src == NO_VAL64) || (*src == INFINITE64))
		(void)data_set_null(dst);
	else
		(void)data_set_int(dst, *src);

	return SLURM_SUCCESS;
}

static int _parse_to_uint16(const parser_t *const parse, void *obj, data_t *str,
			    data_t *errors, const parser_env_t *penv)
{
	uint16_t *dst = (((void *)obj) + parse->field_offset);
	int rc = SLURM_SUCCESS;

	if (data_get_type(str) == DATA_TYPE_NULL)
		*dst = 0;
	else if (data_convert_type(str, DATA_TYPE_INT_64) == DATA_TYPE_INT_64)
		*dst = data_get_int(str);
	else
		rc = ESLURM_DATA_CONV_FAILED;

	log_flag(DATA, "%s: string %hu rc[%d]=%s", __func__, *dst, rc,
		 slurm_strerror(rc));

	return rc;
}

static int _dump_to_uint16(const parser_t *const parse, void *obj, data_t *dst,
			   const parser_env_t *penv)
{
	int16_t *src = (((void *)obj) + parse->field_offset);

	xassert(data_get_type(dst) == DATA_TYPE_NULL);

	/* Never set values of INF or NO_VAL */
	if ((*src == NO_VAL16) || (*src == INFINITE16))
		data_set_null(dst);
	else
		(void)data_set_int(dst, *src);

	return SLURM_SUCCESS;
}

static int _parse_to_uint64(const parser_t *const parse, void *obj, data_t *str,
			    data_t *errors, const parser_env_t *penv)
{
	uint64_t *dst = (((void *)obj) + parse->field_offset);
	int rc = SLURM_SUCCESS;

	if (data_get_type(str) == DATA_TYPE_NULL)
		*dst = 0;
	else if (data_convert_type(str, DATA_TYPE_INT_64) == DATA_TYPE_INT_64)
		*dst = data_get_int(str);
	else
		rc = ESLURM_DATA_CONV_FAILED;

	log_flag(DATA, "%s: string %zu rc[%d]=%s", __func__, *dst, rc,
		 slurm_strerror(rc));

	return rc;
}

static int _dump_to_uint64(const parser_t *const parse, void *obj, data_t *dst,
			   const parser_env_t *penv)
{
	int64_t *src = (((void *)obj) + parse->field_offset);

	xassert(data_get_type(dst) == DATA_TYPE_NULL);

	/* Never set values of INF or NO_VAL */
	if ((*src == NO_VAL64) || (*src == INFINITE64))
		data_set_null(dst);
	else
		(void)data_set_int(dst, *src);

	return SLURM_SUCCESS;
}

static int _parse_to_uint32(const parser_t *const parse, void *obj, data_t *str,
			    data_t *errors, const parser_env_t *penv)
{
	uint32_t *dst = (((void *)obj) + parse->field_offset);
	int rc = SLURM_SUCCESS;

	if (data_get_type(str) == DATA_TYPE_NULL) {
		*dst = 0;
	} else if (data_convert_type(str, DATA_TYPE_INT_64) ==
		   DATA_TYPE_INT_64) {
		/* catch -1 and set to NO_VAL instead of rolling */
		if (0xFFFFFFFF00000000 & data_get_int(str))
			*dst = NO_VAL;
		else
			*dst = data_get_int(str);
	} else
		rc = ESLURM_DATA_CONV_FAILED;

	log_flag(DATA, "%s: string %u rc[%d]=%s", __func__, *dst, rc,
		 slurm_strerror(rc));

	return rc;
}

static int _dump_to_uint32(const parser_t *const parse, void *obj, data_t *dst,
			   const parser_env_t *penv)
{
	uint32_t *src = (((void *)obj) + parse->field_offset);

	xassert(data_get_type(dst) == DATA_TYPE_NULL);

	if ((*src == NO_VAL) || (*src == INFINITE))
		data_set_null(dst);
	else
		(void)data_set_int(dst, *src);

	return SLURM_SUCCESS;
}

#define MAGIC_FOREACH_PARSE_FLAGS 0xba2d2a13
typedef struct {
	int magic;
	uint32_t *flags;
	data_t *errors;
	const parser_t *const parse;
	void *obj;
} for_each_parse_flag_t;

static data_for_each_cmd_t _for_each_parse_flag(data_t *data, void *arg)
{
	for_each_parse_flag_t *args = arg;

	xassert(args->magic == MAGIC_FOREACH_PARSE_FLAGS);

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

	for (int i = 0; i < args->parse->per_type.flags.count; i++) {
		const parser_enum_t *f = (args->parse->per_type.flags.list + i);

		bool match = !xstrcasecmp(data_get_string(data), f->string);

		if (f->type == PARSER_ENUM_FLAG_BIT) {
			const size_t b = f->size;
			if (!match)
				continue;

			/* C allows complier to choose a size for the enum */
			if (b == sizeof(uint64_t)) {
				uint64_t *flags = (((void *)args->obj) +
						   args->parse->field_offset);
				*flags |= f->flag;
			} else if (b == sizeof(uint32_t)) {
				uint32_t *flags = (((void *)args->obj) +
						   args->parse->field_offset);
				*flags |= f->flag;
			} else if (b == sizeof(uint16_t)) {
				uint16_t *flags = (((void *)args->obj) +
						   args->parse->field_offset);
				*flags |= f->flag;
			} else if (b == sizeof(uint8_t)) {
				uint8_t *flags = (((void *)args->obj) +
						  args->parse->field_offset);
				*flags |= f->flag;
			} else
				fatal("%s: unexpected enum size: %zu", __func__,
				      b);
		} else if (f->type == PARSER_ENUM_FLAG_BOOL) {
			const size_t b = f->size;

			if (!match)
				continue;

			/*
			 * flag applies to a bool in the obj structure and not
			 * in obj->flags.
			 * Set true while being aware of the size of the
			 * original variable since it may move around the 1.
			 */

			if (b == sizeof(uint64_t)) {
				uint64_t *ptr = (((void *)args->obj) +
						 f->field_offset);
				*ptr = true;
			} else if (b == sizeof(uint32_t)) {
				uint32_t *ptr = (((void *)args->obj) +
						 f->field_offset);
				*ptr = true;
			} else if (b == sizeof(uint16_t)) {
				uint16_t *ptr = (((void *)args->obj) +
						 f->field_offset);
				*ptr = true;
			} else if (b == sizeof(uint8_t)) {
				uint8_t *ptr = (((void *)args->obj) +
						f->field_offset);
				*ptr = true;
			} else
				fatal("%s: unexpected bool size: %zu", __func__,
				      b);
		} else
			fatal("%s: unexpect type", __func__);
	}

	return DATA_FOR_EACH_CONT;
}

static int _parse_flags(const parser_t *const parse, void *obj, data_t *src,
			data_t *errors, const parser_env_t *penv)
{
	for_each_parse_flag_t args = {
		.errors = errors,
		.magic = MAGIC_FOREACH_PARSE_FLAGS,
		.obj = obj,
		.parse = parse,
	};

	if (data_get_type(src) != DATA_TYPE_LIST)
		return ESLURM_REST_FAIL_PARSING;

	if (data_list_for_each(src, _for_each_parse_flag, &args) < 0)
		return ESLURM_REST_FAIL_PARSING;

	return SLURM_SUCCESS;
	;
}

static int _dump_flags(const parser_t *const parse, void *obj, data_t *data,
		       const parser_env_t *penv)
{
	xassert(data_get_type(data) == DATA_TYPE_NULL);
	data_set_list(data);

	for (int i = 0; i < parse->per_type.flags.count; i++) {
		const parser_enum_t *f = (parse->per_type.flags.list + i);
		bool found = false;

		if (f->type == PARSER_ENUM_FLAG_BIT) {
			const size_t b = f->size;

			/* C allows complier to choose a size for the enum */
			if (b == sizeof(uint64_t)) {
				uint64_t *flags = (((void *)obj) +
						   parse->field_offset);
				if (*flags & f->flag)
					found = true;
			} else if (b == sizeof(uint32_t)) {
				uint32_t *flags = (((void *)obj) +
						   parse->field_offset);
				if (*flags & f->flag)
					found = true;
			} else if (b == sizeof(uint16_t)) {
				uint16_t *flags = (((void *)obj) +
						   parse->field_offset);
				if (*flags & f->flag)
					found = true;
			} else if (b == sizeof(uint8_t)) {
				uint8_t *flags = (((void *)obj) +
						  parse->field_offset);
				if (*flags & f->flag)
					found = true;
			} else
				fatal("%s: unexpected enum size: %zu", __func__,
				      b);

		} else if (f->type == PARSER_ENUM_FLAG_BOOL) {
			const size_t b = f->size;

			if (b == sizeof(uint64_t)) {
				uint64_t *ptr = (((void *)obj) +
						 f->field_offset);
				if (*ptr)
					found = true;
			} else if (b == sizeof(uint32_t)) {
				uint32_t *ptr = (((void *)obj) +
						 f->field_offset);
				if (*ptr)
					found = true;
			} else if (b == sizeof(uint16_t)) {
				uint16_t *ptr = (((void *)obj) +
						 f->field_offset);
				if (*ptr)
					found = true;
			} else if (b == sizeof(uint8_t)) {
				uint8_t *ptr = (((void *)obj) +
						f->field_offset);
				if (*ptr)
					found = true;
			} else
				fatal("%s: unexpected bool size: %zu", __func__,
				      b);

		} else
			fatal("%s: unknown flag type", __func__);

		if (found)
			data_set_string(data_list_append(data), f->string);
	}

	return SLURM_SUCCESS;
	;
}

#define MAGIC_FOREACH_PARSE_QOS 0xabaa2c18
typedef struct {
	int magic;
	List qos_list;
	data_t *errors;
} for_each_parse_qos_t;

static data_for_each_cmd_t _for_each_parse_qos(data_t *data, void *arg)
{
	for_each_parse_qos_t *args = arg;
	data_t *name;

	xassert(args->magic == MAGIC_FOREACH_PARSE_QOS);

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

	/*
	 * Note: we ignore everything but name for loading QOS into an
	 * qos_list as that is the only field accepted
	 */

	if (!(name = data_key_get(data, "name")) ||
	    data_convert_type(name, DATA_TYPE_STRING) != DATA_TYPE_STRING)
		return DATA_FOR_EACH_FAIL;

	(void)list_append(args->qos_list, xstrdup(data_get_string(name)));

	return DATA_FOR_EACH_CONT;
}

static int _parse_qos_str_id(const parser_t *const parse, void *obj,
			     data_t *src, data_t *errors,
			     const parser_env_t *penv)
{
	char *qos_name = NULL;
	uint32_t *qos_id = (((void *)obj) + parse->field_offset);
	slurmdb_qos_rec_t *qos;

	if (data_get_type(src) == DATA_TYPE_NULL) {
		*qos_id = 0;
		return SLURM_SUCCESS;
	} else if (data_convert_type(src, DATA_TYPE_STRING) ==
		   DATA_TYPE_STRING) {
		qos_name = data_get_string(src);
	} else
		return ESLURM_DATA_CONV_FAILED;

	if (!qos_name || !qos_name[0])
		return ESLURM_DATA_CONV_FAILED;

	/* find qos by name from global list */
	xassert(penv->g_qos_list);
	if (!penv->g_qos_list)
		return ESLURM_REST_EMPTY_RESULT;

	qos = list_find_first(penv->g_qos_list,
			      slurmdb_find_qos_in_list_by_name, qos_name);
	if (!qos)
		return ESLURM_REST_EMPTY_RESULT;

	*qos_id = qos->id;

	return SLURM_SUCCESS;
}

static int _dump_qos_str_id(const parser_t *const parse, void *obj, data_t *dst,
			    const parser_env_t *penv)
{
	uint32_t *qos_id = (((void *)obj) + parse->field_offset);
	slurmdb_qos_rec_t *qos;

	if (*qos_id == 0) {
		data_set_null(dst);
		return SLURM_SUCCESS;
	}

	/* find qos by id from global list */
	xassert(penv->g_qos_list);
	if (!penv->g_qos_list)
		return ESLURM_REST_EMPTY_RESULT;

	qos = list_find_first(penv->g_qos_list, slurmdb_find_qos_in_list,
			      qos_id);
	if (!qos)
		/* QOS has an ID but it is not found??? */
		return ESLURM_REST_EMPTY_RESULT;

	(void)data_set_string(dst, qos->name);

	return SLURM_SUCCESS;
}

static int _parse_qos_str_list(const parser_t *const parse, void *obj,
			       data_t *src, data_t *errors,
			       const parser_env_t *penv)
{
	List *qos_list = (((void *)obj) + parse->field_offset);
	for_each_parse_qos_t args = {
		.magic = MAGIC_FOREACH_PARSE_QOS,
		.errors = errors,
	};

	if (!*qos_list)
		*qos_list = list_create(xfree_ptr);

	args.qos_list = *qos_list;

	if (data_list_for_each(src, _for_each_parse_qos, &args) < 0)
		return ESLURM_REST_FAIL_PARSING;

	return SLURM_SUCCESS;
}

#define MAGIC_FOREACH_DUMP_QOS_STR_LIST 0xaaae2af2
typedef struct {
	int magic;
	data_t *qos;
} foreach_dump_qos_str_list_t;

static int _foreach_dump_qos_str_list(void *x, void *arg)
{
	char *qos = x;
	foreach_dump_qos_str_list_t *args = arg;

	xassert(args->magic == MAGIC_FOREACH_DUMP_QOS_STR_LIST);

	data_set_string(data_list_append(args->qos), qos);
	return 0;
}

static int _dump_qos_str_list(const parser_t *const parse, void *obj,
			      data_t *dst, const parser_env_t *penv)
{
	List *qos_list = (((void *)obj) + parse->field_offset);
	foreach_dump_qos_str_list_t args = {
		.magic = MAGIC_FOREACH_DUMP_QOS_STR_LIST,
		.qos = dst,
	};

	xassert(data_get_type(dst) == DATA_TYPE_NULL);
	data_set_list(dst);

	if (list_for_each(*qos_list, _foreach_dump_qos_str_list, &args) < 0)
		return ESLURM_DATA_CONV_FAILED;

	return SLURM_SUCCESS;
}

#define MAGIC_FOREACH_QOS_PREEMPT_LIST 0xa8eb1313
typedef struct {
	int magic;
	data_t *errors;
	List qos_list;
	const parser_env_t *penv;
} foreach_parse_qos_preempt_list_t;

static data_for_each_cmd_t _foreach_parse_qos_preempt_list(data_t *data,
							   void *arg)
{
	foreach_parse_qos_preempt_list_t *args = arg;

	xassert(args->magic == MAGIC_FOREACH_QOS_PREEMPT_LIST);

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

	list_append(args->qos_list, xstrdup(data_get_string(data)));
	return DATA_FOR_EACH_CONT;
}

static int _parse_qos_preempt_list(const parser_t *const parse, void *obj,
				   data_t *src, data_t *errors,
				   const parser_env_t *penv)
{
#ifndef NDEBUG
	bitstr_t **preempt_bitstr =
		(((void *)obj) +
		 parse->per_type.qos_preempt.field_offset_preempt_bitstr);
#endif
	List *preempt_list =
		(((void *)obj) +
		 parse->per_type.qos_preempt.field_offset_preempt_list);
	foreach_parse_qos_preempt_list_t args = {
		.magic = MAGIC_FOREACH_QOS_PREEMPT_LIST,
		.penv = penv,
		.qos_list = list_create(xfree_ptr),
	};

	xassert(!parse->field_offset);
	xassert(!*preempt_bitstr);

	if ((data_get_type(src) != DATA_TYPE_LIST) ||
	    (data_list_for_each(src, _foreach_parse_qos_preempt_list, &args) <
	     0)) {
		FREE_NULL_LIST(args.qos_list);
		return ESLURM_REST_FAIL_PARSING;
	}

	*preempt_list = args.qos_list;

	return SLURM_SUCCESS;
}

static int _dump_qos_preempt_list(const parser_t *const parse, void *obj,
				  data_t *dst, const parser_env_t *penv)
{
	bitstr_t **preempt_bitstr =
		(((void *)obj) +
		 parse->per_type.qos_preempt.field_offset_preempt_bitstr);
#ifndef NDEBUG
	List *preempt_list =
		(((void *)obj) +
		 parse->per_type.qos_preempt.field_offset_preempt_list);
#endif

	xassert(!parse->field_offset);
	xassert(!*preempt_list);
	xassert(penv->g_qos_list);
	xassert(data_get_type(dst) == DATA_TYPE_NULL);

	data_set_list(dst);

	if (!penv->g_qos_list)
		return ESLURM_NOT_SUPPORTED;

	if (!*preempt_bitstr)
		return SLURM_SUCCESS;

	/* based on get_qos_complete_str_bitstr() */
	for (int i = 0; (i < bit_size(*preempt_bitstr)); i++) {
		slurmdb_qos_rec_t *ptr_qos;

		if (!bit_test(*preempt_bitstr, i))
			continue;

		if (!(ptr_qos = list_find_first(penv->g_qos_list,
						slurmdb_find_qos_in_list,
						&i))) {
			/*
			 * There is a race condition here where the global
			 * QOS list could have changed betwen the query of the
			 * list and the bitstrs. Just error and have the user
			 * try again.
			 */
			error("%s: unable to find QOS with level: %u", __func__,
			      i);
			return ESLURM_DATA_CONV_FAILED;
		}

		data_set_string(data_list_append(dst), ptr_qos->name);
	}

	return SLURM_SUCCESS;
}

#define MAGIC_FOREACH_PARSE_ASSOC 0xdbed1a13
typedef struct {
	int magic;
	data_t *errors;
	List assoc_list;
	const parser_env_t *penv;
} foreach_parse_assoc_t;

static data_for_each_cmd_t _foreach_parse_assoc(data_t *data, void *arg)
{
	foreach_parse_assoc_t *args = arg;
	slurmdb_assoc_rec_t *assoc;

	xassert(args->magic == MAGIC_FOREACH_PARSE_ASSOC);

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

	assoc = xmalloc(sizeof(*assoc));
	slurmdb_init_assoc_rec(assoc, false);
	list_append(args->assoc_list, assoc);

	if (_parser_run(assoc, parse_assoc, ARRAY_SIZE(parse_assoc), data,
			args->errors, args->penv))
		return DATA_FOR_EACH_FAIL;

	return DATA_FOR_EACH_CONT;
}

static int _parse_assoc_list(const parser_t *const parse, void *obj,
			     data_t *src, data_t *errors,
			     const parser_env_t *penv)
{
	List *assoc_list = (((void *)obj) + parse->field_offset);
	foreach_parse_assoc_t assoc_args = {
		.magic = MAGIC_FOREACH_PARSE_ASSOC,
		.assoc_list = *assoc_list,
		.penv = penv,
	};

	if (data_get_type(src) != DATA_TYPE_LIST)
		return ESLURM_REST_FAIL_PARSING;

	if (data_list_for_each(src, _foreach_parse_assoc, &assoc_args) < 0)
		return ESLURM_REST_FAIL_PARSING;

	return SLURM_SUCCESS;
}

#define MAGIC_FOREACH_ASSOC 0xfefe2af3
typedef struct {
	int magic;
	data_t *assocs;
	const parser_env_t *penv;
} foreach_assoc_t;

static int _foreach_assoc(void *x, void *arg)
{
	slurmdb_assoc_rec_t *assoc = x;
	foreach_assoc_t *args = arg;

	xassert(args->magic == MAGIC_FOREACH_ASSOC);

	if (_parser_dump(assoc, parse_assoc, ARRAY_SIZE(parse_assoc),
			 data_set_dict(data_list_append(args->assocs)),
			 args->penv) < 0)
		return -1;

	return 0;
}

static int _dump_assoc_list(const parser_t *const parse, void *obj, data_t *dst,
			    const parser_env_t *penv)
{
	List *assoc_list = (((void *)obj) + parse->field_offset);
	foreach_assoc_t args = {
		.magic = MAGIC_FOREACH_ASSOC,
		.penv = penv,
	};

	xassert(data_get_type(dst) == DATA_TYPE_NULL);
	args.assocs = data_set_list(dst);

	if (!*assoc_list)
		return 0;

	if (list_for_each(*assoc_list, _foreach_assoc, &args) < 0)
		return -1;

	return 0;
}

#define MAGIC_FOREACH_PARSE_ASSOC_SHORT 0x8bbd1a00
typedef struct {
	int magic;
	data_t *errors;
	List assoc_list;
	const parser_env_t *penv;
} foreach_parse_assoc_short_t;

static data_for_each_cmd_t _foreach_parse_assoc_short(data_t *data, void *arg)
{
	foreach_parse_assoc_t *args = arg;
	slurmdb_assoc_rec_t *assoc;

	xassert(args->magic == MAGIC_FOREACH_PARSE_ASSOC_SHORT);

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

	assoc = xmalloc(sizeof(*assoc));
	slurmdb_init_assoc_rec(assoc, false);
	list_append(args->assoc_list, assoc);

	if (_parser_run(assoc, parse_assoc_short, ARRAY_SIZE(parse_assoc_short),
			data, args->errors, args->penv))
		return DATA_FOR_EACH_FAIL;

	return DATA_FOR_EACH_CONT;
}

static int _parse_assoc_short_list(const parser_t *const parse, void *obj,
				   data_t *src, data_t *errors,
				   const parser_env_t *penv)
{
	List *assoc_list = (((void *)obj) + parse->field_offset);
	foreach_parse_assoc_short_t assoc_args = {
		.magic = MAGIC_FOREACH_PARSE_ASSOC_SHORT,
		.assoc_list = *assoc_list,
		.penv = penv,
	};

	if (data_get_type(src) != DATA_TYPE_LIST)
		return ESLURM_REST_FAIL_PARSING;

	if (data_list_for_each(src, _foreach_parse_assoc_short, &assoc_args) <
	    0)
		return ESLURM_REST_FAIL_PARSING;

	return SLURM_SUCCESS;
}

#define MAGIC_FOREACH_ACCT_SHORT 0xaefeb0f1
typedef struct {
	int magic;
	data_t *accts;
} foreach_acct_short_t;

static int _foreach_acct_short(void *x, void *arg)
{
	slurmdb_account_rec_t *acct = x;
	foreach_acct_short_t *args = arg;

	xassert(args->magic == MAGIC_FOREACH_ACCT_SHORT);

	(void)data_set_string(data_list_append(args->accts), acct->name);
	return 0;
}

static int _dump_acct_list(const parser_t *const parse, void *obj, data_t *dst,
			   const parser_env_t *penv)
{
	List *acct_list = (((void *)obj) + parse->field_offset);
	foreach_acct_short_t args = {
		.magic = MAGIC_FOREACH_ACCT_SHORT,
	};

	xassert(data_get_type(dst) == DATA_TYPE_NULL);
	args.accts = data_set_list(dst);

	if (!*acct_list)
		return 0;

	if (list_for_each(*acct_list, _foreach_acct_short, &args) < 0)
		return -1;

	return 0;
}

#define MAGIC_FOREACH_ACCT_PARSE_SHORT 0x8eaeb0f1
typedef struct {
	int magic;
	List acct_list;
} foreach_acct_short_parse_t;

static data_for_each_cmd_t _for_each_parse_assoc(data_t *data, void *arg)
{
	foreach_acct_short_parse_t *args = arg;

	if (data_get_type(data) == DATA_TYPE_NULL)
		return DATA_FOR_EACH_FAIL;
	else if (data_convert_type(data, DATA_TYPE_STRING) ==
		 DATA_TYPE_STRING) {
		list_append(args->acct_list, data_get_string(data));
		return DATA_FOR_EACH_CONT;
	}

	return DATA_FOR_EACH_FAIL;
}

static int _parse_acct_list(const parser_t *const parse, void *obj, data_t *src,
			    data_t *errors, const parser_env_t *penv)
{
	List *acct_list = (((void *)obj) + parse->field_offset);
	foreach_acct_short_parse_t args = {
		.magic = MAGIC_FOREACH_ACCT_PARSE_SHORT,
		.acct_list = *acct_list = list_create(xfree_ptr),
	};

	if (data_get_type(src) != DATA_TYPE_LIST)
		return ESLURM_REST_FAIL_PARSING;

	if (data_list_for_each(src, _for_each_parse_assoc, &args) < 0)
		return ESLURM_REST_FAIL_PARSING;

	return SLURM_SUCCESS;
}

#define MAGIC_FOREACH_ASSOC_SHORT 0xfefe00f0
typedef struct {
	int magic;
	data_t *assocs;
	const parser_env_t *penv;
} foreach_assoc_short_t;

static int _foreach_assoc_short(void *x, void *arg)
{
	slurmdb_assoc_rec_t *assoc = x;
	foreach_assoc_t *args = arg;

	xassert(args->magic == MAGIC_FOREACH_ASSOC_SHORT);

	if (_parser_dump(assoc, parse_assoc_short,
			 ARRAY_SIZE(parse_assoc_short),
			 data_set_dict(data_list_append(args->assocs)),
			 args->penv) < 0)
		return -1;

	return 0;
}

static int _dump_assoc_short_list(const parser_t *const parse, void *obj,
				  data_t *dst, const parser_env_t *penv)
{
	List *assoc_list = (((void *)obj) + parse->field_offset);
	foreach_assoc_short_t args = {
		.magic = MAGIC_FOREACH_ASSOC_SHORT,
		.penv = penv,
	};

	xassert(data_get_type(dst) == DATA_TYPE_NULL);
	args.assocs = data_set_list(dst);

	if (!*assoc_list)
		return 0;

	if (list_for_each(*assoc_list, _foreach_assoc_short, &args) < 0)
		return -1;

	return 0;
}

#define MAGIC_FIND_ASSOC 0xa8ba2c18
typedef struct {
	int magic;
	slurmdb_assoc_rec_t *assoc;
} find_assoc_id_t;

/* checks for mis-matches and rejects on the spot */
#define _match(field)                                              \
	do {                                                       \
		/* both null */                                    \
		if (!args->assoc->field && !assoc->field)          \
			continue;                                  \
		/* only  1 is null */                              \
		if (!args->assoc->field != !assoc->field)          \
			return 0;                                  \
		if (xstrcasecmp(args->assoc->field, assoc->field)) \
			return 0;                                  \
	} while (0);

static int _find_assoc_id(void *x, void *key)
{
	slurmdb_assoc_rec_t *assoc = x;
	find_assoc_id_t *args = key;

	xassert(args->magic == MAGIC_FIND_ASSOC);

	if ((args->assoc->id > 0) && (args->assoc->id == assoc->id))
		return 1;

	_match(acct);
	_match(cluster);
	_match(cluster);
	_match(partition);
	_match(user);

	return 1;
}
#undef _match

static int _parse_assoc_id(const parser_t *const parse, void *obj, data_t *src,
			   data_t *errors, const parser_env_t *penv)
{
	int rc = SLURM_SUCCESS;
	uint32_t *associd = (((void *)obj) + parse->field_offset);
	slurmdb_assoc_rec_t *assoc = xmalloc(sizeof(*assoc));
	slurmdb_init_assoc_rec(assoc, false);

	rc = _parser_run(assoc, parse_assoc_short,
			 ARRAY_SIZE(parse_assoc_short), src, errors, penv);

	if (!rc) {
		find_assoc_id_t args = {
			.magic = MAGIC_FIND_ASSOC,
			.assoc = assoc,
		};
		slurmdb_assoc_rec_t *match = list_find_first(
			penv->g_assoc_list, _find_assoc_id, &args);

		if (match)
			*associd = match->id;
		else
			rc = ESLURM_REST_EMPTY_RESULT;
	}

	slurmdb_destroy_assoc_rec(assoc);

	return rc;
}

static int _dump_assoc_id(const parser_t *const parse, void *obj, data_t *dst,
			  const parser_env_t *penv)
{
	uint32_t *associd = (((void *)obj) + parse->field_offset);
	slurmdb_assoc_rec_t *assoc = NULL;

	if (!*associd || (*associd == NO_VAL))
		return SLURM_SUCCESS;

	xassert(data_get_type(dst) == DATA_TYPE_NULL);
	xassert(penv->g_assoc_list);

	if (!(assoc = list_find_first(penv->g_assoc_list,
				      slurmdb_find_assoc_in_list, associd)))
		return ESLURM_DATA_CONV_FAILED;

	return _parser_dump(assoc, parse_assoc_short,
			    ARRAY_SIZE(parse_assoc_short), dst, penv);
}

static int _parse_tres(const parser_t *const parse, void *obj, data_t *src,
		       data_t *errors, const parser_env_t *penv)
{
	slurmdb_tres_rec_t **tres = (((void *)obj) + parse->field_offset);

	xassert(!parse->field_offset);

	if (!penv->g_tres_list) {
		xassert(penv->g_tres_list);
		return ESLURM_NOT_SUPPORTED;
	}

	if (data_get_type(src) != DATA_TYPE_DICT)
		return ESLURM_REST_FAIL_PARSING;

	return _parser_run(*tres, parse_tres, ARRAY_SIZE(parse_tres), src,
			   errors, penv);
}

static int _dump_tres(const parser_t *const parse, void *obj, data_t *dst,
		      const parser_env_t *penv)
{
	slurmdb_tres_rec_t **tres = (((void *)obj) + parse->field_offset);

	return _parser_dump(*tres, parse_tres, ARRAY_SIZE(parse_tres), dst,
			    penv);
}

#define MAGIC_FOREACH_PARSE_TRES_COUNT 0xfbba2c18
typedef struct {
	int magic;
	List tres;
	data_t *errors;
	const parser_env_t *penv;
} for_each_parse_tres_t;

#define MAGIC_FIND_TRES 0xf4ba2c18
typedef struct {
	int magic;
	slurmdb_tres_rec_t *tres;
} find_tres_id_t;

static int _find_tres_id(void *x, void *key)
{
	find_tres_id_t *args = key;
	slurmdb_tres_rec_t *tres = x;

	xassert(args->magic == MAGIC_FIND_TRES);

	if ((args->tres->id > 0) && args->tres->id == tres->id)
		return 1;
	if ((!args->tres->name || !args->tres->name[0]) &&
	    !xstrcasecmp(args->tres->type, tres->type))
		return 1;
	else if (!xstrcasecmp(args->tres->name, tres->name) &&
		 !xstrcasecmp(args->tres->type, tres->type))
		return 1;
	else
		return 0;
}

static data_for_each_cmd_t _for_each_parse_tres_count(data_t *data, void *arg)
{
	for_each_parse_tres_t *args = arg;
	slurmdb_tres_rec_t *tres, *ftres;
	data_t *errors = args->errors;
	find_tres_id_t targs = {
		.magic = MAGIC_FIND_TRES,
	};

	xassert(args->magic == MAGIC_FOREACH_PARSE_TRES_COUNT);

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

	tres = xmalloc(sizeof(*tres));
	(void)list_append(args->tres, tres);

	if (_parser_run(tres, parse_tres, ARRAY_SIZE(parse_tres), data,
			args->errors, args->penv))
		return DATA_FOR_EACH_FAIL;

	if (tres->count < 0) {
		resp_error(errors, ESLURM_REST_FAIL_PARSING,
			   "TRES count below 0", "count");
		return DATA_FOR_EACH_FAIL;
	}

	targs.tres = tres;

	/* Lookup from g_tres_list */
	if ((ftres = list_find_first(args->penv->g_tres_list, _find_tres_id,
				     &targs))) {
		if ((tres->id > 0) && tres->id != ftres->id) {
			resp_error(errors, ESLURM_INVALID_TRES,
				   "TRES id unknown", "id");
			return DATA_FOR_EACH_FAIL;
		}

		if (!tres->id)
			tres->id = ftres->id;
	}

	return DATA_FOR_EACH_CONT;
}

static int _parse_tres_list(const parser_t *const parse, void *obj, data_t *src,
			    data_t *errors, const parser_env_t *penv)
{
	char **tres = (((void *)obj) + parse->field_offset);
	for_each_parse_tres_t args = {
		.magic = MAGIC_FOREACH_PARSE_TRES_COUNT,
		.penv = penv,
		.tres = list_create(slurmdb_destroy_tres_rec),
		.errors = errors,
	};

	if (!penv->g_tres_list) {
		xassert(penv->g_tres_list);
		return ESLURM_NOT_SUPPORTED;
	}

	if (data_get_type(src) != DATA_TYPE_LIST)
		return ESLURM_REST_FAIL_PARSING;

	if (data_list_for_each(src, _for_each_parse_tres_count, &args) < 0)
		return ESLURM_REST_FAIL_PARSING;

	if ((*tres = slurmdb_make_tres_string(args.tres, TRES_STR_FLAG_SIMPLE)))
		return SLURM_SUCCESS;
	else
		return ESLURM_REST_FAIL_PARSING;
}

#define MAGIC_LIST_PER_TRES 0xf7f8baf0
typedef struct {
	int magic;
	data_t *tres;
	const parser_env_t *penv;
} foreach_list_per_tres_t;

static int _dump_tres_list_tres(void *x, void *arg)
{
	slurmdb_tres_rec_t *tres = (slurmdb_tres_rec_t *)x;
	foreach_list_per_tres_t *args = arg;

	if (!tres->type && tres->id) {
		slurmdb_tres_rec_t *c = list_find_first(
			args->penv->g_tres_list, slurmdb_find_tres_in_list,
			&tres->id);

		if (c) {
			tres->type = xstrdup(c->type);
			tres->name = xstrdup(c->name);
		}
	}

	if (_parser_dump(tres, parse_tres, ARRAY_SIZE(parse_tres),
			 data_set_dict(data_list_append(args->tres)),
			 args->penv))
		return -1;

	return 0;
}

static int _dump_tres_list(const parser_t *const parse, void *obj, data_t *dst,
			   const parser_env_t *penv)
{
	char **tres = (((void *)obj) + parse->field_offset);
	List tres_list = NULL;
	foreach_list_per_tres_t args = {
		.magic = MAGIC_LIST_PER_TRES,
		.tres = data_set_list(dst),
		.penv = penv,
	};

	xassert(penv->g_tres_list);
	if (!penv->g_tres_list)
		return ESLURM_NOT_SUPPORTED;

	if (!*tres || !*tres[0])
		/* ignore empty TRES strings */
		return SLURM_SUCCESS;

	slurmdb_tres_list_from_string(&tres_list, *tres, TRES_STR_FLAG_BYTES);

	if (!tres_list)
		return ESLURM_DATA_CONV_FAILED;

	list_for_each(tres_list, _dump_tres_list_tres, &args);

	FREE_NULL_LIST(tres_list);

	return SLURM_SUCCESS;
}
/* based on slurmdb_tres_rec_t but includes node and task */
typedef struct {
	uint64_t count;
	char *node;
	uint64_t task;
	uint32_t id;
	char *name;
	char *type;
} slurmdb_tres_nct_rec_t;

#define _add_parse(mtype, field, path) \
	add_parser(slurmdb_tres_nct_rec_t, mtype, false, field, path)
#define _add_parse_req(mtype, field, path) \
	add_parser(slurmdb_tres_nct_rec_t, mtype, true, field, path)
/* should mirror the structure of slurmdb_tres_nct_rec_t  */
static const parser_t parse_tres_nct[] = {
	_add_parse_req(STRING, type, "type"), _add_parse(STRING, name, "name"),
	_add_parse(UINT32, id, "id"),	      _add_parse(INT64, count, "count"),
	_add_parse(INT64, task, "task"),      _add_parse(STRING, node, "node"),
};
#undef _add_parse
#undef _add_parse_req

typedef enum {
	TRES_EXPLODE_COUNT = 1,
	TRES_EXPLODE_NODE,
	TRES_EXPLODE_TASK,
} tres_explode_type_t;

#define MAGIC_LIST_PER_TRES_TYPE_NCT 0xb1d8acd2
typedef struct {
	int magic;
	tres_explode_type_t type;
	slurmdb_tres_nct_rec_t *tres_nct;
	int tres_nct_count;
	hostlist_t host_list;
} foreach_list_per_tres_type_nct_t;

static int _foreach_list_per_tres_type_nct(void *x, void *arg)
{
	slurmdb_tres_rec_t *tres = (slurmdb_tres_rec_t *)x;
	foreach_list_per_tres_type_nct_t *args = arg;
	slurmdb_tres_nct_rec_t *tres_nct = NULL;

	xassert(args->magic == MAGIC_LIST_PER_TRES_TYPE_NCT);

	for (int i = 0; i < args->tres_nct_count; i++)
		if (args->tres_nct[i].id == tres->id)
			tres_nct = args->tres_nct + i;

	xassert(tres_nct);
	if (!tres_nct)
		/* out of sync?? */
		return -1;

	switch (args->type) {
	case TRES_EXPLODE_NODE:
		xassert(!tres_nct->node);
		free(tres_nct->node);
		/* based on find_hostname() */
		tres_nct->node = hostlist_nth(args->host_list, tres->count);
		return 1;
	case TRES_EXPLODE_TASK:
		xassert(!tres_nct->task);
		tres_nct->task = tres->count;
		return 1;
	case TRES_EXPLODE_COUNT:
		xassert(!tres_nct->count);
		tres_nct->count = tres->count;
		return 1;
	default:
		fatal("%s: unexpected type", __func__);
	}
}

#define MAGIC_FOREACH_POPULATE_GLOBAL_TRES_LIST 0x31b8aad2
typedef struct {
	int magic;
	slurmdb_tres_nct_rec_t *tres_nct;
	int tres_nct_count;
	int offset;
} foreach_populate_g_tres_list;

static int _foreach_populate_g_tres_list(void *x, void *arg)
{
	slurmdb_tres_rec_t *tres = x;
	foreach_populate_g_tres_list *args = arg;
	slurmdb_tres_nct_rec_t *tres_nct = args->tres_nct + args->offset;

	xassert(args->magic == MAGIC_FOREACH_POPULATE_GLOBAL_TRES_LIST);

	tres_nct->id = tres->id;
	tres_nct->name = tres->name;
	tres_nct->type = tres->type;

	xassert(args->offset < args->tres_nct_count);
	args->offset += 1;
	return 0;
}

static int _dump_tres_nct(const parser_t *const parse, void *obj, data_t *dst,
			  const parser_env_t *penv)
{
	int rc = ESLURM_DATA_CONV_FAILED;
	foreach_list_per_tres_type_nct_t args = {
		.magic = MAGIC_LIST_PER_TRES_TYPE_NCT,
	};
	foreach_populate_g_tres_list gtres_args = {
		.magic = MAGIC_FOREACH_POPULATE_GLOBAL_TRES_LIST,
	};
	slurmdb_tres_nct_rec_t *tres_nct = NULL;
	int tres_nct_count = 0;
	char **tres_count = (((void *)obj) +
			     parse->per_type.tres.field_offset_count);
	char **tres_node = (((void *)obj) +
			    parse->per_type.tres.field_offset_node);
	char **tres_task = (((void *)obj) +
			    parse->per_type.tres.field_offset_task);
	char **nodes = (((void *)obj) +
			parse->per_type.tres.field_offset_nodes);
	List tres_count_list = NULL;
	List tres_node_list = NULL;
	List tres_task_list = NULL;

	xassert(data_get_type(dst) == DATA_TYPE_NULL);
	data_set_list(dst);

	xassert(!parse->field_offset);
	xassert(penv->g_tres_list);
	if (!penv->g_tres_list)
		goto cleanup;

	if (!*tres_count && !*tres_node && !*tres_task)
		/* ignore empty TRES strings */
		goto cleanup;

	args.tres_nct_count = gtres_args.tres_nct_count = tres_nct_count =
		list_count(penv->g_tres_list);
	args.tres_nct = gtres_args.tres_nct = tres_nct = xcalloc(
		list_count(penv->g_tres_list), sizeof(*tres_nct));
	if (list_for_each(penv->g_tres_list, _foreach_populate_g_tres_list,
			  &gtres_args) < 0)
		goto cleanup;

	args.host_list = hostlist_create(*nodes);

	slurmdb_tres_list_from_string(&tres_count_list, *tres_count,
				      TRES_STR_FLAG_BYTES);
	slurmdb_tres_list_from_string(&tres_node_list, *tres_node,
				      TRES_STR_FLAG_BYTES);
	slurmdb_tres_list_from_string(&tres_task_list, *tres_task,
				      TRES_STR_FLAG_BYTES);

	args.type = TRES_EXPLODE_COUNT;
	if (tres_count_list &&
	    (list_for_each(tres_count_list, _foreach_list_per_tres_type_nct,
			   &args) < 0))
		goto cleanup;
	args.type = TRES_EXPLODE_NODE;
	if (tres_node_list &&
	    (list_for_each(tres_node_list, _foreach_list_per_tres_type_nct,
			   &args) < 0))
		goto cleanup;
	args.type = TRES_EXPLODE_TASK;
	if (tres_task_list &&
	    (list_for_each(tres_task_list, _foreach_list_per_tres_type_nct,
			   &args) < 0))
		goto cleanup;
	xassert(!(args.type = 0));

	for (int i = 0; i < tres_nct_count; i++)
		if (tres_nct[i].count || tres_nct[i].node || tres_nct[i].task)
			_parser_dump((tres_nct + i), parse_tres_nct,
				     ARRAY_SIZE(parse_tres_nct),
				     data_set_dict(data_list_append(dst)),
				     penv);

	rc = SLURM_SUCCESS;
cleanup:
	FREE_NULL_LIST(tres_count_list);
	FREE_NULL_LIST(tres_node_list);
	FREE_NULL_LIST(tres_task_list);
	FREE_NULL_HOSTLIST(args.host_list);
	for (int i = 0; i < tres_nct_count; i++)
		/* hostlist_nth doesn't use xfree() */
		free(tres_nct[i].node);
	xfree(tres_nct);

	return rc;
}

static int _parse_admin_lvl(const parser_t *const parse, void *obj, data_t *src,
			    data_t *errors, const parser_env_t *penv)
{
	uint16_t *admin_level = (((void *)obj) + parse->field_offset);
	xassert(!(0xffff0000 & *admin_level));

	if (data_convert_type(src, DATA_TYPE_STRING) != DATA_TYPE_STRING)
		return ESLURM_REST_FAIL_PARSING;

	*admin_level = str_2_slurmdb_admin_level(data_get_string(src));

	if (*admin_level == SLURMDB_ADMIN_NOTSET)
		return ESLURM_REST_FAIL_PARSING;

	return SLURM_SUCCESS;
}

static int _dump_admin_lvl(const parser_t *const parse, void *obj, data_t *dst,
			   const parser_env_t *penv)
{
	uint16_t *admin_level = (((void *)obj) + parse->field_offset);

	(void)data_set_string(dst, slurmdb_admin_level_str(*admin_level));

	return SLURM_SUCCESS;
}

#define MAGIC_FOREACH_PARSE_COORD 0xdeed1a14
typedef struct {
	int magic;
	data_t *errors;
	List coord_list;
	const parser_env_t *penv;
} foreach_update_coord_t;

static data_for_each_cmd_t _foreach_update_coord(data_t *data, void *arg)
{
	foreach_update_coord_t *args = arg;
	slurmdb_coord_rec_t *coord;

	xassert(args->magic == MAGIC_FOREACH_PARSE_COORD);

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

	coord = xmalloc(sizeof(*coord));
	list_append(args->coord_list, coord);

	if (_parser_run(coord, parse_coord, ARRAY_SIZE(parse_coord), data,
			args->errors, args->penv))
		return DATA_FOR_EACH_FAIL;

	return DATA_FOR_EACH_CONT;
}

static int _parse_coord_list(const parser_t *const parse, void *obj,
			     data_t *src, data_t *errors,
			     const parser_env_t *penv)
{
	List *coord_list = (((void *)obj) + parse->field_offset);
	foreach_update_coord_t coord_args = {
		.magic = MAGIC_FOREACH_PARSE_COORD,
		.coord_list = *coord_list,
		.penv = penv,
	};

	if (data_get_type(src) != DATA_TYPE_LIST)
		return ESLURM_REST_FAIL_PARSING;

	if (data_list_for_each(src, _foreach_update_coord, &coord_args) < 0)
		return ESLURM_REST_FAIL_PARSING;

	return SLURM_SUCCESS;
}

#define MAGIC_FOREACH_COORDINATOR 0xaefef2f5
typedef struct {
	int magic;
	data_t *coordinators;
	const parser_env_t *penv;
} foreach_coordinator_t;

static int _foreach_coordinator(void *x, void *arg)
{
	slurmdb_coord_rec_t *coor = x;
	foreach_coordinator_t *args = arg;

	xassert(args->magic == MAGIC_FOREACH_COORDINATOR);

	if (_parser_dump(coor, parse_coord, ARRAY_SIZE(parse_coord),
			 data_set_dict(data_list_append(args->coordinators)),
			 args->penv))
		return -1;

	return 0;
}

static int _dump_coord_list(const parser_t *const parse, void *obj, data_t *dst,
			    const parser_env_t *penv)
{
	List *coord_list = (((void *)obj) + parse->field_offset);
	foreach_coordinator_t args = {
		.magic = MAGIC_FOREACH_COORDINATOR,
		.coordinators = data_set_list(dst),
		.penv = penv,
	};

	if (list_for_each(*coord_list, _foreach_coordinator, &args) < 0)
		return ESLURM_DATA_CONV_FAILED;

	return SLURM_SUCCESS;
}
#define MAGIC_FOREACH_STEP 0x7e2eaef1
typedef struct {
	int magic;
	data_t *steps;
	const parser_env_t *penv;
} foreach_step_t;

static int _foreach_step(void *x, void *arg)
{
	int rc = 1;
	slurmdb_step_rec_t *step = x;
	foreach_step_t *args = arg;
	data_t *dstep = data_set_dict(data_list_append(args->steps));

	xassert(args->magic == MAGIC_FOREACH_STEP);

	hostlist_t host_list = hostlist_create(step->nodes);
	if (!host_list) {
		rc = -1;
		goto cleanup;
	}

	xassert(hostlist_count(host_list) == step->nnodes);
	if (!rc && hostlist_count(host_list)) {
		char *host;
		data_t *d = data_set_list(
			data_define_dict_path(dstep, "nodes/list"));
		hostlist_iterator_t itr = hostlist_iterator_create(host_list);

		while ((host = hostlist_next(itr)))
			data_set_string(data_list_append(d), host);

		hostlist_iterator_destroy(itr);
	}

	if (_parser_dump(step, parse_job_step, ARRAY_SIZE(parse_job_step),
			 dstep, args->penv))
		rc = -1;
cleanup:
	FREE_NULL_HOSTLIST(host_list);

	return rc;
}

static int _dump_job_steps(const parser_t *const parse, void *obj, data_t *dst,
			   const parser_env_t *penv)
{
	foreach_step_t args = {
		.magic = MAGIC_FOREACH_STEP,
		.steps = data_set_list(dst),
		.penv = penv,
	};
	List *steps = (((void *)obj) + parse->field_offset);

	if (list_for_each(*steps, _foreach_step, &args) < 0)
		return ESLURM_DATA_CONV_FAILED;

	return SLURM_SUCCESS;
}

static int _dump_job_exit_code(const parser_t *const parse, void *obj,
			       data_t *dst, const parser_env_t *penv)
{
	uint32_t *ec = (((void *)obj) + parse->field_offset);
	data_t *drc, *dsc;

	xassert(data_get_type(dst) == DATA_TYPE_NULL);
	(void)data_set_dict(dst);

	dsc = data_key_set(dst, "status");
	drc = data_key_set(dst, "return_code");

	if (*ec == NO_VAL)
		data_set_string(dsc, "PENDING");
	else if (WIFEXITED(*ec)) {
		data_set_string(dsc, "SUCCESS");
		data_set_int(drc, 0);
	} else if (WIFSIGNALED(*ec)) {
		data_t *sig = data_set_dict(data_key_set(dst, "signal"));
		data_set_string(dsc, "SIGNALED");

		data_set_int(data_key_set(sig, "signal_id"), WTERMSIG(*ec));
		data_set_string(data_key_set(sig, "name"),
				strsignal(WTERMSIG(*ec)));
	} else if (WCOREDUMP(*ec)) {
		data_set_string(dsc, "CORE_DUMPED");
	} else {
		data_set_string(dsc, "ERROR");
		data_set_int(drc, WEXITSTATUS(*ec));
	}

	return SLURM_SUCCESS;
}

#define _add_parse(mtype, field, path) \
	add_parser(slurmdb_assoc_usage_t, mtype, false, field, path)
/* should mirror the structure of slurmdb_assoc_usage_t */
static const parser_t parse_assoc_usage[] = {
	_add_parse(UINT32, accrue_cnt, "accrue_job_count"),
	/* skipping children_list (not packed) */
	/* skipping grp_node_bitmap (not packed) */
	/* skipping grp_node_job_cnt (not packed) */
	/* skipping grp_used_tres (not packed) */
	/* skipping grp_used_tres_run_secs (not packed) */
	_add_parse(FLOAT64, grp_used_wall, "group_used_wallclock"),
	_add_parse(FLOAT64, fs_factor, "fairshare_factor"),
	_add_parse(UINT32, level_shares, "fairshare_shares"),
	/* skipping parent_assoc_ptr (not packed) */
	_add_parse(FLOAT64, priority_norm, "normalized_priority"),
	/* skipping fs_assoc_ptr (not packed) */
	_add_parse(FLOAT128, shares_norm, "normalized_shares"),
	/* skipping tres_count (not packed) */
	_add_parse(FLOAT64, usage_efctv, "effective_normalized_usage"),
	_add_parse(FLOAT64, usage_norm, "normalized_usage"),
	_add_parse(UINT64, usage_raw, "raw_usage"),
	/* skipping fs_assoc_ptr (not packed) */
	/* skipping raw_TRES_usage (not packed) */
	_add_parse(UINT32, used_jobs, "active_jobs"),
	_add_parse(UINT32, used_submit_jobs, "job_count"),
	_add_parse(FLOAT64, level_fs, "fairshare_level"),
	/* skipping valid_qos */
};
#undef _add_parse

static int _parse_assoc_usage(const parser_t *const parse, void *obj,
			      data_t *src, data_t *errors,
			      const parser_env_t *penv)
{
	slurmdb_assoc_rec_t *usage = (((void *)obj) + parse->field_offset);

	if (data_get_type(src) != DATA_TYPE_DICT)
		return ESLURM_REST_FAIL_PARSING;

	return _parser_run(usage, parse_assoc_usage,
			   ARRAY_SIZE(parse_assoc_usage), src, errors, penv);
}

static int _dump_assoc_usage(const parser_t *const parse, void *obj,
			     data_t *dst, const parser_env_t *penv)
{
	slurmdb_assoc_rec_t **usage = (((void *)obj) + parse->field_offset);

	xassert(data_get_type(dst) == DATA_TYPE_NULL);

	if (*usage)
		return _parser_dump(*usage, parse_assoc_usage,
				    ARRAY_SIZE(parse_assoc_usage),
				    data_set_dict(dst), penv);

	return SLURM_SUCCESS;
}

static int _dump_stats_rec_array(const parser_t *const parse, void *obj,
				 data_t *dst, const parser_env_t *penv)
{
	slurmdb_rollup_stats_t **ptr_stats = (((void *)obj) +
					      parse->field_offset);
	slurmdb_rollup_stats_t *rollup_stats;

	xassert(data_get_type(dst) == DATA_TYPE_NULL);
	data_set_list(dst);

	if (!(rollup_stats = *ptr_stats))
		return ESLURM_DATA_CONV_FAILED;

	for (int i = 0; i < DBD_ROLLUP_COUNT; i++) {
		data_t *d;
		uint64_t roll_ave;

		if (rollup_stats->time_total[i] == 0)
			continue;

		d = data_set_dict(data_list_append(dst));

		if (i == 0)
			data_set_string(data_key_set(d, "type"), "internal");
		else if (i == 1)
			data_set_string(data_key_set(d, "type"), "user");
		else
			data_set_string(data_key_set(d, "type"), "unknown");

		data_set_int(data_key_set(d, "last_run"),
			     rollup_stats->timestamp[i]);

		roll_ave = rollup_stats->time_total[i];
		if (rollup_stats->count[i] > 1)
			roll_ave /= rollup_stats->count[i];

		data_set_int(data_key_set(d, "last_cycle"),
			     rollup_stats->time_last[i]);
		data_set_int(data_key_set(d, "max_cycle"),
			     rollup_stats->time_max[i]);
		data_set_int(data_key_set(d, "total_time"),
			     rollup_stats->time_total[i]);
		data_set_int(data_key_set(d, "total_cycles"),
			     rollup_stats->count[i]);
		data_set_int(data_key_set(d, "mean_cycles"), roll_ave);
	}

	return SLURM_SUCCESS;
}

#define MAGIC_FOREACH_STATS_RPC 0x8a2e3ef1
typedef struct {
	int magic;
	const parser_env_t *penv;
	data_t *rpcs;
} foreach_stats_rpc_t;

static int _foreach_stats_rpc(void *x, void *arg)
{
	slurmdb_rpc_obj_t *rpc_obj = (slurmdb_rpc_obj_t *)x;
	foreach_stats_rpc_t *args = arg;

	xassert(args->magic == MAGIC_FOREACH_STATS_RPC);

	if (_parser_dump(
		    rpc_obj, parse_stats_rpcs, ARRAY_SIZE(parse_stats_rpcs),
		    data_set_dict(data_list_append(args->rpcs)), args->penv))
		return -1;

	return 0;
}

static int _dump_stats_rpc_list(const parser_t *const parse, void *obj,
				data_t *dst, const parser_env_t *penv)
{
	List *rpc_list = (((void *)obj) + parse->field_offset);
	foreach_stats_rpc_t args = {
		.magic = MAGIC_FOREACH_STATS_RPC,
		.penv = penv,
	};

	xassert(data_get_type(dst) == DATA_TYPE_NULL);
	args.rpcs = data_set_list(dst);

	if (list_for_each(*rpc_list, _foreach_stats_rpc, &args) < 0)
		return ESLURM_DATA_CONV_FAILED;

	return SLURM_SUCCESS;
}

#define MAGIC_FOREACH_STATS_USER_RPC 0x8a2e3ef3
typedef struct {
	int magic;
	const parser_env_t *penv;
	data_t *users;
} foreach_stats_user_rpc_t;

static int _foreach_stats_user_rpc(void *x, void *arg)
{
	slurmdb_rpc_obj_t *rpc_obj = (slurmdb_rpc_obj_t *)x;
	foreach_stats_user_rpc_t *args = arg;

	xassert(args->magic == MAGIC_FOREACH_STATS_USER_RPC);

	if (_parser_dump(rpc_obj, parse_stats_user_rpcs,
			 ARRAY_SIZE(parse_stats_user_rpcs),
			 data_set_dict(data_list_append(args->users)),
			 args->penv))
		return -1;

	return 0;
}

static int _dump_stats_user_list(const parser_t *const parse, void *obj,
				 data_t *dst, const parser_env_t *penv)
{
	List *user_list = (((void *)obj) + parse->field_offset);
	foreach_stats_user_rpc_t args = {
		.magic = MAGIC_FOREACH_STATS_USER_RPC,
		.penv = penv,
	};

	xassert(data_get_type(dst) == DATA_TYPE_NULL);
	args.users = data_set_list(dst);

	if (list_for_each(*user_list, _foreach_stats_user_rpc, &args))
		return ESLURM_DATA_CONV_FAILED;

	return SLURM_SUCCESS;
}

static int _dump_rpc_id(const parser_t *const parse, void *obj, data_t *dst,
			const parser_env_t *penv)
{
	slurmdbd_msg_type_t *id = (((void *)obj) + parse->field_offset);

	xassert(data_get_type(dst) == DATA_TYPE_NULL);

	data_set_string(dst, slurmdbd_msg_type_2_str(*id, 1));

	return SLURM_SUCCESS;
}

static int _dump_clust_acct_rec(const parser_t *const parse, void *obj,
				data_t *dst, const parser_env_t *penv)
{
	slurmdb_cluster_accounting_rec_t *acct = (((void *)obj) +
						  parse->field_offset);

	xassert(data_get_type(dst) == DATA_TYPE_NULL);
	data_set_list(dst);

	if (!acct)
		return ESLURM_DATA_CONV_FAILED;

	return SLURM_SUCCESS;
}

static int _parse_clust_acct_rec_list(const parser_t *const parse, void *obj,
				      data_t *src, data_t *errors,
				      const parser_env_t *penv)
{
	if (data_get_type(src) != DATA_TYPE_LIST)
		return ESLURM_REST_FAIL_PARSING;

	/*
	 * List of stats: slurmdb_cluster_accounting_rec_t
	 * This can not be ingested, so we will ignore it.
	 */
	debug("%s: ignoring slurmdb_cluster_accounting_rec_t", __func__);

	return SLURM_SUCCESS;
}

#define MAGIC_FOREACH_ACCT_REC 0xa22e3ef3
typedef struct {
	int magic;
	const parser_env_t *penv;
	data_t *list;
} _foreach_clust_acct_rec_t;

static int _foreach_clust_acct_rec(void *x, void *arg)
{
	slurmdb_cluster_accounting_rec_t *obj = x;
	_foreach_clust_acct_rec_t *args = arg;

	xassert(args->magic == MAGIC_FOREACH_ACCT_REC);

	if (_parser_dump(obj, parse_cluster_accounting_rec,
			 ARRAY_SIZE(parse_cluster_accounting_rec),
			 data_set_dict(data_list_append(args->list)),
			 args->penv))
		return -1;

	return 0;
}

static int _dump_clust_acct_rec_list(const parser_t *const parse, void *obj,
				     data_t *dst, const parser_env_t *penv)
{
	List *acct_list = (((void *)obj) + parse->field_offset);
	_foreach_clust_acct_rec_t args = {
		.magic = MAGIC_FOREACH_ACCT_REC,
		.penv = penv,
		.list = dst,
	};

	xassert(data_get_type(dst) == DATA_TYPE_NULL);

	if (!acct_list)
		return ESLURM_REST_FAIL_PARSING;

	data_set_list(dst);

	if (list_for_each(*acct_list, _foreach_clust_acct_rec, &args) < 0)
		return ESLURM_REST_FAIL_PARSING;

	return SLURM_SUCCESS;
}

static int _parse_select_plugin_id(const parser_t *const parse, void *obj,
				   data_t *src, data_t *errors,
				   const parser_env_t *penv)
{
	int *id = (((void *)obj) + parse->field_offset);

	if (data_get_type(src) == DATA_TYPE_NULL)
		return ESLURM_REST_FAIL_PARSING;
	else if (data_convert_type(src, DATA_TYPE_STRING) == DATA_TYPE_STRING &&
		 (*id = select_string_to_plugin_id(data_get_string(src)) > 0))
		return SLURM_SUCCESS;

	return ESLURM_REST_FAIL_PARSING;
}

static int _dump_select_plugin_id(const parser_t *const parse, void *obj,
				  data_t *dst, const parser_env_t *penv)
{
	int *id = (((void *)obj) + parse->field_offset);
	char *s = select_plugin_id_to_string(*id);

	xassert(data_get_type(dst) == DATA_TYPE_NULL);

	if (s) {
		data_set_string(dst, s);
	} else
		data_set_null(dst);

	return SLURM_SUCCESS;
}

static int _dump_task_distribution(const parser_t *const parse, void *obj,
				   data_t *dst, const parser_env_t *penv)
{
	uint32_t *dist = (((void *)obj) + parse->field_offset);
	char *d = slurm_step_layout_type_name(*dist);

	xassert(data_get_type(dst) == DATA_TYPE_NULL);

	data_set_string_own(dst, d);

	return SLURM_SUCCESS;
}

static int _dump_step_id(const parser_t *const parse, void *obj, data_t *dst,
			 const parser_env_t *penv)
{
	uint32_t *id = (((void *)obj) + parse->field_offset);

	// TODO rewrite after bug#9622 resolved

	switch (*id) {
	case SLURM_EXTERN_CONT:
		data_set_string(dst, "extern");
		break;
	case SLURM_BATCH_SCRIPT:
		data_set_string(dst, "batch");
		break;
	case SLURM_PENDING_STEP:
		data_set_string(dst, "pending");
		break;
	case SLURM_INTERACTIVE_STEP:
		data_set_string(dst, "interactive");
		break;
	default:
		data_set_int(dst, *id);
	}

	return SLURM_SUCCESS;
}

static int _dump_wckey_tag(const parser_t *const parse, void *obj, data_t *dst,
			   const parser_env_t *penv)
{
	char **src = (((void *)obj) + parse->field_offset);
	data_t *flags, *key;

	xassert(data_get_type(dst) == DATA_TYPE_NULL);

	if (!*src) {
		data_set_null(dst);
		return SLURM_SUCCESS;
	}

	key = data_key_set(data_set_dict(dst), "wckey");
	flags = data_set_list(data_key_set(dst, "flags"));

	if (*src[0] == '*') {
		data_set_string(data_list_append(flags), "ASSIGNED_DEFAULT");
		data_set_string(key, (*src + 1));
	} else
		data_set_string(key, *src);

	return SLURM_SUCCESS;
}

static int _dump_user_id(const parser_t *const parse, void *obj, data_t *dst,
			 const parser_env_t *penv)
{
	uid_t *uid = (((void *)obj) + parse->field_offset);
	char *u;

	xassert(data_get_type(dst) == DATA_TYPE_NULL);

	if ((u = uid_to_string_or_null(*uid)))
		data_set_string_own(dst, u);
	else
		data_set_null(dst);

	return SLURM_SUCCESS;
}

static int _parse_user_id(const parser_t *const parse, void *obj, data_t *src,
			  data_t *errors, const parser_env_t *penv)
{
	uid_t *uid = (((void *)obj) + parse->field_offset);

	if (data_get_type(src) == DATA_TYPE_NULL)
		return ESLURM_REST_FAIL_PARSING;
	else if (data_convert_type(src, DATA_TYPE_STRING) == DATA_TYPE_STRING &&
		 !uid_from_string(data_get_string(src), uid))
		return SLURM_SUCCESS;

	return ESLURM_REST_FAIL_PARSING;
}

static int _dump_group_id(const parser_t *const parse, void *obj, data_t *dst,
			  const parser_env_t *penv)
{
	gid_t *gid = (((void *)obj) + parse->field_offset);
	char *g;

	xassert(data_get_type(dst) == DATA_TYPE_NULL);

	if ((g = gid_to_string_or_null(*gid)))
		data_set_string_own(dst, g);
	else
		data_set_null(dst);

	return SLURM_SUCCESS;
}

static int _dump_job_reason(const parser_t *const parse, void *obj, data_t *dst,
			    const parser_env_t *penv)
{
	uint32_t *state = (((void *)obj) + parse->field_offset);

	xassert(data_get_type(dst) == DATA_TYPE_NULL);
	data_set_string(dst, job_reason_string(*state));

	return SLURM_SUCCESS;
}

typedef struct {
	parse_rfunc_t rfunc;
	parse_wfunc_t wfunc;
	parser_type_t type;
} parser_funcs_t;

#define _add_func(rfuncp, wfuncp, typev)                        \
	{                                                       \
		.rfunc = rfuncp, .wfunc = wfuncp, .type = typev \
	}
const parser_funcs_t funcs[] = {
	_add_func(_parse_to_string, _dump_to_string, PARSE_STRING),
	_add_func(_parse_to_uint32, _dump_to_uint32, PARSE_UINT32),
	_add_func(_parse_to_int64, _dump_to_int64, PARSE_INT64),
	_add_func(_parse_to_uint64, _dump_to_uint64, PARSE_UINT64),
	_add_func(_parse_to_uint16, _dump_to_uint16, PARSE_UINT16),
	_add_func(_parse_flags, _dump_flags, PARSE_FLAGS),
	_add_func(_parse_qos_str_id, _dump_qos_str_id, PARSE_QOS_ID),
	_add_func(_parse_qos_str_list, _dump_qos_str_list, PARSE_QOS_STR_LIST),
	_add_func(_parse_qos_preempt_list, _dump_qos_preempt_list,
		  PARSE_QOS_PREEMPT_LIST),
	_add_func(_parse_tres, _dump_tres, PARSE_TRES),
	_add_func(_parse_tres_list, _dump_tres_list, PARSE_TRES_LIST),
	_add_func(NULL, _dump_tres_nct, PARSE_TRES_NODE_COUNT_TASK),
	_add_func(NULL, _dump_job_steps, PARSE_JOB_STEPS),
	_add_func(NULL, _dump_job_exit_code, PARSE_JOB_EXIT_CODE),
	_add_func(_parse_admin_lvl, _dump_admin_lvl, PARSE_ADMIN_LVL),
	_add_func(_parse_acct_list, _dump_acct_list, PARSE_ACCOUNT_LIST),
	_add_func(_parse_assoc_list, _dump_assoc_list, PARSE_ASSOC_LIST),
	_add_func(_parse_assoc_short_list, _dump_assoc_short_list,
		  PARSE_ASSOC_SHORT_LIST),
	_add_func(_parse_assoc_usage, _dump_assoc_usage, PARSE_ASSOC_USAGE),
	_add_func(_parse_assoc_id, _dump_assoc_id, PARSE_ASSOC_ID),
	_add_func(_parse_coord_list, _dump_coord_list, PARSE_COORD_LIST),
	_add_func(_parse_to_float64, _dump_to_float64, PARSE_FLOAT64),
	_add_func(_parse_to_float128, _dump_to_float128, PARSE_FLOAT128),
	_add_func(NULL, _dump_stats_rec_array, PARSE_STATS_REC_ARRAY),
	_add_func(NULL, _dump_stats_rpc_list, PARSE_STATS_RPC_LIST),
	_add_func(NULL, _dump_stats_user_list, PARSE_STATS_USER_LIST),
	_add_func(NULL, _dump_rpc_id, PARSE_RPC_ID),
	_add_func(NULL, _dump_clust_acct_rec, PARSE_CLUSTER_ACCT_REC),
	_add_func(_parse_clust_acct_rec_list, _dump_clust_acct_rec_list,
		  PARSE_CLUSTER_ACCT_REC_LIST),
	_add_func(_parse_select_plugin_id, _dump_select_plugin_id,
		  PARSE_SELECT_PLUGIN_ID),
	_add_func(NULL, _dump_task_distribution, PARSE_TASK_DISTRIBUTION),
	_add_func(NULL, _dump_step_id, PARSE_STEP_ID),
	_add_func(NULL, _dump_wckey_tag, PARSE_WCKEY_TAG),
	_add_func(NULL, _dump_group_id, PARSE_GROUP_ID),
	_add_func(NULL, _dump_job_reason, PARSE_JOB_REASON),
	_add_func(_parse_user_id, _dump_user_id, PARSE_USER_ID),
};
#undef _add_func

#define _add_parser(parser, typev)                        \
	{                                                 \
		.type = typev, .parse = parser,           \
		.parse_member_count = ARRAY_SIZE(parser), \
	}
const parsers_t parsers[] = {
	_add_parser(parse_assoc_short, PARSE_ASSOC_SHORT),
	_add_parser(parse_assoc, PARSE_ASSOC),
	_add_parser(parse_job_step, PARSE_JOB_STEP),
	_add_parser(parse_user, PARSE_USER),
	_add_parser(parse_job, PARSE_JOB),
	_add_parser(parse_acct, PARSE_ACCOUNT),
	_add_parser(parse_tres, PARSE_TRES),
	_add_parser(parse_qos, PARSE_QOS),
	_add_parser(parse_coord, PARSE_COORD),
	_add_parser(parse_wckey, PARSE_WCKEY),
	_add_parser(parse_stats_rec, PARSE_STATS_REC),
	_add_parser(parse_cluster_rec, PARSE_CLUSTER_REC),
};
#undef _add_parser

extern int parse(parser_type_t type, void *obj, data_t *src, data_t *errors,
		 const parser_env_t *penv)
{
	for (int i = 0; i < ARRAY_SIZE(parsers); i++)
		if (parsers[i].type == type)
			return _parser_run(obj, parsers[i].parse,
					   parsers[i].parse_member_count, src,
					   errors, penv);

	fatal("invalid type?");
}

extern int dump(parser_type_t type, void *obj, data_t *dst,
		const parser_env_t *penv)
{
	for (int i = 0; i < ARRAY_SIZE(parsers); i++)
		if (parsers[i].type == type)
			return _parser_dump(obj, parsers[i].parse,
					    parsers[i].parse_member_count, dst,
					    penv);

	fatal("invalid type?");
}

static int _parser_run(void *obj, const parser_t *const parse,
		       const size_t parse_member_count, data_t *data,
		       data_t *errors, const parser_env_t *penv)
{
	int rc = SLURM_SUCCESS;

	for (int i = 0; (!rc) && (i < parse_member_count); i++) {
		for (int f = 0; f < ARRAY_SIZE(funcs); f++) {
			data_t *pd = data_resolve_dict_path(data, parse[i].key);

			if (pd && parse[i].type == funcs[f].type) {
				xassert(funcs[f].rfunc);
				rc = funcs[f].rfunc((parse + i), obj, pd,
						    errors, penv);
			}
		}

		if (rc && parse[i].required)
			resp_error(errors, rc, "Required field failed to parse",
				   parse[i].key);
		else
			rc = SLURM_SUCCESS;
	}

	return rc;
}

static int _parser_dump(void *obj, const parser_t *const parse,
			const size_t parse_member_count, data_t *data,
			const parser_env_t *penv)
{
	int rc = SLURM_SUCCESS;

	for (int i = 0; (!rc) && (i < parse_member_count); i++) {
		data_t *pd;

		/* make sure we aren't clobbering something */
		xassert(!data_resolve_dict_path(data, parse[i].key));

		if (!(pd = data_define_dict_path(data, parse[i].key))) {
			error("%s: failed to define field %s", __func__,
			      parse[i].key);
			rc = ESLURM_REST_EMPTY_RESULT;
			break;
		}

		for (int f = 0; (!rc) && (f < ARRAY_SIZE(funcs)); f++) {
			if (parse[i].type == funcs[f].type) {
				xassert(funcs[f].wfunc);

				if ((rc = funcs[f].wfunc((parse + i), obj, pd,
							 penv))) {
					error("%s: failed on field %s: %s",
					      __func__, parse[i].key,
					      slurm_strerror(rc));
					break;
				}
			}
		}
	}

	return rc;
}
