diff options
| author | Justin M. Keyes <justinkz@gmail.com> | 2023-02-14 05:19:04 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-02-14 02:19:04 -0800 |
| commit | 46a87a5d2bac598fed0870f0d3c926087f95d30f (patch) | |
| tree | 500dba055ff89fcc0610e7a497d7a4471bff04f1 | |
| parent | 53968082675cd3b8d1809e53a47c0311b7347ef9 (diff) | |
| download | rneovim-46a87a5d2bac598fed0870f0d3c926087f95d30f.tar.gz rneovim-46a87a5d2bac598fed0870f0d3c926087f95d30f.tar.bz2 rneovim-46a87a5d2bac598fed0870f0d3c926087f95d30f.zip | |
refactor(api): VALIDATE macros #22187
Problem:
- API validation involves too much boilerplate.
- API validation errors are not consistently worded.
Solution:
Introduce some macros. Currently these are clumsy, but they at least
help with consistency and avoid some nesting.
| -rw-r--r-- | src/nvim/api/autocmd.c | 229 | ||||
| -rw-r--r-- | src/nvim/api/buffer.c | 153 | ||||
| -rw-r--r-- | src/nvim/api/command.c | 63 | ||||
| -rw-r--r-- | src/nvim/api/extmark.c | 268 | ||||
| -rw-r--r-- | src/nvim/api/options.c | 128 | ||||
| -rw-r--r-- | src/nvim/api/private/helpers.c | 30 | ||||
| -rw-r--r-- | src/nvim/api/private/validate.h | 69 | ||||
| -rw-r--r-- | src/nvim/api/vim.c | 171 | ||||
| -rw-r--r-- | src/nvim/option.c | 6 | ||||
| -rw-r--r-- | src/nvim/ui.c | 9 | ||||
| -rw-r--r-- | test/functional/api/autocmd_spec.lua | 18 | ||||
| -rw-r--r-- | test/functional/api/buffer_updates_spec.lua | 2 | ||||
| -rw-r--r-- | test/functional/api/command_spec.lua | 2 | ||||
| -rw-r--r-- | test/functional/api/extmark_spec.lua | 14 | ||||
| -rw-r--r-- | test/functional/api/vim_spec.lua | 56 | ||||
| -rw-r--r-- | test/functional/lua/vim_spec.lua | 4 | ||||
| -rw-r--r-- | test/functional/ui/bufhl_spec.lua | 4 |
17 files changed, 592 insertions, 634 deletions
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index f801c716e5..9aadd48a13 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -12,6 +12,7 @@ #include "nvim/api/autocmd.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/private/validate.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" @@ -107,22 +108,21 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) break; case kObjectTypeString: group = augroup_find(opts->group.data.string.data); - if (group < 0) { - api_set_error(err, kErrorTypeValidation, "invalid augroup passed."); + VALIDATE_S((group >= 0), "group", "", { goto cleanup; - } + }); break; case kObjectTypeInteger: group = (int)opts->group.data.integer; char *name = augroup_name(group); - if (!augroup_exists(name)) { - api_set_error(err, kErrorTypeValidation, "invalid augroup passed."); + VALIDATE_S(augroup_exists(name), "group", "", { goto cleanup; - } + }); break; default: - api_set_error(err, kErrorTypeValidation, "group must be a string or an integer."); - goto cleanup; + VALIDATE_S(false, "group (must be string or integer)", "", { + goto cleanup; + }); } if (opts->event.type != kObjectTypeNil) { @@ -134,29 +134,24 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) event_set[event_nr] = true; } else if (v.type == kObjectTypeArray) { FOREACH_ITEM(v.data.array, event_v, { - if (event_v.type != kObjectTypeString) { - api_set_error(err, - kErrorTypeValidation, - "Every event must be a string in 'event'"); + VALIDATE_T("event item", kObjectTypeString, event_v.type, { goto cleanup; - } + }); GET_ONE_EVENT(event_nr, event_v, cleanup); event_set[event_nr] = true; }) } else { - api_set_error(err, - kErrorTypeValidation, - "Not a valid 'event' value. Must be a string or an array"); - goto cleanup; + VALIDATE_S(false, "event (must be String or Array)", "", { + goto cleanup; + }); } } - if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { - api_set_error(err, kErrorTypeValidation, - "Cannot use both 'pattern' and 'buffer'"); + VALIDATE((opts->pattern.type == kObjectTypeNil || opts->buffer.type == kObjectTypeNil), + "Cannot use both 'pattern' and 'buffer'", { goto cleanup; - } + }); int pattern_filter_count = 0; if (opts->pattern.type != kObjectTypeNil) { @@ -166,25 +161,23 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) pattern_filter_count += 1; } else if (v.type == kObjectTypeArray) { if (v.data.array.size > AUCMD_MAX_PATTERNS) { - api_set_error(err, kErrorTypeValidation, - "Too many patterns. Please limit yourself to %d or fewer", + api_set_error(err, kErrorTypeValidation, "Too many patterns (maximum of %d)", AUCMD_MAX_PATTERNS); goto cleanup; } FOREACH_ITEM(v.data.array, item, { - if (item.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'pattern': must be a string"); + VALIDATE_T("pattern", kObjectTypeString, item.type, { goto cleanup; - } + }); pattern_filters[pattern_filter_count] = item.data.string.data; pattern_filter_count += 1; }); } else { - api_set_error(err, kErrorTypeValidation, - "Not a valid 'pattern' value. Must be a string or an array"); - goto cleanup; + VALIDATE_EXP(false, "pattern", "String or Array", api_typename(v.type), { + goto cleanup; + }); } } @@ -198,17 +191,16 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) ADD(buffers, CSTR_TO_OBJ(pattern_buflocal)); } else if (opts->buffer.type == kObjectTypeArray) { if (opts->buffer.data.array.size > AUCMD_MAX_PATTERNS) { - api_set_error(err, - kErrorTypeValidation, - "Too many buffers. Please limit yourself to %d or fewer", AUCMD_MAX_PATTERNS); + api_set_error(err, kErrorTypeValidation, "Too many buffers (maximum of %d)", + AUCMD_MAX_PATTERNS); goto cleanup; } FOREACH_ITEM(opts->buffer.data.array, bufnr, { - if (bufnr.type != kObjectTypeInteger && bufnr.type != kObjectTypeBuffer) { - api_set_error(err, kErrorTypeValidation, "Invalid value for 'buffer': must be an integer"); + VALIDATE_EXP((bufnr.type == kObjectTypeInteger || bufnr.type == kObjectTypeBuffer), + "buffer", "Integer", api_typename(bufnr.type), { goto cleanup; - } + }); buf_T *buf = find_buffer_by_handle((Buffer)bufnr.data.integer, err); if (ERROR_SET(err)) { @@ -219,9 +211,9 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) ADD(buffers, CSTR_TO_OBJ(pattern_buflocal)); }); } else if (opts->buffer.type != kObjectTypeNil) { - api_set_error(err, kErrorTypeValidation, - "Invalid value for 'buffer': must be an integer or array of integers"); - goto cleanup; + VALIDATE_EXP(false, "buffer", "Integer or Array", api_typename(opts->buffer.type), { + goto cleanup; + }); } FOREACH_ITEM(buffers, bufnr, { @@ -432,30 +424,23 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc goto cleanup; } - if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) { - api_set_error(err, kErrorTypeValidation, "specify either 'callback' or 'command', not both"); + VALIDATE((opts->callback.type == kObjectTypeNil || opts->command.type == kObjectTypeNil), + "Cannot use both 'callback' and 'command'", { goto cleanup; - } else if (opts->callback.type != kObjectTypeNil) { - // TODO(tjdevries): It's possible we could accept callable tables, - // but we don't do that many other places, so for the moment let's - // not do that. + }); + + if (opts->callback.type != kObjectTypeNil) { + // NOTE: We could accept callable tables, but that isn't common in the API. Object *callback = &opts->callback; switch (callback->type) { case kObjectTypeLuaRef: - if (callback->data.luaref == LUA_NOREF) { - api_set_error(err, - kErrorTypeValidation, - "must pass an actual value"); + VALIDATE_S((callback->data.luaref != LUA_NOREF), "callback", "<no value>", { goto cleanup; - } - - if (!nlua_ref_is_function(callback->data.luaref)) { - api_set_error(err, - kErrorTypeValidation, - "must pass a function for callback"); + }); + VALIDATE_S(nlua_ref_is_function(callback->data.luaref), "callback", "<not a function>", { goto cleanup; - } + }); cb.type = kCallbackLua; cb.data.luaref = api_new_luaref(callback->data.luaref); @@ -465,28 +450,25 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc cb.data.funcref = string_to_cstr(callback->data.string); break; default: - api_set_error(err, - kErrorTypeException, - "'callback' must be a lua function or name of vim function"); - goto cleanup; + VALIDATE_EXP(false, "callback", "Lua function or Vim function name", + api_typename(callback->type), { + goto cleanup; + }); } aucmd.type = CALLABLE_CB; aucmd.callable.cb = cb; } else if (opts->command.type != kObjectTypeNil) { Object *command = &opts->command; - if (command->type == kObjectTypeString) { - aucmd.type = CALLABLE_EX; - aucmd.callable.cmd = string_to_cstr(command->data.string); - } else { - api_set_error(err, - kErrorTypeValidation, - "'command' must be a string"); + VALIDATE_T("command", kObjectTypeString, command->type, { goto cleanup; - } + }); + aucmd.type = CALLABLE_EX; + aucmd.callable.cmd = string_to_cstr(command->data.string); } else { - api_set_error(err, kErrorTypeValidation, "must pass one of: 'command', 'callback'"); - goto cleanup; + VALIDATE_S(false, "'command' or 'callback' is required", "", { + goto cleanup; + }); } bool is_once = api_object_to_bool(opts->once, "once", false, err); @@ -502,24 +484,19 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc } if (opts->desc.type != kObjectTypeNil) { - if (opts->desc.type == kObjectTypeString) { - desc = opts->desc.data.string.data; - } else { - api_set_error(err, - kErrorTypeValidation, - "'desc' must be a string"); + VALIDATE_T("desc", kObjectTypeString, opts->desc.type, { goto cleanup; - } + }); + desc = opts->desc.data.string.data; } if (patterns.size == 0) { ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("*"))); } - if (event_array.size == 0) { - api_set_error(err, kErrorTypeValidation, "'event' is a required key"); + VALIDATE_R((event_array.size != 0), "event", { goto cleanup; - } + }); autocmd_id = next_autocmd_id++; FOREACH_ITEM(event_array, event_str, { @@ -564,10 +541,9 @@ cleanup: void nvim_del_autocmd(Integer id, Error *err) FUNC_API_SINCE(9) { - if (id <= 0) { - api_set_error(err, kErrorTypeException, "Invalid autocmd id"); + VALIDATE_INT((id > 0), "autocmd id", id, { return; - } + }); if (!autocmd_delete_id(id)) { api_set_error(err, kErrorTypeException, "Failed to delete autocmd"); } @@ -610,11 +586,10 @@ void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err) goto cleanup; } - if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { - api_set_error(err, kErrorTypeValidation, - "Cannot use both 'pattern' and 'buffer'"); + VALIDATE((opts->pattern.type == kObjectTypeNil || opts->buffer.type == kObjectTypeNil), + "Cannot use both 'pattern' and 'buffer'", { goto cleanup; - } + }); int au_group = get_augroup_from_object(opts->group, err); if (au_group == AUGROUP_ERROR) { @@ -772,32 +747,29 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err) break; case kObjectTypeString: au_group = augroup_find(opts->group.data.string.data); - if (au_group == AUGROUP_ERROR) { - api_set_error(err, - kErrorTypeValidation, - "invalid augroup: %s", opts->group.data.string.data); + VALIDATE_S((au_group != AUGROUP_ERROR), "group", opts->group.data.string.data, { goto cleanup; - } + }); break; case kObjectTypeInteger: au_group = (int)opts->group.data.integer; char *name = augroup_name(au_group); - if (!augroup_exists(name)) { - api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group); + VALIDATE_INT(augroup_exists(name), "group", (int64_t)au_group, { goto cleanup; - } + }); break; default: - api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); - goto cleanup; + VALIDATE_EXP(false, "group", "String or Integer", api_typename(opts->group.type), { + goto cleanup; + }); } if (opts->buffer.type != kObjectTypeNil) { Object buf_obj = opts->buffer; - if (buf_obj.type != kObjectTypeInteger && buf_obj.type != kObjectTypeBuffer) { - api_set_error(err, kErrorTypeException, "invalid buffer: %d", buf_obj.type); + VALIDATE_EXP((buf_obj.type == kObjectTypeInteger || buf_obj.type == kObjectTypeBuffer), + "buffer", "Integer", api_typename(buf_obj.type), { goto cleanup; - } + }); buf = find_buffer_by_handle((Buffer)buf_obj.data.integer, err); @@ -844,18 +816,15 @@ static bool check_autocmd_string_array(Array arr, char *k, Error *err) { FOREACH_ITEM(arr, entry, { if (entry.type != kObjectTypeString) { - api_set_error(err, - kErrorTypeValidation, - "All entries in '%s' must be strings", - k); + api_set_error(err, kErrorTypeValidation, "Invalid '%s' item type: expected String, got %s", + k, api_typename(entry.type)); return false; } // Disallow newlines in the middle of the line. const String l = entry.data.string; if (memchr(l.data, NL, l.size)) { - api_set_error(err, kErrorTypeValidation, - "String cannot contain newlines"); + api_set_error(err, kErrorTypeValidation, "'%s' item cannot contain newlines", k); return false; } }) @@ -873,10 +842,9 @@ static bool unpack_string_or_array(Array *array, Object *v, char *k, bool requir *array = copy_array(v->data.array, NULL); } else { if (required) { - api_set_error(err, - kErrorTypeValidation, - "'%s' must be an array or a string.", - k); + api_set_error(err, kErrorTypeValidation, + "Invalid '%s' type: expected Array or String, got %s", + k, api_typename(v->type)); return false; } } @@ -894,27 +862,22 @@ static int get_augroup_from_object(Object group, Error *err) return AUGROUP_DEFAULT; case kObjectTypeString: au_group = augroup_find(group.data.string.data); - if (au_group == AUGROUP_ERROR) { - api_set_error(err, - kErrorTypeValidation, - "invalid augroup: %s", group.data.string.data); - + VALIDATE_S((au_group != AUGROUP_ERROR), "group", group.data.string.data, { return AUGROUP_ERROR; - } + }); return au_group; case kObjectTypeInteger: au_group = (int)group.data.integer; char *name = augroup_name(au_group); - if (!augroup_exists(name)) { - api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group); + VALIDATE_INT(augroup_exists(name), "group", (int64_t)au_group, { return AUGROUP_ERROR; - } - + }); return au_group; default: - api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); - return AUGROUP_ERROR; + VALIDATE_EXP(false, "group", "String or Integer", api_typename(group.type), { + return AUGROUP_ERROR; + }); } } @@ -923,11 +886,12 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob { const char pattern_buflocal[BUFLOCAL_PAT_LEN]; - if (pattern.type != kObjectTypeNil && buffer.type != kObjectTypeNil) { - api_set_error(err, kErrorTypeValidation, - "cannot pass both: 'pattern' and 'buffer' for the same autocmd"); + VALIDATE((pattern.type == kObjectTypeNil || buffer.type == kObjectTypeNil), + "Cannot use both 'pattern' and 'buffer' for the same autocmd", { return false; - } else if (pattern.type != kObjectTypeNil) { + }); + + if (pattern.type != kObjectTypeNil) { Object *v = &pattern; if (v->type == kObjectTypeString) { @@ -956,18 +920,15 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob } }) } else { - api_set_error(err, - kErrorTypeValidation, - "'pattern' must be a string or table"); - return false; + VALIDATE_EXP(false, "pattern", "String or Table", api_typename(v->type), { + return false; + }); } } else if (buffer.type != kObjectTypeNil) { - if (buffer.type != kObjectTypeInteger && buffer.type != kObjectTypeBuffer) { - api_set_error(err, - kErrorTypeValidation, - "'buffer' must be an integer"); + VALIDATE_EXP((buffer.type == kObjectTypeInteger || buffer.type == kObjectTypeBuffer), + "buffer", "Integer", api_typename(buffer.type), { return false; - } + }); buf_T *buf = find_buffer_by_handle((Buffer)buffer.data.integer, err); if (ERROR_SET(err)) { diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index fe9e6077d6..b2e78db278 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -16,6 +16,7 @@ #include "nvim/api/buffer.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/private/validate.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" @@ -179,11 +180,9 @@ Boolean nvim_buf_attach(uint64_t channel_id, Buffer buffer, Boolean send_buffer, if (is_lua) { for (size_t j = 0; cbs[j].name; j++) { if (strequal(cbs[j].name, k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, - "%s is not a function", cbs[j].name); + VALIDATE_T(cbs[j].name, kObjectTypeLuaRef, v->type, { goto error; - } + }); *(cbs[j].dest) = v->data.luaref; v->data.luaref = LUA_NOREF; key_used = true; @@ -194,26 +193,23 @@ Boolean nvim_buf_attach(uint64_t channel_id, Buffer buffer, Boolean send_buffer, if (key_used) { continue; } else if (strequal("utf_sizes", k.data)) { - if (v->type != kObjectTypeBoolean) { - api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean"); + VALIDATE_T("utf_sizes", kObjectTypeBoolean, v->type, { goto error; - } + }); cb.utf_sizes = v->data.boolean; key_used = true; } else if (strequal("preview", k.data)) { - if (v->type != kObjectTypeBoolean) { - api_set_error(err, kErrorTypeValidation, "preview must be boolean"); + VALIDATE_T("preview", kObjectTypeBoolean, v->type, { goto error; - } + }); cb.preview = v->data.boolean; key_used = true; } } - if (!key_used) { - api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + VALIDATE_S(key_used, "key", k.data, { goto error; - } + }); } return buf_updates_register(buf, channel_id, cb, send_buffer); @@ -297,10 +293,9 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, start = normalize_index(buf, start, true, &oob); end = normalize_index(buf, end, true, &oob); - if (strict_indexing && oob) { - api_set_error(err, kErrorTypeValidation, "Index out of bounds"); + VALIDATE((!strict_indexing || !oob), "Index out of bounds", { return rv; - } + }); if (start >= end) { // Return 0-length array @@ -328,20 +323,15 @@ end: static bool check_string_array(Array arr, bool disallow_nl, Error *err) { for (size_t i = 0; i < arr.size; i++) { - if (arr.items[i].type != kObjectTypeString) { - api_set_error(err, - kErrorTypeValidation, - "All items in the replacement array must be strings"); + VALIDATE_T("replacement item", kObjectTypeString, arr.items[i].type, { return false; - } + }); // Disallow newlines in the middle of the line. if (disallow_nl) { const String l = arr.items[i].data.string; - if (memchr(l.data, NL, l.size)) { - api_set_error(err, kErrorTypeValidation, - "String cannot contain newlines"); + VALIDATE(!memchr(l.data, NL, l.size), "String cannot contain newlines", { return false; - } + }); } } return true; @@ -383,17 +373,12 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ start = normalize_index(buf, start, true, &oob); end = normalize_index(buf, end, true, &oob); - if (strict_indexing && oob) { - api_set_error(err, kErrorTypeValidation, "Index out of bounds"); + VALIDATE((!strict_indexing || !oob), "Index out of bounds", { return; - } - - if (start > end) { - api_set_error(err, - kErrorTypeValidation, - "Argument \"start\" is higher than \"end\""); + }); + VALIDATE((start <= end), "\"start\" is higher than \"end\"", { return; - } + }); bool disallow_nl = (channel_id != VIML_INTERNAL_CALL); if (!check_string_array(replacement, disallow_nl, err)) { @@ -453,10 +438,9 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ for (size_t i = 0; i < to_replace; i++) { int64_t lnum = start + (int64_t)i; - if (lnum >= MAXLNUM) { - api_set_error(err, kErrorTypeValidation, "Index value is too high"); + VALIDATE(lnum < MAXLNUM, "Index value is too high", { goto end; - } + }); if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to replace line"); @@ -473,10 +457,9 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ for (size_t i = to_replace; i < new_len; i++) { int64_t lnum = start + (int64_t)i - 1; - if (lnum >= MAXLNUM) { - api_set_error(err, kErrorTypeValidation, "Index value is too high"); + VALIDATE(lnum < MAXLNUM, "Index value is too high", { goto end; - } + }); if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to insert line"); @@ -563,16 +546,14 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In // check range is ordered and everything! // start_row, end_row within buffer len (except add text past the end?) start_row = normalize_index(buf, start_row, false, &oob); - if (oob) { - api_set_error(err, kErrorTypeValidation, "start_row out of bounds"); + VALIDATE((!oob), "start_row out of bounds", { return; - } + }); end_row = normalize_index(buf, end_row, false, &oob); - if (oob) { - api_set_error(err, kErrorTypeValidation, "end_row out of bounds"); + VALIDATE((!oob), "end_row out of bounds", { return; - } + }); char *str_at_start = NULL; char *str_at_end = NULL; @@ -580,23 +561,21 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In // Another call to ml_get_buf() may free the line, so make a copy. str_at_start = xstrdup(ml_get_buf(buf, (linenr_T)start_row, false)); size_t len_at_start = strlen(str_at_start); - if (start_col < 0 || (size_t)start_col > len_at_start) { - api_set_error(err, kErrorTypeValidation, "start_col out of bounds"); + VALIDATE((start_col >= 0 && (size_t)start_col <= len_at_start), "start_col out of bounds", { goto early_end; - } + }); // Another call to ml_get_buf() may free the line, so make a copy. str_at_end = xstrdup(ml_get_buf(buf, (linenr_T)end_row, false)); size_t len_at_end = strlen(str_at_end); - if (end_col < 0 || (size_t)end_col > len_at_end) { - api_set_error(err, kErrorTypeValidation, "end_col out of bounds"); + VALIDATE((end_col >= 0 && (size_t)end_col <= len_at_end), "end_col out of bounds", { goto early_end; - } + }); - if (start_row > end_row || (end_row == start_row && start_col > end_col)) { - api_set_error(err, kErrorTypeValidation, "start is higher than end"); + VALIDATE((start_row <= end_row && !(end_row == start_row && start_col > end_col)), + "start is higher than end", { goto early_end; - } + }); bool disallow_nl = (channel_id != VIML_INTERNAL_CALL); if (!check_string_array(replacement, disallow_nl, err)) { @@ -702,10 +681,9 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In for (size_t i = 0; i < to_replace; i++) { int64_t lnum = start_row + (int64_t)i; - if (lnum >= MAXLNUM) { - api_set_error(err, kErrorTypeValidation, "Index value is too high"); + VALIDATE((lnum < MAXLNUM), "Index value is too high", { goto end; - } + }); if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to replace line"); @@ -720,10 +698,9 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In for (size_t i = to_replace; i < new_len; i++) { int64_t lnum = start_row + (int64_t)i - 1; - if (lnum >= MAXLNUM) { - api_set_error(err, kErrorTypeValidation, "Index value is too high"); + VALIDATE((lnum < MAXLNUM), "Index value is too high", { goto end; - } + }); if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to insert line"); @@ -800,10 +777,9 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer, { Array rv = ARRAY_DICT_INIT; - if (opts.size > 0) { - api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); + VALIDATE((opts.size == 0), "opts dict isn't empty", { return rv; - } + }); buf_T *buf = find_buffer_by_handle(buffer, err); @@ -820,18 +796,16 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer, start_row = normalize_index(buf, start_row, false, &oob); end_row = normalize_index(buf, end_row, false, &oob); - if (oob) { - api_set_error(err, kErrorTypeValidation, "Index out of bounds"); + VALIDATE((!oob), "Index out of bounds", { return rv; - } + }); // nvim_buf_get_lines doesn't care if the start row is greater than the end // row (it will just return an empty array), but nvim_buf_get_text does in // order to maintain symmetry with nvim_buf_set_text. - if (start_row > end_row) { - api_set_error(err, kErrorTypeValidation, "start is higher than end"); + VALIDATE((start_row <= end_row), "start is higher than end", { return rv; - } + }); bool replace_nl = (channel_id != VIML_INTERNAL_CALL); @@ -907,10 +881,9 @@ Integer nvim_buf_get_offset(Buffer buffer, Integer index, Error *err) return -1; } - if (index < 0 || index > buf->b_ml.ml_line_count) { - api_set_error(err, kErrorTypeValidation, "Index out of bounds"); + VALIDATE((index >= 0 && index <= buf->b_ml.ml_line_count), "Index out of bounds", { return 0; - } + }); return ml_find_line_or_offset(buf, (int)index + 1, NULL, true); } @@ -1118,8 +1091,9 @@ void nvim_buf_delete(Buffer buffer, Dictionary opts, Error *err) } else if (strequal("unload", k.data)) { unload = api_object_to_bool(v, "unload", false, err); } else { - api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); - return; + VALIDATE_S(false, "key", k.data, { + return; + }); } } @@ -1174,20 +1148,16 @@ Boolean nvim_buf_del_mark(Buffer buffer, String name, Error *err) return res; } - if (name.size != 1) { - api_set_error(err, kErrorTypeValidation, - "Mark name must be a single character"); + VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, { return res; - } + }); fmark_T *fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, *name.data); // fm is NULL when there's no mark with the given name - if (fm == NULL) { - api_set_error(err, kErrorTypeValidation, "Invalid mark name: '%c'", - *name.data); + VALIDATE_S((fm != NULL), "mark name", name.data, { return res; - } + }); // mark.lnum is 0 when the mark is not valid in the buffer, or is not set. if (fm->mark.lnum != 0 && fm->fnum == buf->handle) { @@ -1224,11 +1194,9 @@ Boolean nvim_buf_set_mark(Buffer buffer, String name, Integer line, Integer col, return res; } - if (name.size != 1) { - api_set_error(err, kErrorTypeValidation, - "Mark name must be a single character"); + VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, { return res; - } + }); res = set_mark(buf, name, line, col, err); @@ -1257,21 +1225,18 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) return rv; } - if (name.size != 1) { - api_set_error(err, kErrorTypeValidation, - "Mark name must be a single character"); + VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, { return rv; - } + }); fmark_T *fm; pos_T pos; char mark = *name.data; fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, mark); - if (fm == NULL) { - api_set_error(err, kErrorTypeValidation, "Invalid mark name"); + VALIDATE_S((fm != NULL), "mark name", name.data, { return rv; - } + }); // (0, 0) uppercase/file mark set in another buffer. if (fm->fnum != buf->handle) { pos.lnum = 0; diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index f09a00e5c2..0a95833256 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -11,6 +11,7 @@ #include "nvim/api/command.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/private/validate.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer_defs.h" @@ -99,10 +100,9 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) { Dictionary result = ARRAY_DICT_INIT; - if (opts.size > 0) { - api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); + VALIDATE((opts.size == 0), "opts dict isn't empty", { return result; - } + }); // Parse command line exarg_T ea; @@ -998,7 +998,7 @@ void nvim_buf_del_user_command(Buffer buffer, String name, Error *err) } } - api_set_error(err, kErrorTypeException, "No such user-defined command: %s", name.data); + api_set_error(err, kErrorTypeException, "Invalid command (not found): %s", name.data); } void create_user_command(String name, Object command, Dict(user_command) *opts, int flags, @@ -1014,20 +1014,17 @@ void create_user_command(String name, Object command, Dict(user_command) *opts, LuaRef compl_luaref = LUA_NOREF; LuaRef preview_luaref = LUA_NOREF; - if (!uc_validate_name(name.data)) { - api_set_error(err, kErrorTypeValidation, "Invalid command name"); + VALIDATE_S(uc_validate_name(name.data), "command name", name.data, { goto err; - } - - if (mb_islower(name.data[0])) { - api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter"); + }); + VALIDATE_S(!mb_islower(name.data[0]), "command name (must begin with an uppercase letter)", + name.data, { goto err; - } - - if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) { - api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive"); + }); + VALIDATE((!HAS_KEY(opts->range) || !HAS_KEY(opts->count)), + "Cannot use both 'range' and 'count'", { goto err; - } + }); if (opts->nargs.type == kObjectTypeInteger) { switch (opts->nargs.data.integer) { @@ -1038,14 +1035,14 @@ void create_user_command(String name, Object command, Dict(user_command) *opts, argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG; break; default: - api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); - goto err; + V |