| /*****************************************************************************\ |
| * openapi.c - Slurm data parser openapi specifier |
| ***************************************************************************** |
| * 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 "src/common/data.h" |
| #include "src/common/http.h" |
| #include "src/common/log.h" |
| #include "src/common/xassert.h" |
| #include "src/common/xmalloc.h" |
| #include "src/common/xstring.h" |
| |
| #include "api.h" |
| #include "openapi.h" |
| #include "parsers.h" |
| #include "parsing.h" |
| |
| #define MAGIC_SPEC_ARGS 0xa891beab |
| #define REF_PATH OPENAPI_PATH_REL OPENAPI_SCHEMAS_PATH |
| #define TYPE_PREFIX "DATA_PARSER_" |
| #define KEY_PREFIX XSTRINGIFY(DATA_VERSION) "_" |
| #define IS_FLAG_BIT_DEPRECATED(bit) (bit->deprecated || IS_PLUGIN_DEPRECATED) |
| #define IS_PARSER_DEPRECATED(parser) \ |
| (parser->deprecated || IS_PLUGIN_DEPRECATED) |
| |
| typedef struct { |
| int magic; /* MAGIC_SPEC_ARGS */ |
| args_t *args; |
| const parser_t *parsers; |
| int parser_count; |
| data_t *paths; /* existing paths in OAS */ |
| data_t *new_paths; /* newly populated paths */ |
| data_t *schemas; |
| data_t *spec; |
| data_t *path_params; /* dict of each path param */ |
| data_t *params; /* current parameters target */ |
| int *references; /* references[i] = count(parsers[i]) */ |
| bool disable_refs; |
| } spec_args_t; |
| |
| #define MAGIC_REFS_PTR 0xaa910e8b |
| |
| typedef struct { |
| int magic; /* MAGIC_REFS_PTR */ |
| int *references; /* references[i] = count(parsers[i]) */ |
| } refs_ptr_t; |
| |
| static void _replace_refs(data_t *data, spec_args_t *sargs); |
| static void _count_refs(data_t *data, spec_args_t *sargs); |
| extern void _set_ref(data_t *obj, const parser_t *parent, |
| const parser_t *parser, spec_args_t *sargs); |
| static data_t *_resolve_parser_key(const parser_t *parser, data_t *dst); |
| |
| static uint32_t _resolve_parser_index(const parser_t *parser, |
| spec_args_t *sargs) |
| { |
| for (int i = 0; i < sargs->parser_count; i++) |
| if (parser->type == sargs->parsers[i].type) |
| return i; |
| |
| xassert(false); |
| return NO_VAL; |
| } |
| |
| static const parser_t *_resolve_parser(const char *type, spec_args_t *sargs) |
| { |
| for (int i = 0; i < sargs->parser_count; i++) |
| if (!xstrcmp(sargs->parsers[i].type_string, type)) |
| return &sargs->parsers[i]; |
| |
| return NULL; |
| } |
| |
| static char *_get_parser_key(const parser_t *parser) |
| { |
| char *stype; |
| char *key = NULL; |
| |
| check_parser(parser); |
| xassert(!xstrncmp(parser->type_string, TYPE_PREFIX, |
| strlen(TYPE_PREFIX))); |
| |
| stype = xstrdup(parser->type_string + strlen(TYPE_PREFIX)); |
| xstrtolower(stype); |
| xstrfmtcat(key, "%s%s", KEY_PREFIX, stype); |
| xfree(stype); |
| |
| return key; |
| } |
| |
| static char *_get_parser_path(const parser_t *parser) |
| { |
| char *key = _get_parser_key(parser); |
| char *path = NULL; |
| |
| xstrfmtcat(path, "%s%s", REF_PATH, key); |
| xfree(key); |
| |
| return path; |
| } |
| |
| /* |
| * Populate OpenAPI specification field |
| * IN obj - data_t ptr to specific field in OpenAPI schema |
| * IN format - OpenAPI format to use |
| * IN desc - Description of field to use |
| * RET ptr to "items" for ARRAY or "properties" for OBJECT or NULL |
| */ |
| static data_t *_set_openapi_props(data_t *obj, openapi_type_format_t format, |
| const char *desc) |
| { |
| data_t *dtype; |
| const char *format_str; |
| |
| xassert(format > OPENAPI_FORMAT_INVALID); |
| xassert(format < OPENAPI_FORMAT_MAX); |
| |
| if (data_get_type(obj) == DATA_TYPE_NULL) |
| data_set_dict(obj); |
| |
| dtype = data_key_set(obj, "type"); |
| |
| /* type may have already been set by _resolve_parser_key() */ |
| xassert((data_get_type(dtype) == DATA_TYPE_NULL) || |
| ((data_get_type(dtype) == DATA_TYPE_STRING) && |
| !xstrcmp(data_get_string(dtype), "object"))); |
| |
| data_set_string(dtype, openapi_type_format_to_type_string(format)); |
| |
| if ((format_str = openapi_type_format_to_format_string(format))) { |
| data_t *dformat = data_key_set(obj, "format"); |
| xassert(data_get_type(dformat) == DATA_TYPE_NULL); |
| data_set_string(dformat, format_str); |
| } |
| |
| if (desc) |
| data_set_string(data_key_set(obj, "description"), desc); |
| |
| if (format == OPENAPI_FORMAT_ARRAY) |
| return data_set_dict(data_key_set(obj, "items")); |
| |
| if (format == OPENAPI_FORMAT_OBJECT) |
| return data_set_dict(data_key_set(obj, "properties")); |
| |
| return NULL; |
| } |
| |
| static bool _should_be_ref(const parser_t *parser, spec_args_t *sargs) |
| { |
| uint32_t parser_index; |
| |
| if (sargs->disable_refs) |
| return false; |
| |
| /* |
| * Removed parsers/fields are just place holders and using $ref will |
| * result in an invalid $ref include path. |
| */ |
| if (parser->model == PARSER_MODEL_REMOVED) |
| return false; |
| if (parser->model == PARSER_MODEL_ARRAY_REMOVED_FIELD) |
| return false; |
| |
| parser_index = _resolve_parser_index(parser, sargs); |
| |
| /* parser with single reference doesn't need to be a $ref */ |
| if ((parser_index != NO_VAL) && !is_prefer_refs_mode(sargs->args)) { |
| debug4("%s: %s references=%u", |
| __func__, parser->type_string, |
| sargs->references[parser_index]); |
| |
| if (sargs->references[parser_index] <= 1) |
| return false; |
| } |
| |
| if ((parser->obj_openapi == OPENAPI_FORMAT_OBJECT) || |
| (parser->obj_openapi == OPENAPI_FORMAT_ARRAY)) |
| return true; |
| |
| if (parser->array_type || parser->pointer_type || parser->list_type || |
| parser->fields || parser->alias_type) |
| return true; |
| |
| return false; |
| } |
| |
| static void _add_eflags(data_t *props, const parser_t *parser, |
| spec_args_t *sargs) |
| { |
| parser = find_parser_by_type(parser->type); |
| |
| for (int i = 0; i < parser->flag_bit_array_count; i++) { |
| const flag_bit_t *bit = &parser->flag_bit_array[i]; |
| data_t *dchild; |
| |
| if (bit->hidden) |
| continue; |
| |
| dchild = data_key_set(props, bit->name); |
| _set_openapi_props(dchild, OPENAPI_FORMAT_BOOL, NULL); |
| } |
| } |
| |
| static void _add_field(data_t *obj, data_t *required, |
| const parser_t *const parent, |
| const parser_t *const pchild, spec_args_t *sargs) |
| { |
| data_t *dchild; |
| |
| if (pchild->model == PARSER_MODEL_ARRAY_SKIP_FIELD) |
| return; |
| |
| if (pchild->required) |
| data_set_string(data_list_append(required), pchild->key); |
| |
| dchild = _resolve_parser_key(pchild, obj); |
| |
| if (pchild->model == |
| PARSER_MODEL_ARRAY_LINKED_EXPLODED_FLAG_ARRAY_FIELD) { |
| data_t *p = data_key_get(dchild, "properties"); |
| _add_eflags(p, pchild, sargs); |
| } else { |
| _set_ref(dchild, parent, pchild, sargs); |
| } |
| } |
| |
| static void _add_param_flag_enum(data_t *param, const parser_t *parser) |
| { |
| data_t *fenums = data_set_list(data_key_set(param, "enum")); |
| data_set_string(data_key_set(param, "type"), |
| openapi_type_format_to_type_string(OPENAPI_FORMAT_STRING)); |
| |
| for (int i = 0; i < parser->flag_bit_array_count; i++) |
| if (!parser->flag_bit_array[i].hidden) |
| data_set_string(data_list_append(fenums), |
| parser->flag_bit_array[i].name); |
| } |
| |
| /* |
| * Populate OpenAPI specification field using parser |
| * IN obj - data_t ptr to specific field in OpenAPI schema |
| * IN parser - populate field with info from parser |
| * IN description - description from parent pointer parser or NULL |
| * IN deprecated - parser marked as deprecated |
| * |
| * RET ptr to "items" for ARRAY or "properties" for OBJECT or NULL |
| */ |
| static data_t *_set_openapi_parse(data_t *obj, const parser_t *parser, |
| spec_args_t *sargs, const char *desc, |
| bool deprecated) |
| { |
| data_t *props; |
| openapi_type_format_t format; |
| |
| xassert(parser->magic == MAGIC_PARSER); |
| xassert(sargs->magic == MAGIC_SPEC_ARGS); |
| xassert(sargs->args->magic == MAGIC_ARGS); |
| xassert(parser->model != PARSER_MODEL_ARRAY_SKIP_FIELD); |
| xassert(!parser->pointer_type); |
| xassert(!parser->alias_type); |
| xassert(parser->model != PARSER_MODEL_ARRAY_LINKED_FIELD); |
| xassert(parser->model != |
| PARSER_MODEL_ARRAY_LINKED_EXPLODED_FLAG_ARRAY_FIELD); |
| xassert(parser->model != PARSER_MODEL_ARRAY_REMOVED_FIELD); |
| |
| if (parser->array_type || parser->list_type || |
| (parser->flag_bit_array && !parser->single_flag)) |
| format = OPENAPI_FORMAT_ARRAY; |
| else if (parser->flag_bit_array && parser->single_flag) |
| format = OPENAPI_FORMAT_STRING; |
| else if (parser->fields) |
| format = OPENAPI_FORMAT_OBJECT; |
| else |
| format = parser->obj_openapi; |
| |
| xassert(format > OPENAPI_FORMAT_INVALID); |
| xassert(format < OPENAPI_FORMAT_MAX); |
| |
| if (parser->obj_desc && !desc) |
| desc = parser->obj_desc; |
| |
| if ((props = _set_openapi_props(obj, format, desc))) { |
| if (parser->array_type) { |
| _set_ref(props, parser, |
| find_parser_by_type(parser->array_type), |
| sargs); |
| } else if (parser->list_type) { |
| _set_ref(props, parser, |
| find_parser_by_type(parser->list_type), sargs); |
| } else if (parser->flag_bit_array) { |
| _add_param_flag_enum(props, parser); |
| } else if (parser->fields) { |
| data_t *required = |
| data_set_list(data_key_set(obj, "required")); |
| |
| for (int i = 0; i < parser->field_count; i++) |
| _add_field(obj, required, parser, |
| &parser->fields[i], sargs); |
| } else if (parser->model == PARSER_MODEL_REMOVED) { |
| /* do nothing */ |
| } else if (!is_complex_mode(sargs->args)) { |
| fatal("%s: parser %s need to provide openapi specification, array type or pointer type", |
| __func__, parser->type_string); |
| } |
| } |
| |
| if (deprecated) |
| data_set_bool(data_key_set(obj, "deprecated"), true); |
| |
| return props; |
| } |
| |
| extern void _set_ref(data_t *obj, const parser_t *parent, |
| const parser_t *parser, spec_args_t *sargs) |
| { |
| char *str, *key; |
| const char *desc = NULL; |
| bool deprecated = (parent && parent->deprecated); |
| |
| xassert(sargs->magic == MAGIC_SPEC_ARGS); |
| xassert(sargs->args->magic == MAGIC_ARGS); |
| |
| while (true) { |
| if (desc) |
| /* do nothing */; |
| else if (parent && parent->obj_desc) |
| desc = parent->obj_desc; |
| else if (parser->obj_desc) |
| desc = parser->obj_desc; |
| |
| /* All children are deprecated once the parent is */ |
| if (parser->deprecated) |
| deprecated = true; |
| |
| if (parser->model == PARSER_MODEL_REMOVED) { |
| if (is_complex_mode(sargs->args)) |
| return; |
| break; |
| } |
| |
| if ((parser->model == PARSER_MODEL_ARRAY_LINKED_FIELD) || |
| (parser->model == |
| PARSER_MODEL_ARRAY_LINKED_EXPLODED_FLAG_ARRAY_FIELD) || |
| (parser->model == PARSER_MODEL_ARRAY_REMOVED_FIELD)) { |
| /* resolve to linked parser */ |
| parent = parser; |
| parser = find_parser_by_type(parser->type); |
| |
| continue; |
| } |
| |
| if (parser->pointer_type) { |
| parser = find_parser_by_type(parser->pointer_type); |
| continue; |
| } |
| |
| if (parser->alias_type) { |
| parser = find_parser_by_type(parser->alias_type); |
| continue; |
| } |
| |
| break; |
| } |
| |
| if (!_should_be_ref(parser, sargs)) { |
| _set_openapi_parse(obj, parser, sargs, desc, deprecated); |
| return; |
| } |
| |
| if (data_get_type(obj) == DATA_TYPE_NULL) |
| data_set_dict(obj); |
| |
| xassert(data_get_type(obj) == DATA_TYPE_DICT); |
| |
| str = _get_parser_path(parser); |
| data_set_string_own(data_key_set(obj, "$ref"), str); |
| |
| if (desc && !data_key_get(obj, "description")) |
| data_set_string(data_key_set(obj, "description"), desc); |
| |
| if (deprecated) |
| data_set_bool(data_key_set(obj, "deprecated"), true); |
| |
| /* Add schema for $ref target */ |
| |
| key = _get_parser_key(parser); |
| obj = data_key_set(sargs->schemas, key); |
| |
| if (data_get_type(obj) == DATA_TYPE_NULL) { |
| debug4("%s: adding schema %s", __func__, key); |
| _set_openapi_parse(data_set_dict(obj), parser, sargs, NULL, |
| parser->deprecated); |
| } else { |
| debug4("%s: skip adding duplicate schema %s", __func__, key); |
| } |
| |
| xfree(key); |
| } |
| |
| static data_t *_resolve_parser_key(const parser_t *parser, data_t *dst) |
| { |
| int rc; |
| data_t *path = data_set_list(data_new()); |
| data_t *pkey; |
| |
| /* |
| * key may be multiple dicts combined. |
| * Need to create each dict needed to complete path. |
| */ |
| |
| if ((rc = openapi_append_rel_path(path, parser->key))) |
| fatal("%s: failed to split %s: %s", __func__, parser->key, |
| slurm_strerror(rc)); |
| |
| while ((pkey = data_list_dequeue(path))) { |
| data_t *props, *type; |
| |
| if (data_get_type(dst) == DATA_TYPE_NULL) |
| data_set_dict(dst); |
| |
| xassert(data_get_type(pkey) == DATA_TYPE_STRING); |
| xassert(data_get_type(dst) == DATA_TYPE_DICT); |
| |
| if (!(type = data_key_get(dst, "type"))) |
| data_set_string(data_key_set(dst, "type"), "object"); |
| else |
| xassert(!xstrcmp(data_get_string( |
| data_key_get(dst, "type")), "object")); |
| |
| props = data_key_set(dst, "properties"); |
| |
| xassert((data_get_type(props) == DATA_TYPE_DICT) || |
| (data_get_type(props) == DATA_TYPE_NULL)); |
| |
| if (data_get_type(props) != DATA_TYPE_DICT) |
| data_set_dict(props); |
| |
| dst = data_key_set(props, data_get_string(pkey)); |
| |
| if (data_get_type(dst) == DATA_TYPE_NULL) |
| data_set_dict(dst); |
| |
| xassert(data_get_type(dst) == DATA_TYPE_DICT); |
| |
| FREE_NULL_DATA(pkey); |
| } |
| |
| FREE_NULL_DATA(path); |
| return dst; |
| } |
| |
| static data_for_each_cmd_t _convert_list_entry(data_t *data, void *arg) |
| { |
| spec_args_t *sargs = arg; |
| |
| xassert(sargs->magic == MAGIC_SPEC_ARGS); |
| xassert(sargs->args->magic == MAGIC_ARGS); |
| |
| if ((data_get_type(data) == DATA_TYPE_LIST) || |
| (data_get_type(data) == DATA_TYPE_DICT)) |
| _replace_refs(data, sargs); |
| |
| return DATA_FOR_EACH_CONT; |
| } |
| |
| static data_for_each_cmd_t _convert_dict_entry(const char *key, data_t *data, |
| void *arg) |
| { |
| spec_args_t *sargs = arg; |
| |
| xassert(sargs->magic == MAGIC_SPEC_ARGS); |
| xassert(sargs->args->magic == MAGIC_ARGS); |
| |
| if ((data_get_type(data) == DATA_TYPE_LIST) || |
| (data_get_type(data) == DATA_TYPE_DICT)) |
| _replace_refs(data, sargs); |
| |
| return DATA_FOR_EACH_CONT; |
| } |
| |
| /* |
| * Find every $ref = DATA_PARSER_* and add correct path |
| */ |
| static void _replace_refs(data_t *data, spec_args_t *sargs) |
| { |
| data_t *ref; |
| |
| xassert(sargs->magic == MAGIC_SPEC_ARGS); |
| xassert(sargs->args->magic == MAGIC_ARGS); |
| xassert(sargs->parsers); |
| xassert(sargs->parser_count > 0); |
| |
| if (!data) |
| return; |
| |
| if (data_get_type(data) == DATA_TYPE_LIST) |
| (void) data_list_for_each(data, _convert_list_entry, sargs); |
| |
| if (data_get_type(data) != DATA_TYPE_DICT) |
| return; |
| |
| if ((ref = data_key_get(data, "$ref")) && |
| (data_get_type(ref) == DATA_TYPE_STRING) && |
| !xstrncmp(data_get_string(ref), TYPE_PREFIX, |
| strlen(TYPE_PREFIX))) { |
| const parser_t *parser = NULL; |
| |
| for (int i = 0; i < sargs->parser_count; i++) { |
| if (!xstrcmp(sargs->parsers[i].type_string, |
| data_get_string(ref))) { |
| parser = &sargs->parsers[i]; |
| break; |
| } |
| } |
| |
| if (!parser) { |
| debug("%s: skipping unknown %s", |
| __func__, data_get_string(data)); |
| data_set_null(data); |
| return; |
| } |
| |
| _set_ref(data, NULL, parser, sargs); |
| } else { |
| (void) data_dict_for_each(data, _convert_dict_entry, sargs); |
| } |
| } |
| |
| static data_for_each_cmd_t _count_list_entry(data_t *data, void *arg) |
| { |
| spec_args_t *sargs = arg; |
| |
| xassert(sargs->magic == MAGIC_SPEC_ARGS); |
| xassert(sargs->args->magic == MAGIC_ARGS); |
| |
| if ((data_get_type(data) == DATA_TYPE_LIST) || |
| (data_get_type(data) == DATA_TYPE_DICT)) |
| _count_refs(data, sargs); |
| |
| return DATA_FOR_EACH_CONT; |
| } |
| |
| static void _increment_ref(const parser_t *parent, const parser_t *parser, |
| spec_args_t *sargs) |
| { |
| uint32_t parser_index; |
| |
| parser = unalias_parser(parser); |
| |
| if ((parser_index = _resolve_parser_index(parser, sargs)) != NO_VAL) { |
| sargs->references[parser_index]++; |
| |
| debug4("%s: %s->%s incremented references=%u", |
| __func__, (parent ? parent->type_string : "*" ), |
| parser->type_string, sargs->references[parser_index]); |
| } |
| } |
| |
| static data_for_each_cmd_t _count_dict_entry(const char *key, data_t *data, |
| void *arg) |
| { |
| spec_args_t *sargs = arg; |
| |
| xassert(sargs->magic == MAGIC_SPEC_ARGS); |
| xassert(sargs->args->magic == MAGIC_ARGS); |
| |
| if (!xstrcmp(key, "$ref") && |
| (data_get_type(data) == DATA_TYPE_STRING) && |
| !xstrncmp(data_get_string(data), TYPE_PREFIX, |
| strlen(TYPE_PREFIX))) |
| _increment_ref(NULL, _resolve_parser(data_get_string(data), |
| sargs), sargs); |
| |
| if ((data_get_type(data) == DATA_TYPE_LIST) || |
| (data_get_type(data) == DATA_TYPE_DICT)) |
| _count_refs(data, sargs); |
| |
| return DATA_FOR_EACH_CONT; |
| } |
| |
| /* |
| * Find every $ref = DATA_PARSER_* and count references |
| */ |
| static void _count_refs(data_t *data, spec_args_t *sargs) |
| { |
| xassert(sargs->magic == MAGIC_SPEC_ARGS); |
| xassert(sargs->args->magic == MAGIC_ARGS); |
| xassert(sargs->parsers); |
| xassert(sargs->parser_count > 0); |
| |
| if (!data) |
| return; |
| |
| if (data_get_type(data) == DATA_TYPE_DICT) |
| (void) data_dict_for_each(data, _count_dict_entry, sargs); |
| else if (data_get_type(data) == DATA_TYPE_LIST) |
| (void) data_list_for_each(data, _count_list_entry, sargs); |
| } |
| |
| static void _count_parser_refs(spec_args_t *sargs) |
| { |
| xassert(sargs->magic == MAGIC_SPEC_ARGS); |
| xassert(sargs->args->magic == MAGIC_ARGS); |
| xassert(sargs->parsers); |
| xassert(sargs->parser_count > 0); |
| |
| for (int ip = 0; ip < sargs->parser_count; ip++) { |
| const parser_t *parser = &sargs->parsers[ip]; |
| |
| if ((parser->model != PARSER_MODEL_ARRAY) || |
| !parser->field_count) |
| continue; |
| |
| for (int i = 0; i < parser->field_count; i++) { |
| const parser_t *pchild = |
| find_parser_by_type(parser->fields[i].type); |
| |
| if (pchild) |
| _increment_ref(parser, pchild, sargs); |
| } |
| } |
| } |
| |
| static data_t *_add_param(data_t *param, const char *name, |
| openapi_type_format_t format, bool allow_empty, |
| const char *desc, bool deprecated, bool required, |
| spec_args_t *args) |
| { |
| data_t *schema; |
| bool in_path = data_key_get(args->path_params, name); |
| |
| xassert(format > OPENAPI_FORMAT_INVALID); |
| xassert(format < OPENAPI_FORMAT_MAX); |
| |
| data_set_string(data_key_set(param, "in"), |
| (in_path ? "path" : "query")); |
| xassert(name); |
| data_set_string(data_key_set(param, "name"), name); |
| data_set_string(data_key_set(param, "style"), |
| (in_path ? "simple" : "form")); |
| data_set_bool(data_key_set(param, "explode"), false); |
| if (deprecated) |
| data_set_bool(data_key_set(param, "deprecated"), true); |
| data_set_bool(data_key_set(param, "allowEmptyValue"), allow_empty); |
| data_set_bool(data_key_set(param, "allowReserved"), false); |
| if (desc) |
| data_set_string(data_key_set(param, "description"), desc); |
| data_set_bool(data_key_set(param, "required"), (in_path || required)); |
| |
| schema = data_set_dict(data_key_set(param, "schema")); |
| data_set_string(data_key_set(schema, "type"), "string"); |
| |
| return schema; |
| } |
| |
| static void _add_param_eflags(data_t *params, const parser_t *parser, |
| spec_args_t *args) |
| { |
| parser = find_parser_by_type(parser->type); |
| |
| for (int i = 0; i < parser->flag_bit_array_count; i++) { |
| const flag_bit_t *bit = &parser->flag_bit_array[i]; |
| |
| if (!bit->hidden) |
| _add_param(data_set_dict(data_list_append(params)), |
| bit->name, OPENAPI_FORMAT_BOOL, true, |
| bit->description, |
| IS_FLAG_BIT_DEPRECATED(bit), false, args); |
| } |
| } |
| |
| static void _add_param_linked(data_t *params, const parser_t *fp, |
| spec_args_t *args) |
| { |
| data_t *schema; |
| const parser_t *p; |
| |
| if (fp->model == PARSER_MODEL_ARRAY_SKIP_FIELD) { |
| return; |
| } else if (fp->model == |
| PARSER_MODEL_ARRAY_LINKED_EXPLODED_FLAG_ARRAY_FIELD) { |
| _add_param_eflags(params, fp, args); |
| return; |
| } else if (fp->model == PARSER_MODEL_ARRAY_LINKED_FIELD) { |
| p = find_parser_by_type(fp->type); |
| } else { |
| p = fp; |
| } |
| |
| /* resolve out pointer type to first non-pointer */ |
| p = unalias_parser(p); |
| |
| if (p->model == PARSER_MODEL_ARRAY) { |
| /* no way to parse an dictionary/object currently */ |
| return; |
| } |
| |
| schema = _add_param(data_set_dict(data_list_append(params)), fp->key, |
| OPENAPI_FORMAT_STRING, |
| (p->obj_openapi == OPENAPI_FORMAT_BOOL), |
| fp->obj_desc, IS_PARSER_DEPRECATED(fp), |
| fp->required, args); |
| |
| if (fp->model == PARSER_MODEL_ARRAY_LINKED_FIELD) |
| fp = find_parser_by_type(fp->type); |
| |
| if (fp->flag_bit_array) |
| _add_param_flag_enum(schema, fp); |
| } |
| |
| static data_for_each_cmd_t _foreach_path_method_ref(data_t *ref, void *arg) |
| { |
| spec_args_t *args = arg; |
| const parser_t *parser; |
| |
| if (!(parser = _resolve_parser(data_get_string(ref), args))) { |
| error("%s: Unable to find parser for $ref = %s", |
| __func__, data_get_string(ref)); |
| return DATA_FOR_EACH_FAIL; |
| } |
| |
| /* auto-dereference pointers to avoid unneeded resolution failures */ |
| parser = unalias_parser(parser); |
| |
| if (parser->model != PARSER_MODEL_ARRAY) { |
| error("$ref parameters must be an array parser"); |
| return DATA_FOR_EACH_FAIL; |
| } |
| |
| debug3("$ref=%s found parser %s(0x%"PRIxPTR")=%s", |
| data_get_string(ref), parser->type_string, (uintptr_t) parser, |
| parser->obj_type_string); |
| |
| for (int i = 0; i < parser->field_count; i++) |
| _add_param_linked(args->params, &parser->fields[i], args); |
| |
| return DATA_FOR_EACH_CONT; |
| } |
| |
| static data_for_each_cmd_t _foreach_path_method(const char *key, data_t *data, |
| void *arg) |
| { |
| spec_args_t *args = arg; |
| data_t *params, *ref, *refs; |
| int rc = DATA_FOR_EACH_CONT; |
| |
| if (data_get_type(data) != DATA_TYPE_DICT) |
| return DATA_FOR_EACH_CONT; |
| |
| if (!(params = data_key_get(data, OPENAPI_PATH_PARAMS_FIELD))) |
| return DATA_FOR_EACH_CONT; |
| |
| if (data_get_type(params) != DATA_TYPE_DICT) |
| return DATA_FOR_EACH_CONT; |
| |
| if (!(ref = data_key_get(params, OPENAPI_REF_TAG))) |
| return DATA_FOR_EACH_CONT; |
| |
| refs = data_new(); |
| data_move(refs, ref); |
| args->params = data_set_list(params); |
| |
| if (data_get_type(refs) == DATA_TYPE_LIST) { |
| if (data_list_for_each(refs, _foreach_path_method_ref, |
| args) < 0) |
| rc = DATA_FOR_EACH_FAIL; |
| } else if (data_get_type(refs) == DATA_TYPE_STRING) { |
| rc = _foreach_path_method_ref(refs, args); |
| } else { |
| error("$ref must be string or dict"); |
| return DATA_FOR_EACH_FAIL; |
| } |
| |
| FREE_NULL_DATA(refs); |
| return rc; |
| } |
| |
| static data_for_each_cmd_t _foreach_path_entry(data_t *data, void *arg) |
| { |
| spec_args_t *args = arg; |
| char *path, *path2; |
| |
| if (data_convert_type(data, DATA_TYPE_STRING) != DATA_TYPE_STRING) |
| return DATA_FOR_EACH_FAIL; |
| |
| path = xstrdup(data_get_string(data)); |
| |
| if (path[0] != '{') { |
| xfree(path); |
| return DATA_FOR_EACH_CONT; |
| } |
| |
| if ((path2 = xstrstr(path, "}"))) |
| *path2 = '\0'; |
| |
| data_key_set(args->path_params, (path + 1)); |
| |
| xfree(path); |
| |
| return DATA_FOR_EACH_CONT; |
| } |
| |
| static data_for_each_cmd_t _foreach_path(const char *key, data_t *data, |
| void *arg) |
| { |
| int rc = SLURM_SUCCESS; |
| char *param, *start, *end, *replaced; |
| spec_args_t *args = arg; |
| data_t *n, *path; |
| |
| param = xstrdup(key); |
| |
| if (!(start = xstrstr(param, OPENAPI_DATA_PARSER_PARAM))) { |
| xfree(param); |
| return DATA_FOR_EACH_CONT; |
| } |
| |
| *start = '\0'; |
| end = start + strlen(OPENAPI_DATA_PARSER_PARAM); |
| replaced = xstrdup_printf("%s%s%s", param, XSTRINGIFY(DATA_VERSION), |
| end); |
| xfree(param); |
| |
| if (!args->new_paths) |
| args->new_paths = data_set_dict(data_new()); |
| |
| n = data_key_set(args->new_paths, replaced); |
| |
| data_copy(n, data); |
| |
| args->path_params = data_set_dict(data_new()); |
| path = parse_url_path(replaced, false, true); |
| if (data_list_for_each(path, _foreach_path_entry, args) < 0) |
| rc = SLURM_ERROR; |
| FREE_NULL_DATA(path); |
| |
| if (!rc && (data_dict_for_each(n, _foreach_path_method, args) < 0)) |
| rc = SLURM_ERROR; |
| |
| xfree(replaced); |
| FREE_NULL_DATA(args->path_params); |
| |
| return rc ? DATA_FOR_EACH_FAIL : DATA_FOR_EACH_CONT; |
| } |
| |
| static data_for_each_cmd_t _foreach_join_path(const char *key, data_t *data, |
| void *arg) |
| { |
| spec_args_t *args = arg; |
| data_t *path = data_key_set(args->paths, key); |
| |
| data_move(path, data); |
| _count_refs(path, args); |
| _count_parser_refs(args); |
| _replace_refs(path, args); |
| |
| return DATA_FOR_EACH_CONT; |
| } |
| |
| extern int data_parser_p_specify(args_t *args, data_t *spec) |
| { |
| spec_args_t sargs = { |
| .magic = MAGIC_SPEC_ARGS, |
| .args = args, |
| .spec = spec, |
| }; |
| |
| xassert(args->magic == MAGIC_ARGS); |
| |
| if (!spec || (data_get_type(spec) != DATA_TYPE_DICT)) |
| return error("OpenAPI specification invalid"); |
| |
| sargs.schemas = data_resolve_dict_path(spec, OPENAPI_SCHEMAS_PATH); |
| sargs.paths = data_resolve_dict_path(spec, OPENAPI_PATHS_PATH); |
| |
| if (!sargs.schemas || (data_get_type(sargs.schemas) != DATA_TYPE_DICT)) |
| return error("%s not found or invalid type", |
| OPENAPI_SCHEMAS_PATH); |
| |
| get_parsers(&sargs.parsers, &sargs.parser_count); |
| sargs.references = xcalloc(sargs.parser_count, |
| sizeof(*sargs.references)); |
| |
| (void) data_dict_for_each(sargs.paths, _foreach_path, &sargs); |
| (void) data_dict_for_each(sargs.new_paths, _foreach_join_path, &sargs); |
| FREE_NULL_DATA(sargs.new_paths); |
| xfree(sargs.references); |
| |
| return SLURM_SUCCESS; |
| } |
| |
| extern void set_openapi_schema(data_t *dst, const parser_t *parser, |
| args_t *args) |
| { |
| spec_args_t sargs = { |
| .magic = MAGIC_SPEC_ARGS, |
| .args = args, |
| .spec = dst, |
| .disable_refs = true, |
| }; |
| |
| xassert(args->magic == MAGIC_ARGS); |
| xassert(data_get_type(dst) == DATA_TYPE_NULL); |
| |
| data_set_dict(dst); |
| |
| get_parsers(&sargs.parsers, &sargs.parser_count); |
| |
| (void) _set_openapi_parse(dst, parser, &sargs, NULL, false); |
| } |
| |
| extern int data_parser_p_increment_reference(args_t *args, |
| data_parser_type_t type, |
| refs_ptr_t **references_ptr) |
| { |
| spec_args_t sargs = { |
| .magic = MAGIC_SPEC_ARGS, |
| .args = args, |
| }; |
| refs_ptr_t *refs = *references_ptr; |
| const parser_t *parser; |
| |
| get_parsers(&sargs.parsers, &sargs.parser_count); |
| |
| if (!refs) { |
| refs = *references_ptr = xmalloc(sizeof(*refs)); |
| refs->magic = MAGIC_REFS_PTR; |
| refs->references = |
| xcalloc(sargs.parser_count, sizeof(*refs->references)); |
| } |
| |
| if (!(parser = find_parser_by_type(type))) |
| return ESLURM_DATA_INVALID_PARSER; |
| |
| xassert(refs->magic == MAGIC_REFS_PTR); |
| xassert(args->magic == MAGIC_ARGS); |
| xassert(type > DATA_PARSER_TYPE_INVALID); |
| xassert(type < DATA_PARSER_TYPE_MAX); |
| xassert(sargs.parser_count > 0); |
| |
| sargs.references = refs->references, |
| _increment_ref(NULL, parser, &sargs); |
| |
| return SLURM_SUCCESS; |
| } |
| |
| extern int data_parser_p_populate_schema(args_t *args, data_parser_type_t type, |
| refs_ptr_t **references_ptr, |
| data_t *dst, data_t *schemas) |
| { |
| #ifndef NDEBUG |
| refs_ptr_t *refs = *references_ptr; |
| #endif |
| spec_args_t sargs = { |
| .magic = MAGIC_SPEC_ARGS, |
| .args = args, |
| .schemas = schemas, |
| .references = (*references_ptr)->references, |
| }; |
| const parser_t *parser; |
| |
| xassert(refs && refs->magic == MAGIC_REFS_PTR); |
| xassert(args->magic == MAGIC_ARGS); |
| xassert(type > DATA_PARSER_TYPE_INVALID); |
| xassert(type < DATA_PARSER_TYPE_MAX); |
| xassert(data_get_type(dst) == DATA_TYPE_DICT); |
| |
| get_parsers(&sargs.parsers, &sargs.parser_count); |
| if (!(parser = find_parser_by_type(type))) |
| return ESLURM_DATA_INVALID_PARSER; |
| |
| _set_ref(dst, NULL, parser, &sargs); |
| |
| return SLURM_SUCCESS; |
| } |
| |
| extern int data_parser_p_populate_parameters(args_t *args, |
| data_parser_type_t parameter_type, |
| data_parser_type_t query_type, |
| refs_ptr_t **references_ptr, |
| data_t *dst, data_t *schemas) |
| { |
| #ifndef NDEBUG |
| refs_ptr_t *refs = *references_ptr; |
| #endif |
| spec_args_t sargs = { |
| .magic = MAGIC_SPEC_ARGS, |
| .args = args, |
| .schemas = schemas, |
| .references = (*references_ptr)->references, |
| }; |
| const parser_t *param_parser = NULL, *query_parser = NULL; |
| |
| xassert(refs && refs->magic == MAGIC_REFS_PTR); |
| xassert(args->magic == MAGIC_ARGS); |
| xassert(!parameter_type || (parameter_type > DATA_PARSER_TYPE_INVALID)); |
| xassert(!parameter_type || (parameter_type < DATA_PARSER_TYPE_MAX)); |
| xassert(!query_type || (query_type > DATA_PARSER_TYPE_INVALID)); |
| xassert(!query_type || (query_type < DATA_PARSER_TYPE_MAX)); |
| xassert(data_get_type(dst) == DATA_TYPE_NULL); |
| |
| data_set_list(dst); |
| |
| get_parsers(&sargs.parsers, &sargs.parser_count); |
| |
| sargs.path_params = data_set_dict(data_new()); |
| |
| if (parameter_type && |
| !(param_parser = |
| unalias_parser(find_parser_by_type(parameter_type)))) |
| return ESLURM_DATA_INVALID_PARSER; |
| if (query_type && |
| !(query_parser = unalias_parser(find_parser_by_type(query_type)))) |
| return ESLURM_DATA_INVALID_PARSER; |
| |
| if (param_parser) { |
| if (param_parser->model != PARSER_MODEL_ARRAY) |
| fatal_abort("parameters must be an array parser"); |
| |
| debug3("%s: adding parameter %s(0x%"PRIxPTR")=%s to %pd", |
| __func__, param_parser->type_string, |
| (uintptr_t) param_parser, param_parser->obj_type_string, |
| dst); |
| |
| for (int i = 0; i < param_parser->field_count; i++) |
| data_key_set(sargs.path_params, |
| param_parser->fields[i].key); |
| |
| for (int i = 0; i < param_parser->field_count; i++) |
| _add_param_linked(dst, ¶m_parser->fields[i], |
| &sargs); |
| } |
| if (query_parser) { |
| if (query_parser->model != PARSER_MODEL_ARRAY) |
| fatal_abort("parameters must be an array parser"); |
| |
| debug3("%s: adding parameter %s(0x%"PRIxPTR")=%s to %pd", |
| __func__, query_parser->type_string, |
| (uintptr_t) query_parser, query_parser->obj_type_string, |
| dst); |
| |
| for (int i = 0; i < query_parser->field_count; i++) |
| _add_param_linked(dst, &query_parser->fields[i], |
| &sargs); |
| } |
| |
| FREE_NULL_DATA(sargs.path_params); |
| |
| return SLURM_SUCCESS; |
| } |
| |
| extern void data_parser_p_release_references(args_t *args, |
| refs_ptr_t **references_ptr) |
| { |
| refs_ptr_t *refs = *references_ptr; |
| |
| xassert(args->magic == MAGIC_ARGS); |
| |
| if (!refs) |
| return; |
| |
| *references_ptr = NULL; |
| |
| xassert(refs->magic == MAGIC_REFS_PTR); |
| xfree(refs->references); |
| refs->magic = ~MAGIC_REFS_PTR; |
| xfree(refs); |
| } |