| /*****************************************************************************\ | |
| * 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; | |
| 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 (will take ownership) | |
| * 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, | |
| 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_own(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) && | |
| (parser->model != PARSER_MODEL_FLAG_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); | |
| } | |
| static void _gen_desc(char **desc_ptr, char **desc_at_ptr, | |
| const parser_t *parser) | |
| { | |
| bool has_key = (parser && parser->key && parser->key[0]); | |
| bool has_parser = (parser && parser->obj_desc && parser->obj_desc[0]); | |
| xassert(parser); | |
| if (!has_parser) | |
| return; | |
| if (has_key) | |
| _xstrfmtcatat(desc_ptr, desc_at_ptr, "%s", parser->obj_desc); | |
| else | |
| _xstrfmtcatat(desc_ptr, desc_at_ptr, "%s%s%s", | |
| (*desc_ptr ? " (" : ""), parser->obj_desc, | |
| (*desc_ptr ? ")" : "")); | |
| } | |
| /* | |
| * 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, 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 (!desc) { | |
| char *desc_at = NULL; | |
| _gen_desc(&desc, &desc_at, parser); | |
| } | |
| 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; | |
| char *desc = NULL, *desc_at = NULL; | |
| bool deprecated = (parent && parent->deprecated); | |
| xassert(sargs->magic == MAGIC_SPEC_ARGS); | |
| xassert(sargs->args->magic == MAGIC_ARGS); | |
| while (true) { | |
| _gen_desc(&desc, &desc_at, parser); | |
| /* 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) { | |
| xassert(!data_key_get(obj, "description")); | |
| data_set_string_own(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); | |
| 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; | |
| data_set_list(dst); | |
| get_parsers(&sargs.parsers, &sargs.parser_count); | |
| sargs.path_params = data_set_dict(data_new()); | |
| 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); | |
| } |