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.
This commit is contained in:
Justin M. Keyes 2023-02-14 05:19:04 -05:00 committed by GitHub
parent 5396808267
commit 46a87a5d2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 592 additions and 634 deletions

View File

@ -12,6 +12,7 @@
#include "nvim/api/autocmd.h" #include "nvim/api/autocmd.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/private/validate.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/autocmd.h" #include "nvim/autocmd.h"
#include "nvim/buffer.h" #include "nvim/buffer.h"
@ -107,22 +108,21 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
break; break;
case kObjectTypeString: case kObjectTypeString:
group = augroup_find(opts->group.data.string.data); group = augroup_find(opts->group.data.string.data);
if (group < 0) { VALIDATE_S((group >= 0), "group", "", {
api_set_error(err, kErrorTypeValidation, "invalid augroup passed.");
goto cleanup; goto cleanup;
} });
break; break;
case kObjectTypeInteger: case kObjectTypeInteger:
group = (int)opts->group.data.integer; group = (int)opts->group.data.integer;
char *name = augroup_name(group); char *name = augroup_name(group);
if (!augroup_exists(name)) { VALIDATE_S(augroup_exists(name), "group", "", {
api_set_error(err, kErrorTypeValidation, "invalid augroup passed.");
goto cleanup; goto cleanup;
} });
break; break;
default: default:
api_set_error(err, kErrorTypeValidation, "group must be a string or an integer."); VALIDATE_S(false, "group (must be string or integer)", "", {
goto cleanup; goto cleanup;
});
} }
if (opts->event.type != kObjectTypeNil) { if (opts->event.type != kObjectTypeNil) {
@ -134,29 +134,24 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
event_set[event_nr] = true; event_set[event_nr] = true;
} else if (v.type == kObjectTypeArray) { } else if (v.type == kObjectTypeArray) {
FOREACH_ITEM(v.data.array, event_v, { FOREACH_ITEM(v.data.array, event_v, {
if (event_v.type != kObjectTypeString) { VALIDATE_T("event item", kObjectTypeString, event_v.type, {
api_set_error(err,
kErrorTypeValidation,
"Every event must be a string in 'event'");
goto cleanup; goto cleanup;
} });
GET_ONE_EVENT(event_nr, event_v, cleanup); GET_ONE_EVENT(event_nr, event_v, cleanup);
event_set[event_nr] = true; event_set[event_nr] = true;
}) })
} else { } else {
api_set_error(err, VALIDATE_S(false, "event (must be String or Array)", "", {
kErrorTypeValidation, goto cleanup;
"Not a valid 'event' value. Must be a string or an array"); });
goto cleanup;
} }
} }
if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { VALIDATE((opts->pattern.type == kObjectTypeNil || opts->buffer.type == kObjectTypeNil),
api_set_error(err, kErrorTypeValidation, "Cannot use both 'pattern' and 'buffer'", {
"Cannot use both 'pattern' and 'buffer'");
goto cleanup; goto cleanup;
} });
int pattern_filter_count = 0; int pattern_filter_count = 0;
if (opts->pattern.type != kObjectTypeNil) { if (opts->pattern.type != kObjectTypeNil) {
@ -166,25 +161,23 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
pattern_filter_count += 1; pattern_filter_count += 1;
} else if (v.type == kObjectTypeArray) { } else if (v.type == kObjectTypeArray) {
if (v.data.array.size > AUCMD_MAX_PATTERNS) { if (v.data.array.size > AUCMD_MAX_PATTERNS) {
api_set_error(err, kErrorTypeValidation, api_set_error(err, kErrorTypeValidation, "Too many patterns (maximum of %d)",
"Too many patterns. Please limit yourself to %d or fewer",
AUCMD_MAX_PATTERNS); AUCMD_MAX_PATTERNS);
goto cleanup; goto cleanup;
} }
FOREACH_ITEM(v.data.array, item, { FOREACH_ITEM(v.data.array, item, {
if (item.type != kObjectTypeString) { VALIDATE_T("pattern", kObjectTypeString, item.type, {
api_set_error(err, kErrorTypeValidation, "Invalid value for 'pattern': must be a string");
goto cleanup; goto cleanup;
} });
pattern_filters[pattern_filter_count] = item.data.string.data; pattern_filters[pattern_filter_count] = item.data.string.data;
pattern_filter_count += 1; pattern_filter_count += 1;
}); });
} else { } else {
api_set_error(err, kErrorTypeValidation, VALIDATE_EXP(false, "pattern", "String or Array", api_typename(v.type), {
"Not a valid 'pattern' value. Must be a string or an array"); goto cleanup;
goto cleanup; });
} }
} }
@ -198,17 +191,16 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
ADD(buffers, CSTR_TO_OBJ(pattern_buflocal)); ADD(buffers, CSTR_TO_OBJ(pattern_buflocal));
} else if (opts->buffer.type == kObjectTypeArray) { } else if (opts->buffer.type == kObjectTypeArray) {
if (opts->buffer.data.array.size > AUCMD_MAX_PATTERNS) { if (opts->buffer.data.array.size > AUCMD_MAX_PATTERNS) {
api_set_error(err, api_set_error(err, kErrorTypeValidation, "Too many buffers (maximum of %d)",
kErrorTypeValidation, AUCMD_MAX_PATTERNS);
"Too many buffers. Please limit yourself to %d or fewer", AUCMD_MAX_PATTERNS);
goto cleanup; goto cleanup;
} }
FOREACH_ITEM(opts->buffer.data.array, bufnr, { FOREACH_ITEM(opts->buffer.data.array, bufnr, {
if (bufnr.type != kObjectTypeInteger && bufnr.type != kObjectTypeBuffer) { VALIDATE_EXP((bufnr.type == kObjectTypeInteger || bufnr.type == kObjectTypeBuffer),
api_set_error(err, kErrorTypeValidation, "Invalid value for 'buffer': must be an integer"); "buffer", "Integer", api_typename(bufnr.type), {
goto cleanup; goto cleanup;
} });
buf_T *buf = find_buffer_by_handle((Buffer)bufnr.data.integer, err); buf_T *buf = find_buffer_by_handle((Buffer)bufnr.data.integer, err);
if (ERROR_SET(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)); ADD(buffers, CSTR_TO_OBJ(pattern_buflocal));
}); });
} else if (opts->buffer.type != kObjectTypeNil) { } else if (opts->buffer.type != kObjectTypeNil) {
api_set_error(err, kErrorTypeValidation, VALIDATE_EXP(false, "buffer", "Integer or Array", api_typename(opts->buffer.type), {
"Invalid value for 'buffer': must be an integer or array of integers"); goto cleanup;
goto cleanup; });
} }
FOREACH_ITEM(buffers, bufnr, { FOREACH_ITEM(buffers, bufnr, {
@ -432,30 +424,23 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
goto cleanup; goto cleanup;
} }
if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) { VALIDATE((opts->callback.type == kObjectTypeNil || opts->command.type == kObjectTypeNil),
api_set_error(err, kErrorTypeValidation, "specify either 'callback' or 'command', not both"); "Cannot use both 'callback' and 'command'", {
goto cleanup; 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 if (opts->callback.type != kObjectTypeNil) {
// not do that. // NOTE: We could accept callable tables, but that isn't common in the API.
Object *callback = &opts->callback; Object *callback = &opts->callback;
switch (callback->type) { switch (callback->type) {
case kObjectTypeLuaRef: case kObjectTypeLuaRef:
if (callback->data.luaref == LUA_NOREF) { VALIDATE_S((callback->data.luaref != LUA_NOREF), "callback", "<no value>", {
api_set_error(err,
kErrorTypeValidation,
"must pass an actual value");
goto cleanup; goto cleanup;
} });
VALIDATE_S(nlua_ref_is_function(callback->data.luaref), "callback", "<not a function>", {
if (!nlua_ref_is_function(callback->data.luaref)) {
api_set_error(err,
kErrorTypeValidation,
"must pass a function for callback");
goto cleanup; goto cleanup;
} });
cb.type = kCallbackLua; cb.type = kCallbackLua;
cb.data.luaref = api_new_luaref(callback->data.luaref); 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); cb.data.funcref = string_to_cstr(callback->data.string);
break; break;
default: default:
api_set_error(err, VALIDATE_EXP(false, "callback", "Lua function or Vim function name",
kErrorTypeException, api_typename(callback->type), {
"'callback' must be a lua function or name of vim function"); goto cleanup;
goto cleanup; });
} }
aucmd.type = CALLABLE_CB; aucmd.type = CALLABLE_CB;
aucmd.callable.cb = cb; aucmd.callable.cb = cb;
} else if (opts->command.type != kObjectTypeNil) { } else if (opts->command.type != kObjectTypeNil) {
Object *command = &opts->command; Object *command = &opts->command;
if (command->type == kObjectTypeString) { VALIDATE_T("command", kObjectTypeString, command->type, {
aucmd.type = CALLABLE_EX;
aucmd.callable.cmd = string_to_cstr(command->data.string);
} else {
api_set_error(err,
kErrorTypeValidation,
"'command' must be a string");
goto cleanup; goto cleanup;
} });
aucmd.type = CALLABLE_EX;
aucmd.callable.cmd = string_to_cstr(command->data.string);
} else { } else {
api_set_error(err, kErrorTypeValidation, "must pass one of: 'command', 'callback'"); VALIDATE_S(false, "'command' or 'callback' is required", "", {
goto cleanup; goto cleanup;
});
} }
bool is_once = api_object_to_bool(opts->once, "once", false, err); 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 != kObjectTypeNil) {
if (opts->desc.type == kObjectTypeString) { VALIDATE_T("desc", kObjectTypeString, opts->desc.type, {
desc = opts->desc.data.string.data;
} else {
api_set_error(err,
kErrorTypeValidation,
"'desc' must be a string");
goto cleanup; goto cleanup;
} });
desc = opts->desc.data.string.data;
} }
if (patterns.size == 0) { if (patterns.size == 0) {
ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("*"))); ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("*")));
} }
if (event_array.size == 0) { VALIDATE_R((event_array.size != 0), "event", {
api_set_error(err, kErrorTypeValidation, "'event' is a required key");
goto cleanup; goto cleanup;
} });
autocmd_id = next_autocmd_id++; autocmd_id = next_autocmd_id++;
FOREACH_ITEM(event_array, event_str, { FOREACH_ITEM(event_array, event_str, {
@ -564,10 +541,9 @@ cleanup:
void nvim_del_autocmd(Integer id, Error *err) void nvim_del_autocmd(Integer id, Error *err)
FUNC_API_SINCE(9) FUNC_API_SINCE(9)
{ {
if (id <= 0) { VALIDATE_INT((id > 0), "autocmd id", id, {
api_set_error(err, kErrorTypeException, "Invalid autocmd id");
return; return;
} });
if (!autocmd_delete_id(id)) { if (!autocmd_delete_id(id)) {
api_set_error(err, kErrorTypeException, "Failed to delete autocmd"); 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; goto cleanup;
} }
if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { VALIDATE((opts->pattern.type == kObjectTypeNil || opts->buffer.type == kObjectTypeNil),
api_set_error(err, kErrorTypeValidation, "Cannot use both 'pattern' and 'buffer'", {
"Cannot use both 'pattern' and 'buffer'");
goto cleanup; goto cleanup;
} });
int au_group = get_augroup_from_object(opts->group, err); int au_group = get_augroup_from_object(opts->group, err);
if (au_group == AUGROUP_ERROR) { if (au_group == AUGROUP_ERROR) {
@ -772,32 +747,29 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err)
break; break;
case kObjectTypeString: case kObjectTypeString:
au_group = augroup_find(opts->group.data.string.data); au_group = augroup_find(opts->group.data.string.data);
if (au_group == AUGROUP_ERROR) { VALIDATE_S((au_group != AUGROUP_ERROR), "group", opts->group.data.string.data, {
api_set_error(err,
kErrorTypeValidation,
"invalid augroup: %s", opts->group.data.string.data);
goto cleanup; goto cleanup;
} });
break; break;
case kObjectTypeInteger: case kObjectTypeInteger:
au_group = (int)opts->group.data.integer; au_group = (int)opts->group.data.integer;
char *name = augroup_name(au_group); char *name = augroup_name(au_group);
if (!augroup_exists(name)) { VALIDATE_INT(augroup_exists(name), "group", (int64_t)au_group, {
api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group);
goto cleanup; goto cleanup;
} });
break; break;
default: default:
api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); VALIDATE_EXP(false, "group", "String or Integer", api_typename(opts->group.type), {
goto cleanup; goto cleanup;
});
} }
if (opts->buffer.type != kObjectTypeNil) { if (opts->buffer.type != kObjectTypeNil) {
Object buf_obj = opts->buffer; Object buf_obj = opts->buffer;
if (buf_obj.type != kObjectTypeInteger && buf_obj.type != kObjectTypeBuffer) { VALIDATE_EXP((buf_obj.type == kObjectTypeInteger || buf_obj.type == kObjectTypeBuffer),
api_set_error(err, kErrorTypeException, "invalid buffer: %d", buf_obj.type); "buffer", "Integer", api_typename(buf_obj.type), {
goto cleanup; goto cleanup;
} });
buf = find_buffer_by_handle((Buffer)buf_obj.data.integer, err); 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, { FOREACH_ITEM(arr, entry, {
if (entry.type != kObjectTypeString) { if (entry.type != kObjectTypeString) {
api_set_error(err, api_set_error(err, kErrorTypeValidation, "Invalid '%s' item type: expected String, got %s",
kErrorTypeValidation, k, api_typename(entry.type));
"All entries in '%s' must be strings",
k);
return false; return false;
} }
// Disallow newlines in the middle of the line. // Disallow newlines in the middle of the line.
const String l = entry.data.string; const String l = entry.data.string;
if (memchr(l.data, NL, l.size)) { if (memchr(l.data, NL, l.size)) {
api_set_error(err, kErrorTypeValidation, api_set_error(err, kErrorTypeValidation, "'%s' item cannot contain newlines", k);
"String cannot contain newlines");
return false; 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); *array = copy_array(v->data.array, NULL);
} else { } else {
if (required) { if (required) {
api_set_error(err, api_set_error(err, kErrorTypeValidation,
kErrorTypeValidation, "Invalid '%s' type: expected Array or String, got %s",
"'%s' must be an array or a string.", k, api_typename(v->type));
k);
return false; return false;
} }
} }
@ -894,27 +862,22 @@ static int get_augroup_from_object(Object group, Error *err)
return AUGROUP_DEFAULT; return AUGROUP_DEFAULT;
case kObjectTypeString: case kObjectTypeString:
au_group = augroup_find(group.data.string.data); au_group = augroup_find(group.data.string.data);
if (au_group == AUGROUP_ERROR) { VALIDATE_S((au_group != AUGROUP_ERROR), "group", group.data.string.data, {
api_set_error(err,
kErrorTypeValidation,
"invalid augroup: %s", group.data.string.data);
return AUGROUP_ERROR; return AUGROUP_ERROR;
} });
return au_group; return au_group;
case kObjectTypeInteger: case kObjectTypeInteger:
au_group = (int)group.data.integer; au_group = (int)group.data.integer;
char *name = augroup_name(au_group); char *name = augroup_name(au_group);
if (!augroup_exists(name)) { VALIDATE_INT(augroup_exists(name), "group", (int64_t)au_group, {
api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group);
return AUGROUP_ERROR; return AUGROUP_ERROR;
} });
return au_group; return au_group;
default: default:
api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); VALIDATE_EXP(false, "group", "String or Integer", api_typename(group.type), {
return AUGROUP_ERROR; 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]; const char pattern_buflocal[BUFLOCAL_PAT_LEN];
if (pattern.type != kObjectTypeNil && buffer.type != kObjectTypeNil) { VALIDATE((pattern.type == kObjectTypeNil || buffer.type == kObjectTypeNil),
api_set_error(err, kErrorTypeValidation, "Cannot use both 'pattern' and 'buffer' for the same autocmd", {
"cannot pass both: 'pattern' and 'buffer' for the same autocmd");
return false; return false;
} else if (pattern.type != kObjectTypeNil) { });
if (pattern.type != kObjectTypeNil) {
Object *v = &pattern; Object *v = &pattern;
if (v->type == kObjectTypeString) { if (v->type == kObjectTypeString) {
@ -956,18 +920,15 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob
} }
}) })
} else { } else {
api_set_error(err, VALIDATE_EXP(false, "pattern", "String or Table", api_typename(v->type), {
kErrorTypeValidation, return false;
"'pattern' must be a string or table"); });
return false;
} }
} else if (buffer.type != kObjectTypeNil) { } else if (buffer.type != kObjectTypeNil) {
if (buffer.type != kObjectTypeInteger && buffer.type != kObjectTypeBuffer) { VALIDATE_EXP((buffer.type == kObjectTypeInteger || buffer.type == kObjectTypeBuffer),
api_set_error(err, "buffer", "Integer", api_typename(buffer.type), {
kErrorTypeValidation,
"'buffer' must be an integer");
return false; return false;
} });
buf_T *buf = find_buffer_by_handle((Buffer)buffer.data.integer, err); buf_T *buf = find_buffer_by_handle((Buffer)buffer.data.integer, err);
if (ERROR_SET(err)) { if (ERROR_SET(err)) {

View File

@ -16,6 +16,7 @@
#include "nvim/api/buffer.h" #include "nvim/api/buffer.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/private/validate.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/autocmd.h" #include "nvim/autocmd.h"
#include "nvim/buffer.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) { if (is_lua) {
for (size_t j = 0; cbs[j].name; j++) { for (size_t j = 0; cbs[j].name; j++) {
if (strequal(cbs[j].name, k.data)) { if (strequal(cbs[j].name, k.data)) {
if (v->type != kObjectTypeLuaRef) { VALIDATE_T(cbs[j].name, kObjectTypeLuaRef, v->type, {
api_set_error(err, kErrorTypeValidation,
"%s is not a function", cbs[j].name);
goto error; goto error;
} });
*(cbs[j].dest) = v->data.luaref; *(cbs[j].dest) = v->data.luaref;
v->data.luaref = LUA_NOREF; v->data.luaref = LUA_NOREF;
key_used = true; key_used = true;
@ -194,26 +193,23 @@ Boolean nvim_buf_attach(uint64_t channel_id, Buffer buffer, Boolean send_buffer,
if (key_used) { if (key_used) {
continue; continue;
} else if (strequal("utf_sizes", k.data)) { } else if (strequal("utf_sizes", k.data)) {
if (v->type != kObjectTypeBoolean) { VALIDATE_T("utf_sizes", kObjectTypeBoolean, v->type, {
api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean");
goto error; goto error;
} });
cb.utf_sizes = v->data.boolean; cb.utf_sizes = v->data.boolean;
key_used = true; key_used = true;
} else if (strequal("preview", k.data)) { } else if (strequal("preview", k.data)) {
if (v->type != kObjectTypeBoolean) { VALIDATE_T("preview", kObjectTypeBoolean, v->type, {
api_set_error(err, kErrorTypeValidation, "preview must be boolean");
goto error; goto error;
} });
cb.preview = v->data.boolean; cb.preview = v->data.boolean;
key_used = true; key_used = true;
} }
} }
if (!key_used) { VALIDATE_S(key_used, "key", k.data, {
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
goto error; goto error;
} });
} }
return buf_updates_register(buf, channel_id, cb, send_buffer); 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); start = normalize_index(buf, start, true, &oob);
end = normalize_index(buf, end, true, &oob); end = normalize_index(buf, end, true, &oob);
if (strict_indexing && oob) { VALIDATE((!strict_indexing || !oob), "Index out of bounds", {
api_set_error(err, kErrorTypeValidation, "Index out of bounds");
return rv; return rv;
} });
if (start >= end) { if (start >= end) {
// Return 0-length array // Return 0-length array
@ -328,20 +323,15 @@ end:
static bool check_string_array(Array arr, bool disallow_nl, Error *err) static bool check_string_array(Array arr, bool disallow_nl, Error *err)
{ {
for (size_t i = 0; i < arr.size; i++) { for (size_t i = 0; i < arr.size; i++) {
if (arr.items[i].type != kObjectTypeString) { VALIDATE_T("replacement item", kObjectTypeString, arr.items[i].type, {
api_set_error(err,
kErrorTypeValidation,
"All items in the replacement array must be strings");
return false; return false;
} });
// Disallow newlines in the middle of the line. // Disallow newlines in the middle of the line.
if (disallow_nl) { if (disallow_nl) {
const String l = arr.items[i].data.string; const String l = arr.items[i].data.string;
if (memchr(l.data, NL, l.size)) { VALIDATE(!memchr(l.data, NL, l.size), "String cannot contain newlines", {
api_set_error(err, kErrorTypeValidation,
"String cannot contain newlines");
return false; return false;
} });
} }
} }
return true; 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); start = normalize_index(buf, start, true, &oob);
end = normalize_index(buf, end, true, &oob); end = normalize_index(buf, end, true, &oob);
if (strict_indexing && oob) { VALIDATE((!strict_indexing || !oob), "Index out of bounds", {
api_set_error(err, kErrorTypeValidation, "Index out of bounds");
return; return;
} });
VALIDATE((start <= end), "\"start\" is higher than \"end\"", {
if (start > end) {
api_set_error(err,
kErrorTypeValidation,
"Argument \"start\" is higher than \"end\"");
return; return;
} });
bool disallow_nl = (channel_id != VIML_INTERNAL_CALL); bool disallow_nl = (channel_id != VIML_INTERNAL_CALL);
if (!check_string_array(replacement, disallow_nl, err)) { 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++) { for (size_t i = 0; i < to_replace; i++) {
int64_t lnum = start + (int64_t)i; int64_t lnum = start + (int64_t)i;
if (lnum >= MAXLNUM) { VALIDATE(lnum < MAXLNUM, "Index value is too high", {
api_set_error(err, kErrorTypeValidation, "Index value is too high");
goto end; goto end;
} });
if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) { if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to replace line"); 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++) { for (size_t i = to_replace; i < new_len; i++) {
int64_t lnum = start + (int64_t)i - 1; int64_t lnum = start + (int64_t)i - 1;
if (lnum >= MAXLNUM) { VALIDATE(lnum < MAXLNUM, "Index value is too high", {
api_set_error(err, kErrorTypeValidation, "Index value is too high");
goto end; goto end;
} });
if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) { if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to insert line"); 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! // check range is ordered and everything!
// start_row, end_row within buffer len (except add text past the end?) // start_row, end_row within buffer len (except add text past the end?)
start_row = normalize_index(buf, start_row, false, &oob); start_row = normalize_index(buf, start_row, false, &oob);
if (oob) { VALIDATE((!oob), "start_row out of bounds", {
api_set_error(err, kErrorTypeValidation, "start_row out of bounds");
return; return;
} });
end_row = normalize_index(buf, end_row, false, &oob); end_row = normalize_index(buf, end_row, false, &oob);
if (oob) { VALIDATE((!oob), "end_row out of bounds", {
api_set_error(err, kErrorTypeValidation, "end_row out of bounds");
return; return;
} });
char *str_at_start = NULL; char *str_at_start = NULL;
char *str_at_end = 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. // 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)); str_at_start = xstrdup(ml_get_buf(buf, (linenr_T)start_row, false));
size_t len_at_start = strlen(str_at_start); size_t len_at_start = strlen(str_at_start);
if (start_col < 0 || (size_t)start_col > len_at_start) { VALIDATE((start_col >= 0 && (size_t)start_col <= len_at_start), "start_col out of bounds", {
api_set_error(err, kErrorTypeValidation, "start_col out of bounds");
goto early_end; goto early_end;
} });
// Another call to ml_get_buf() may free the line, so make a copy. // 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)); str_at_end = xstrdup(ml_get_buf(buf, (linenr_T)end_row, false));
size_t len_at_end = strlen(str_at_end); size_t len_at_end = strlen(str_at_end);
if (end_col < 0 || (size_t)end_col > len_at_end) { VALIDATE((end_col >= 0 && (size_t)end_col <= len_at_end), "end_col out of bounds", {
api_set_error(err, kErrorTypeValidation, "end_col out of bounds");
goto early_end; goto early_end;
} });
if (start_row > end_row || (end_row == start_row && start_col > end_col)) { VALIDATE((start_row <= end_row && !(end_row == start_row && start_col > end_col)),
api_set_error(err, kErrorTypeValidation, "start is higher than end"); "start is higher than end", {
goto early_end; goto early_end;
} });
bool disallow_nl = (channel_id != VIML_INTERNAL_CALL); bool disallow_nl = (channel_id != VIML_INTERNAL_CALL);
if (!check_string_array(replacement, disallow_nl, err)) { 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++) { for (size_t i = 0; i < to_replace; i++) {
int64_t lnum = start_row + (int64_t)i; int64_t lnum = start_row + (int64_t)i;
if (lnum >= MAXLNUM) { VALIDATE((lnum < MAXLNUM), "Index value is too high", {
api_set_error(err, kErrorTypeValidation, "Index value is too high");
goto end; goto end;
} });
if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) { if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to replace line"); 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++) { for (size_t i = to_replace; i < new_len; i++) {
int64_t lnum = start_row + (int64_t)i - 1; int64_t lnum = start_row + (int64_t)i - 1;
if (lnum >= MAXLNUM) { VALIDATE((lnum < MAXLNUM), "Index value is too high", {
api_set_error(err, kErrorTypeValidation, "Index value is too high");
goto end; goto end;
} });
if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) { if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to insert line"); 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; Array rv = ARRAY_DICT_INIT;
if (opts.size > 0) { VALIDATE((opts.size == 0), "opts dict isn't empty", {
api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
return rv; return rv;
} });
buf_T *buf = find_buffer_by_handle(buffer, err); 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); start_row = normalize_index(buf, start_row, false, &oob);
end_row = normalize_index(buf, end_row, false, &oob); end_row = normalize_index(buf, end_row, false, &oob);
if (oob) { VALIDATE((!oob), "Index out of bounds", {
api_set_error(err, kErrorTypeValidation, "Index out of bounds");
return rv; return rv;
} });
// nvim_buf_get_lines doesn't care if the start row is greater than the end // 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 // row (it will just return an empty array), but nvim_buf_get_text does in
// order to maintain symmetry with nvim_buf_set_text. // order to maintain symmetry with nvim_buf_set_text.
if (start_row > end_row) { VALIDATE((start_row <= end_row), "start is higher than end", {
api_set_error(err, kErrorTypeValidation, "start is higher than end");
return rv; return rv;
} });
bool replace_nl = (channel_id != VIML_INTERNAL_CALL); 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; return -1;
} }
if (index < 0 || index > buf->b_ml.ml_line_count) { VALIDATE((index >= 0 && index <= buf->b_ml.ml_line_count), "Index out of bounds", {
api_set_error(err, kErrorTypeValidation, "Index out of bounds");
return 0; return 0;
} });
return ml_find_line_or_offset(buf, (int)index + 1, NULL, true); 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)) { } else if (strequal("unload", k.data)) {
unload = api_object_to_bool(v, "unload", false, err); unload = api_object_to_bool(v, "unload", false, err);
} else { } else {
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); VALIDATE_S(false, "key", k.data, {
return; return;
});
} }
} }
@ -1174,20 +1148,16 @@ Boolean nvim_buf_del_mark(Buffer buffer, String name, Error *err)
return res; return res;
} }
if (name.size != 1) { VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
api_set_error(err, kErrorTypeValidation,
"Mark name must be a single character");
return res; return res;
} });
fmark_T *fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, *name.data); fmark_T *fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, *name.data);
// fm is NULL when there's no mark with the given name // fm is NULL when there's no mark with the given name
if (fm == NULL) { VALIDATE_S((fm != NULL), "mark name", name.data, {
api_set_error(err, kErrorTypeValidation, "Invalid mark name: '%c'",
*name.data);
return res; return res;
} });
// mark.lnum is 0 when the mark is not valid in the buffer, or is not set. // 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) { 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; return res;
} }
if (name.size != 1) { VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
api_set_error(err, kErrorTypeValidation,
"Mark name must be a single character");
return res; return res;
} });
res = set_mark(buf, name, line, col, err); 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; return rv;
} }
if (name.size != 1) { VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
api_set_error(err, kErrorTypeValidation,
"Mark name must be a single character");
return rv; return rv;
} });
fmark_T *fm; fmark_T *fm;
pos_T pos; pos_T pos;
char mark = *name.data; char mark = *name.data;
fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, mark); fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, mark);
if (fm == NULL) { VALIDATE_S((fm != NULL), "mark name", name.data, {
api_set_error(err, kErrorTypeValidation, "Invalid mark name");
return rv; return rv;
} });
// (0, 0) uppercase/file mark set in another buffer. // (0, 0) uppercase/file mark set in another buffer.
if (fm->fnum != buf->handle) { if (fm->fnum != buf->handle) {
pos.lnum = 0; pos.lnum = 0;

View File

@ -11,6 +11,7 @@
#include "nvim/api/command.h" #include "nvim/api/command.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/private/validate.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/autocmd.h" #include "nvim/autocmd.h"
#include "nvim/buffer_defs.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; Dictionary result = ARRAY_DICT_INIT;
if (opts.size > 0) { VALIDATE((opts.size == 0), "opts dict isn't empty", {
api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
return result; return result;
} });
// Parse command line // Parse command line
exarg_T ea; 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, 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 compl_luaref = LUA_NOREF;
LuaRef preview_luaref = LUA_NOREF; LuaRef preview_luaref = LUA_NOREF;
if (!uc_validate_name(name.data)) { VALIDATE_S(uc_validate_name(name.data), "command name", name.data, {
api_set_error(err, kErrorTypeValidation, "Invalid command name");
goto err; goto err;
} });
VALIDATE_S(!mb_islower(name.data[0]), "command name (must begin with an uppercase letter)",
if (mb_islower(name.data[0])) { name.data, {
api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter");
goto err; goto err;
} });
VALIDATE((!HAS_KEY(opts->range) || !HAS_KEY(opts->count)),
if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) { "Cannot use both 'range' and 'count'", {
api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive");
goto err; goto err;
} });
if (opts->nargs.type == kObjectTypeInteger) { if (opts->nargs.type == kObjectTypeInteger) {
switch (opts->nargs.data.integer) { 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; argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG;
break; break;
default: default:
api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); VALIDATE_INT(false, "nargs", (int64_t)opts->nargs.data.integer, {
goto err; goto err;
});
} }
} else if (opts->nargs.type == kObjectTypeString) { } else if (opts->nargs.type == kObjectTypeString) {
if (opts->nargs.data.string.size > 1) { VALIDATE_S((opts->nargs.data.string.size <= 1), "nargs", opts->nargs.data.string.data, {
api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
goto err; goto err;
} });
switch (opts->nargs.data.string.data[0]) { switch (opts->nargs.data.string.data[0]) {
case '*': case '*':
@ -1058,18 +1055,19 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
argt |= EX_EXTRA | EX_NEEDARG; argt |= EX_EXTRA | EX_NEEDARG;
break; break;
default: default:
api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); VALIDATE_S(false, "nargs", opts->nargs.data.string.data, {
goto err; goto err;
});
} }
} else if (HAS_KEY(opts->nargs)) { } else if (HAS_KEY(opts->nargs)) {
api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); VALIDATE_S(false, "nargs", "", {
goto err; goto err;
});
} }
if (HAS_KEY(opts->complete) && !argt) { VALIDATE((!HAS_KEY(opts->complete) || argt), "'complete' used without 'nargs'", {
api_set_error(err, kErrorTypeValidation, "'complete' used without 'nargs'");
goto err; goto err;
} });
if (opts->range.type == kObjectTypeBoolean) { if (opts->range.type == kObjectTypeBoolean) {
if (opts->range.data.boolean) { if (opts->range.data.boolean) {
@ -1077,13 +1075,12 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
addr_type_arg = ADDR_LINES; addr_type_arg = ADDR_LINES;
} }
} else if (opts->range.type == kObjectTypeString) { } else if (opts->range.type == kObjectTypeString) {
if (opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1) { VALIDATE_S((opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1),
argt |= EX_RANGE | EX_DFLALL; "range", "", {
addr_type_arg = ADDR_LINES;
} else {
api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
goto err; goto err;
} });
argt |= EX_RANGE | EX_DFLALL;
addr_type_arg = ADDR_LINES;
} else if (opts->range.type == kObjectTypeInteger) { } else if (opts->range.type == kObjectTypeInteger) {
argt |= EX_RANGE | EX_ZEROR; argt |= EX_RANGE | EX_ZEROR;
def = opts->range.data.integer; def = opts->range.data.integer;

View File

@ -11,6 +11,7 @@
#include "nvim/api/extmark.h" #include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/private/validate.h"
#include "nvim/buffer_defs.h" #include "nvim/buffer_defs.h"
#include "nvim/charset.h" #include "nvim/charset.h"
#include "nvim/decoration.h" #include "nvim/decoration.h"
@ -218,10 +219,9 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
return rv; return rv;
} }
if (!ns_initialized((uint32_t)ns_id)) { VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return rv; return rv;
} });
bool details = false; bool details = false;
for (size_t i = 0; i < opts.size; i++) { for (size_t i = 0; i < opts.size; i++) {
@ -233,12 +233,14 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
} else if (v->type == kObjectTypeInteger) { } else if (v->type == kObjectTypeInteger) {
details = v->data.integer; details = v->data.integer;
} else { } else {
api_set_error(err, kErrorTypeValidation, "details is not an boolean"); VALIDATE_EXP(false, "details", "Boolean or Integer", api_typename(v->type), {
return rv; return rv;
});
} }
} else { } else {
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); VALIDATE_S(false, "key", k.data, {
return rv; return rv;
});
} }
} }
@ -301,10 +303,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
return rv; return rv;
} }
if (!ns_initialized((uint32_t)ns_id)) { VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return rv; return rv;
} });
Integer limit = -1; Integer limit = -1;
bool details = false; bool details = false;
@ -313,10 +314,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
String k = opts.items[i].key; String k = opts.items[i].key;
Object *v = &opts.items[i].value; Object *v = &opts.items[i].value;
if (strequal("limit", k.data)) { if (strequal("limit", k.data)) {
if (v->type != kObjectTypeInteger) { VALIDATE_T("limit", kObjectTypeInteger, v->type, {
api_set_error(err, kErrorTypeValidation, "limit is not an integer");
return rv; return rv;
} });
limit = v->data.integer; limit = v->data.integer;
} else if (strequal("details", k.data)) { } else if (strequal("details", k.data)) {
if (v->type == kObjectTypeBoolean) { if (v->type == kObjectTypeBoolean) {
@ -324,12 +324,14 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
} else if (v->type == kObjectTypeInteger) { } else if (v->type == kObjectTypeInteger) {
details = v->data.integer; details = v->data.integer;
} else { } else {
api_set_error(err, kErrorTypeValidation, "details is not an boolean"); VALIDATE_EXP(false, "details", "Boolean or Integer", api_typename(v->type), {
return rv; return rv;
});
} }
} else { } else {
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); VALIDATE_S(false, "key", k.data, {
return rv; return rv;
});
} }
} }
@ -501,27 +503,26 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
goto error; goto error;
} }
if (!ns_initialized((uint32_t)ns_id)) { VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
goto error; goto error;
} });
uint32_t id = 0; uint32_t id = 0;
if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) { if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) {
id = (uint32_t)opts->id.data.integer; id = (uint32_t)opts->id.data.integer;
} else if (HAS_KEY(opts->id)) { } else if (HAS_KEY(opts->id)) {
api_set_error(err, kErrorTypeValidation, "id is not a positive integer"); VALIDATE_S(false, "id (must be positive integer)", "", {
goto error; goto error;
});
} }
int line2 = -1; int line2 = -1;
// For backward compatibility we support "end_line" as an alias for "end_row" // For backward compatibility we support "end_line" as an alias for "end_row"
if (HAS_KEY(opts->end_line)) { if (HAS_KEY(opts->end_line)) {
if (HAS_KEY(opts->end_row)) { VALIDATE(!HAS_KEY(opts->end_row), "cannot use both end_row and end_line", {
api_set_error(err, kErrorTypeValidation, "cannot use both end_row and end_line");
goto error; goto error;
} });
opts->end_row = opts->end_line; opts->end_row = opts->end_line;
} }
@ -536,29 +537,28 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
if (opts->end_row.type == kObjectTypeInteger) { if (opts->end_row.type == kObjectTypeInteger) {
Integer val = opts->end_row.data.integer; Integer val = opts->end_row.data.integer;
if (val < 0 || (val > buf->b_ml.ml_line_count && strict)) { VALIDATE((val >= 0 && !(val > buf->b_ml.ml_line_count && strict)),
api_set_error(err, kErrorTypeValidation, "end_row value outside range"); "end_row value outside range", {
goto error; goto error;
} else { });
line2 = (int)val; line2 = (int)val;
}
} else if (HAS_KEY(opts->end_row)) { } else if (HAS_KEY(opts->end_row)) {
api_set_error(err, kErrorTypeValidation, "end_row is not an integer"); VALIDATE_T("end_row", kObjectTypeInteger, opts->end_row.type, {
goto error; goto error;
});
} }
colnr_T col2 = -1; colnr_T col2 = -1;
if (opts->end_col.type == kObjectTypeInteger) { if (opts->end_col.type == kObjectTypeInteger) {
Integer val = opts->end_col.data.integer; Integer val = opts->end_col.data.integer;
if (val < 0 || val > MAXCOL) { VALIDATE((val >= 0 && val <= MAXCOL), "end_col value outside range", {
api_set_error(err, kErrorTypeValidation, "end_col value outside range");
goto error; goto error;
} else { });
col2 = (int)val; col2 = (int)val;
}
} else if (HAS_KEY(opts->end_col)) { } else if (HAS_KEY(opts->end_col)) {
api_set_error(err, kErrorTypeValidation, "end_col is not an integer"); VALIDATE_T("end_col", kObjectTypeInteger, opts->end_col.type, {
goto error; goto error;
});
} }
// uncrustify:off // uncrustify:off
@ -596,8 +596,9 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
} }
has_decor = true; has_decor = true;
} else if (HAS_KEY(opts->conceal)) { } else if (HAS_KEY(opts->conceal)) {
api_set_error(err, kErrorTypeValidation, "conceal is not a String"); VALIDATE_T("conceal", kObjectTypeString, opts->conceal.type, {
goto error; goto error;
});
} }
if (opts->virt_text.type == kObjectTypeArray) { if (opts->virt_text.type == kObjectTypeArray) {
@ -608,8 +609,9 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
goto error; goto error;
} }
} else if (HAS_KEY(opts->virt_text)) { } else if (HAS_KEY(opts->virt_text)) {
api_set_error(err, kErrorTypeValidation, "virt_text is not an Array"); VALIDATE_T("virt_text", kObjectTypeArray, opts->virt_text.type, {
goto error; goto error;
});
} }
if (opts->virt_text_pos.type == kObjectTypeString) { if (opts->virt_text_pos.type == kObjectTypeString) {
@ -621,21 +623,23 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
} else if (strequal("right_align", str.data)) { } else if (strequal("right_align", str.data)) {
decor.virt_text_pos = kVTRightAlign; decor.virt_text_pos = kVTRightAlign;
} else { } else {
api_set_error(err, kErrorTypeValidation, "virt_text_pos: invalid value"); VALIDATE_S(false, "virt_text_pos", "", {
goto error; goto error;
});
} }
} else if (HAS_KEY(opts->virt_text_pos)) { } else if (HAS_KEY(opts->virt_text_pos)) {
api_set_error(err, kErrorTypeValidation, "virt_text_pos is not a String"); VALIDATE_T("virt_text_pos", kObjectTypeString, opts->virt_text_pos.type, {
goto error; goto error;
});
} }
if (opts->virt_text_win_col.type == kObjectTypeInteger) { if (opts->virt_text_win_col.type == kObjectTypeInteger) {
decor.col = (int)opts->virt_text_win_col.data.integer; decor.col = (int)opts->virt_text_win_col.data.integer;
decor.virt_text_pos = kVTWinCol; decor.virt_text_pos = kVTWinCol;
} else if (HAS_KEY(opts->virt_text_win_col)) { } else if (HAS_KEY(opts->virt_text_win_col)) {
api_set_error(err, kErrorTypeValidation, VALIDATE_T("virt_text_win_col", kObjectTypeInteger, opts->virt_text_win_col.type, {
"virt_text_win_col is not a Number of the correct size"); goto error;
goto error; });
} }
OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false); OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false);
@ -650,13 +654,14 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
} else if (strequal("blend", str.data)) { } else if (strequal("blend", str.data)) {
decor.hl_mode = kHlModeBlend; decor.hl_mode = kHlModeBlend;
} else { } else {
api_set_error(err, kErrorTypeValidation, VALIDATE_S(false, "virt_text_pos", "", {
"virt_text_pos: invalid value"); goto error;
goto error; });
} }
} else if (HAS_KEY(opts->hl_mode)) { } else if (HAS_KEY(opts->hl_mode)) {
api_set_error(err, kErrorTypeValidation, "hl_mode is not a String"); VALIDATE_T("hl_mode", kObjectTypeString, opts->hl_mode.type, {
goto error; goto error;
});
} }
bool virt_lines_leftcol = false; bool virt_lines_leftcol = false;
@ -665,10 +670,9 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
if (opts->virt_lines.type == kObjectTypeArray) { if (opts->virt_lines.type == kObjectTypeArray) {
Array a = opts->virt_lines.data.array; Array a = opts->virt_lines.data.array;
for (size_t j = 0; j < a.size; j++) { for (size_t j = 0; j < a.size; j++) {
if (a.items[j].type != kObjectTypeArray) { VALIDATE_T("virt_text_line", kObjectTypeArray, a.items[j].type, {
api_set_error(err, kErrorTypeValidation, "virt_text_line item is not an Array");
goto error; goto error;
} });
int dummig; int dummig;
VirtText jtem = parse_virt_text(a.items[j].data.array, err, &dummig); VirtText jtem = parse_virt_text(a.items[j].data.array, err, &dummig);
kv_push(decor.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol })); kv_push(decor.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol }));
@ -678,8 +682,9 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
has_decor = true; has_decor = true;
} }
} else if (HAS_KEY(opts->virt_lines)) { } else if (HAS_KEY(opts->virt_lines)) {
api_set_error(err, kErrorTypeValidation, "virt_lines is not an Array"); VALIDATE_T("virt_lines", kObjectTypeArray, opts->virt_lines.type, {
goto error; goto error;
});
} }
OPTION_TO_BOOL(decor.virt_lines_above, virt_lines_above, false); OPTION_TO_BOOL(decor.virt_lines_above, virt_lines_above, false);
@ -687,26 +692,26 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
if (opts->priority.type == kObjectTypeInteger) { if (opts->priority.type == kObjectTypeInteger) {
Integer val = opts->priority.data.integer; Integer val = opts->priority.data.integer;
if (val < 0 || val > UINT16_MAX) { VALIDATE_S((val >= 0 && val <= UINT16_MAX), "priority", "(out of range)", {
api_set_error(err, kErrorTypeValidation, "priority is not a valid value");
goto error; goto error;
} });
decor.priority = (DecorPriority)val; decor.priority = (DecorPriority)val;
} else if (HAS_KEY(opts->priority)) { } else if (HAS_KEY(opts->priority)) {
api_set_error(err, kErrorTypeValidation, "priority is not a Number of the correct size"); VALIDATE_T("priority", kObjectTypeInteger, opts->priority.type, {
goto error; goto error;
});
} }
if (opts->sign_text.type == kObjectTypeString) { if (opts->sign_text.type == kObjectTypeString) {
if (!init_sign_text(&decor.sign_text, VALIDATE_S(init_sign_text(&decor.sign_text, opts->sign_text.data.string.data),
opts->sign_text.data.string.data)) { "sign_text", "", {
api_set_error(err, kErrorTypeValidation, "sign_text is not a valid value");
goto error; goto error;
} });
has_decor = true; has_decor = true;
} else if (HAS_KEY(opts->sign_text)) { } else if (HAS_KEY(opts->sign_text)) {
api_set_error(err, kErrorTypeValidation, "sign_text is not a String"); VALIDATE_T("sign_text", kObjectTypeString, opts->sign_text.type, {
goto error; goto error;
});
} }
bool right_gravity = true; bool right_gravity = true;
@ -714,11 +719,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
// Only error out if they try to set end_right_gravity without // Only error out if they try to set end_right_gravity without
// setting end_col or end_row // setting end_col or end_row
if (line2 == -1 && col2 == -1 && HAS_KEY(opts->end_right_gravity)) { VALIDATE(!(line2 == -1 && col2 == -1 && HAS_KEY(opts->end_right_gravity)),
api_set_error(err, kErrorTypeValidation, "cannot set end_right_gravity without setting end_row or end_col", {
"cannot set end_right_gravity without setting end_row or end_col");
goto error; goto error;
} });
bool end_right_gravity = false; bool end_right_gravity = false;
OPTION_TO_BOOL(end_right_gravity, end_right_gravity, false); OPTION_TO_BOOL(end_right_gravity, end_right_gravity, false);
@ -742,16 +746,15 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
has_decor = true; has_decor = true;
} }
if (line < 0) { VALIDATE_S((line >= 0), "line", "(out of range)", {
api_set_error(err, kErrorTypeValidation, "line value outside range");
goto error; goto error;
} else if (line > buf->b_ml.ml_line_count) { });
if (strict) {
api_set_error(err, kErrorTypeValidation, "line value outside range"); if (line > buf->b_ml.ml_line_count) {
VALIDATE_S(!strict, "line", "(out of range)", {
goto error; goto error;
} else { });
line = buf->b_ml.ml_line_count; line = buf->b_ml.ml_line_count;
}
} else if (line < buf->b_ml.ml_line_count) { } else if (line < buf->b_ml.ml_line_count) {
len = ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line + 1, false)); len = ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line + 1, false));
} }
@ -759,15 +762,14 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
if (col == -1) { if (col == -1) {
col = (Integer)len; col = (Integer)len;
} else if (col > (Integer)len) { } else if (col > (Integer)len) {
if (strict) { VALIDATE_S(!strict, "col", "(out of range)", {
api_set_error(err, kErrorTypeValidation, "col value outside range");
goto error; goto error;
} else { });
col = (Integer)len; col = (Integer)len;
}
} else if (col < -1) { } else if (col < -1) {
api_set_error(err, kErrorTypeValidation, "col value outside range"); VALIDATE_S(false, "col", "(out of range)", {
goto error; goto error;
});
} }
if (col2 >= 0) { if (col2 >= 0) {
@ -781,12 +783,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
line2 = (int)line; line2 = (int)line;
} }
if (col2 > (Integer)len) { if (col2 > (Integer)len) {
if (strict) { VALIDATE_S(!strict, "end_col", "(out of range)", {
api_set_error(err, kErrorTypeValidation, "end_col value outside range");
goto error; goto error;
} else { });
col2 = (int)len; col2 = (int)len;
}
} }
} else if (line2 >= 0) { } else if (line2 >= 0) {
col2 = 0; col2 = 0;
@ -829,10 +829,9 @@ Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *er
if (!buf) { if (!buf) {
return false; return false;
} }
if (!ns_initialized((uint32_t)ns_id)) { VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return false; return false;
} });
return extmark_del(buf, (uint32_t)ns_id, (uint32_t)id); return extmark_del(buf, (uint32_t)ns_id, (uint32_t)id);
} }
@ -887,14 +886,13 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In
return 0; return 0;
} }
if (line < 0 || line >= MAXLNUM) { VALIDATE_S((line >= 0 && line < MAXLNUM), "line number", "(out of range)", {
api_set_error(err, kErrorTypeValidation, "Line number outside range");
return 0; return 0;
} });
if (col_start < 0 || col_start > MAXCOL) { VALIDATE_S((col_start >= 0 && col_start <= MAXCOL), "column", "(out of range)", {
api_set_error(err, kErrorTypeValidation, "Column value outside range");
return 0; return 0;
} });
if (col_end < 0 || col_end > MAXCOL) { if (col_end < 0 || col_end > MAXCOL) {
col_end = MAXCOL; col_end = MAXCOL;
} }
@ -950,10 +948,10 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
return; return;
} }
if (line_start < 0 || line_start >= MAXLNUM) { VALIDATE_S((line_start >= 0 && line_start < MAXLNUM), "line number", "(out of range)", {
api_set_error(err, kErrorTypeValidation, "Line number outside range");
return; return;
} });
if (line_end < 0 || line_end > MAXLNUM) { if (line_end < 0 || line_end > MAXLNUM) {
line_end = MAXLNUM; line_end = MAXLNUM;
} }
@ -1034,11 +1032,10 @@ void nvim_set_decoration_provider(Integer ns_id, Dict(set_decoration_provider) *
continue; continue;
} }
if (v->type != kObjectTypeLuaRef) { VALIDATE_T(cbs[i].name, kObjectTypeLuaRef, v->type, {
api_set_error(err, kErrorTypeValidation,
"%s is not a function", cbs[i].name);
goto error; goto error;
} });
*(cbs[i].dest) = v->data.luaref; *(cbs[i].dest) = v->data.luaref;
v->data.luaref = LUA_NOREF; v->data.luaref = LUA_NOREF;
} }
@ -1075,39 +1072,39 @@ static bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, in
*col = MAXCOL; *col = MAXCOL;
return true; return true;
} else if (id < 0) { } else if (id < 0) {
api_set_error(err, kErrorTypeValidation, "Mark id must be positive"); VALIDATE_INT(false, "mark id", id, {
return false; return false;
});
} }
ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id); ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
if (extmark.row >= 0) {
*row = extmark.row; VALIDATE_INT((extmark.row >= 0), "mark id (not found)", id, {
*col = extmark.col;
return true;
} else {
api_set_error(err, kErrorTypeValidation, "No mark with requested id");
return false; return false;
} });
*row = extmark.row;
*col = extmark.col;
return true;
// Check if it is a position // Check if it is a position
} else if (obj.type == kObjectTypeArray) { } else if (obj.type == kObjectTypeArray) {
Array pos = obj.data.array; Array pos = obj.data.array;
if (pos.size != 2 VALIDATE((pos.size == 2
|| pos.items[0].type != kObjectTypeInteger && pos.items[0].type == kObjectTypeInteger
|| pos.items[1].type != kObjectTypeInteger) { && pos.items[1].type == kObjectTypeInteger),
api_set_error(err, kErrorTypeValidation, "Invalid position: expected 2 Integer items", {
"Position must have 2 integer elements");
return false; return false;
} });
Integer pos_row = pos.items[0].data.integer; Integer pos_row = pos.items[0].data.integer;
Integer pos_col = pos.items[1].data.integer; Integer pos_col = pos.items[1].data.integer;
*row = (int)(pos_row >= 0 ? pos_row : MAXLNUM); *row = (int)(pos_row >= 0 ? pos_row : MAXLNUM);
*col = (colnr_T)(pos_col >= 0 ? pos_col : MAXCOL); *col = (colnr_T)(pos_col >= 0 ? pos_col : MAXCOL);
return true; return true;
} else { } else {
api_set_error(err, kErrorTypeValidation, VALIDATE(false, "Invalid position: expected mark id Integer or 2-item Array", {
"Position must be a mark id Integer or position Array"); return false;
return false; });
} }
} }
// adapted from sign.c:sign_define_init_text. // adapted from sign.c:sign_define_init_text.
@ -1151,17 +1148,14 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width)
VirtText virt_text = KV_INITIAL_VALUE; VirtText virt_text = KV_INITIAL_VALUE;
int w = 0; int w = 0;
for (size_t i = 0; i < chunks.size; i++) { for (size_t i = 0; i < chunks.size; i++) {
if (chunks.items[i].type != kObjectTypeArray) { VALIDATE_T("chunk", kObjectTypeArray, chunks.items[i].type, {
api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
goto free_exit; goto free_exit;
} });
Array chunk = chunks.items[i].data.array; Array chunk = chunks.items[i].data.array;
if (chunk.size == 0 || chunk.size > 2 VALIDATE((chunk.size > 0 && chunk.size <= 2 && chunk.items[0].type == kObjectTypeString),
|| chunk.items[0].type != kObjectTypeString) { "Invalid chunk: expected Array with 1 or 2 Strings", {
api_set_error(err, kErrorTypeValidation,
"Chunk is not an array with one or two strings");
goto free_exit; goto free_exit;
} });
String str = chunk.items[0].data.string; String str = chunk.items[0].data.string;

View File

@ -9,6 +9,7 @@
#include "nvim/api/options.h" #include "nvim/api/options.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/private/validate.h"
#include "nvim/autocmd.h" #include "nvim/autocmd.h"
#include "nvim/buffer_defs.h" #include "nvim/buffer_defs.h"
#include "nvim/eval/window.h" #include "nvim/eval/window.h"
@ -31,11 +32,14 @@ static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_t
} else if (!strcmp(opts->scope.data.string.data, "global")) { } else if (!strcmp(opts->scope.data.string.data, "global")) {
*scope = OPT_GLOBAL; *scope = OPT_GLOBAL;
} else { } else {
api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'"); VALIDATE(false, "Invalid scope (expected 'local' or 'global')", {
return FAIL; return FAIL;
});
} }
} else if (HAS_KEY(opts->scope)) { } else if (HAS_KEY(opts->scope)) {
api_set_error(err, kErrorTypeValidation, "invalid value for key: scope"); VALIDATE_T("scope", kObjectTypeString, opts->scope.type, {
return FAIL;
});
return FAIL; return FAIL;
} }
@ -48,8 +52,9 @@ static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_t
return FAIL; return FAIL;
} }
} else if (HAS_KEY(opts->win)) { } else if (HAS_KEY(opts->win)) {
api_set_error(err, kErrorTypeValidation, "invalid value for key: win"); VALIDATE_T("win", kObjectTypeInteger, opts->win.type, {
return FAIL; return FAIL;
});
} }
if (opts->buf.type == kObjectTypeInteger) { if (opts->buf.type == kObjectTypeInteger) {
@ -60,19 +65,17 @@ static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_t
return FAIL; return FAIL;
} }
} else if (HAS_KEY(opts->buf)) { } else if (HAS_KEY(opts->buf)) {
api_set_error(err, kErrorTypeValidation, "invalid value for key: buf"); VALIDATE_T("buf", kObjectTypeInteger, opts->buf.type, {
return FAIL; return FAIL;
});
} }
if (HAS_KEY(opts->scope) && HAS_KEY(opts->buf)) { VALIDATE((!HAS_KEY(opts->scope) || !HAS_KEY(opts->buf)), "cannot use both 'scope' and 'buf'", {
api_set_error(err, kErrorTypeValidation, "scope and buf cannot be used together");
return FAIL; return FAIL;
} });
VALIDATE((!HAS_KEY(opts->win) || !HAS_KEY(opts->buf)), "cannot use both 'buf' and 'win'", {
if (HAS_KEY(opts->win) && HAS_KEY(opts->buf)) {
api_set_error(err, kErrorTypeValidation, "buf and win cannot be used together");
return FAIL; return FAIL;
} });
return OK; return OK;
} }
@ -132,8 +135,9 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
} }
break; break;
default: default:
api_set_error(err, kErrorTypeValidation, "unknown option '%s'", name.data); VALIDATE_S(false, "option", name.data, {
return rv; return rv;
});
} }
return rv; return rv;
@ -193,8 +197,9 @@ void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error
scope |= OPT_CLEAR; scope |= OPT_CLEAR;
break; break;
default: default:
api_set_error(err, kErrorTypeValidation, "invalid value for option"); VALIDATE_EXP(false, "option type", "Integer, Boolean, or String", api_typename(value.type), {
return; return;
});
} }
access_option_value_for(name.data, &numval, &stringval, scope, opt_type, to, false, err); access_option_value_for(name.data, &numval, &stringval, scope, opt_type, to, false, err);
@ -351,21 +356,18 @@ static Object get_option_from(void *from, int type, String name, Error *err)
{ {
Object rv = OBJECT_INIT; Object rv = OBJECT_INIT;
if (name.size == 0) { VALIDATE_S(name.size > 0, "option name", "<empty>", {
api_set_error(err, kErrorTypeValidation, "Empty option name");
return rv; return rv;
} });
// Return values // Return values
int64_t numval; int64_t numval;
char *stringval = NULL; char *stringval = NULL;
int flags = get_option_value_strict(name.data, &numval, &stringval, type, from);
if (!flags) { int flags = get_option_value_strict(name.data, &numval, &stringval, type, from);
api_set_error(err, kErrorTypeValidation, "Invalid option name: '%s'", VALIDATE_S(flags != 0, "option name", name.data, {
name.data);
return rv; return rv;
} });
if (flags & SOPT_BOOL) { if (flags & SOPT_BOOL) {
rv.type = kObjectTypeBoolean; rv.type = kObjectTypeBoolean;
@ -374,20 +376,15 @@ static Object get_option_from(void *from, int type, String name, Error *err)
rv.type = kObjectTypeInteger; rv.type = kObjectTypeInteger;
rv.data.integer = numval; rv.data.integer = numval;
} else if (flags & SOPT_STRING) { } else if (flags & SOPT_STRING) {
if (stringval) { if (!stringval) {
rv.type = kObjectTypeString; api_set_error(err, kErrorTypeException, "Failed to get option '%s'", name.data);
rv.data.string.data = stringval; return rv;
rv.data.string.size = strlen(stringval);
} else {
api_set_error(err, kErrorTypeException,
"Failed to get value for option '%s'",
name.data);
} }
rv.type = kObjectTypeString;
rv.data.string.data = stringval;
rv.data.string.size = strlen(stringval);
} else { } else {
api_set_error(err, api_set_error(err, kErrorTypeException, "Unknown type for option '%s'", name.data);
kErrorTypeException,
"Unknown type for option '%s'",
name.data);
} }
return rv; return rv;
@ -402,29 +399,22 @@ static Object get_option_from(void *from, int type, String name, Error *err)
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void set_option_to(uint64_t channel_id, void *to, int type, String name, Object value, Error *err) void set_option_to(uint64_t channel_id, void *to, int type, String name, Object value, Error *err)
{ {
if (name.size == 0) { VALIDATE_S(name.size > 0, "option name", "<empty>", {
api_set_error(err, kErrorTypeValidation, "Empty option name");
return; return;
} });
int flags = get_option_value_strict(name.data, NULL, NULL, type, to); int flags = get_option_value_strict(name.data, NULL, NULL, type, to);
VALIDATE_S(flags != 0, "option name", name.data, {
if (flags == 0) {
api_set_error(err, kErrorTypeValidation, "Invalid option name '%s'",
name.data);
return; return;
} });
if (value.type == kObjectTypeNil) { if (value.type == kObjectTypeNil) {
if (type == SREQ_GLOBAL) { if (type == SREQ_GLOBAL) {
api_set_error(err, kErrorTypeException, "Cannot unset option '%s'", api_set_error(err, kErrorTypeException, "Cannot unset option '%s'", name.data);
name.data);
return; return;
} else if (!(flags & SOPT_GLOBAL)) { } else if (!(flags & SOPT_GLOBAL)) {
api_set_error(err, api_set_error(err, kErrorTypeException,
kErrorTypeException, "Cannot unset option '%s' because it doesn't have a global value",
"Cannot unset option '%s' "
"because it doesn't have a global value",
name.data); name.data);
return; return;
} else { } else {
@ -437,39 +427,23 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object
char *stringval = NULL; char *stringval = NULL;
if (flags & SOPT_BOOL) { if (flags & SOPT_BOOL) {
if (value.type != kObjectTypeBoolean) { VALIDATE_FMT(value.type == kObjectTypeBoolean, "Option '%s' value must be Boolean", name.data, {
api_set_error(err,
kErrorTypeValidation,
"Option '%s' requires a Boolean value",
name.data);
return; return;
} });
numval = value.data.boolean; numval = value.data.boolean;
} else if (flags & SOPT_NUM) { } else if (flags & SOPT_NUM) {
if (value.type != kObjectTypeInteger) { VALIDATE_FMT(value.type == kObjectTypeInteger, "Option '%s' value must be Integer", name.data, {
api_set_error(err, kErrorTypeValidation,
"Option '%s' requires an integer value",
name.data);
return; return;
} });
VALIDATE_FMT((value.data.integer <= INT_MAX && value.data.integer >= INT_MIN),
if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) { "Option '%s' value is out of range", name.data, {
api_set_error(err, kErrorTypeValidation,
"Value for option '%s' is out of range",
name.data);
return; return;
} });
numval = (int)value.data.integer; numval = (int)value.data.integer;
} else { } else {
if (value.type != kObjectTypeString) { VALIDATE_FMT(value.type == kObjectTypeString, "Option '%s' value must be String", name.data, {
api_set_error(err, kErrorTypeValidation,
"Option '%s' requires a string value",
name.data);
return; return;
} });
stringval = value.data.string.data; stringval = value.data.string.data;
} }

View File

@ -827,6 +827,36 @@ int object_to_hl_id(Object obj, const char *what, Error *err)
} }
} }
char *api_typename(ObjectType t)
{
switch (t) {
case kObjectTypeNil:
return "nil";
case kObjectTypeBoolean:
return "Boolean";
case kObjectTypeInteger:
return "Integer";
case kObjectTypeFloat:
return "Float";
case kObjectTypeString:
return "String";
case kObjectTypeArray:
return "Array";
case kObjectTypeDictionary:
return "Dict";
case kObjectTypeLuaRef:
return "Function";
case kObjectTypeBuffer:
return "Buffer";
case kObjectTypeWindow:
return "Window";
case kObjectTypeTabpage:
return "Tabpage";
default:
abort();
}
}
HlMessage parse_hl_msg(Array chunks, Error *err) HlMessage parse_hl_msg(Array chunks, Error *err)
{ {
HlMessage hl_msg = KV_INITIAL_VALUE; HlMessage hl_msg = KV_INITIAL_VALUE;

View File

@ -0,0 +1,69 @@
#ifndef NVIM_API_PRIVATE_VALIDATE_H
#define NVIM_API_PRIVATE_VALIDATE_H
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#define VALIDATE_INT(cond, name, val_, code) \
do { \
if (!(cond)) { \
api_set_error(err, kErrorTypeValidation, "Invalid " name ": %" PRId64, val_); \
code; \
} \
} while (0)
#define VALIDATE_S(cond, name, val_, code) \
do { \
if (!(cond)) { \
if (strequal(val_, "")) { \
api_set_error(err, kErrorTypeValidation, "Invalid " name); \
} else { \
api_set_error(err, kErrorTypeValidation, "Invalid " name ": '%s'", val_); \
} \
code; \
} \
} while (0)
#define VALIDATE_R(cond, name, code) \
do { \
if (!(cond)) { \
api_set_error(err, kErrorTypeValidation, "'" name "' is required"); \
code; \
} \
} while (0)
#define VALIDATE_EXP(cond, name, expected, actual, code) \
do { \
if (!(cond)) { \
api_set_error(err, kErrorTypeValidation, "Invalid " name ": expected %s, got %s", \
expected, actual); \
code; \
} \
} while (0)
#define VALIDATE_T(name, expected_t, actual_t, code) \
do { \
if (expected_t != actual_t) { \
api_set_error(err, kErrorTypeValidation, "Invalid %s: expected %s, got %s", \
name, api_typename(expected_t), api_typename(actual_t)); \
code; \
} \
} while (0)
#define VALIDATE(cond, msg_, code) \
do { \
if (!(cond)) { \
api_set_error(err, kErrorTypeValidation, "%s", msg_); \
code; \
} \
} while (0)
#define VALIDATE_FMT(cond, fmt_, msg_, code) \
do { \
if (!(cond)) { \
api_set_error(err, kErrorTypeValidation, fmt_, msg_); \
code; \
} \
} while (0)
#endif // NVIM_API_PRIVATE_VALIDATE_H

View File

@ -18,6 +18,7 @@
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h" #include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/private/validate.h"
#include "nvim/api/vim.h" #include "nvim/api/vim.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/autocmd.h" #include "nvim/autocmd.h"
@ -193,10 +194,9 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err)
void nvim_set_hl_ns(Integer ns_id, Error *err) void nvim_set_hl_ns(Integer ns_id, Error *err)
FUNC_API_SINCE(10) FUNC_API_SINCE(10)
{ {
if (ns_id < 0) { VALIDATE_INT((ns_id >= 0), "namespace", ns_id, {
api_set_error(err, kErrorTypeValidation, "no such namespace");
return; return;
} });
ns_hl_global = (NS)ns_id; ns_hl_global = (NS)ns_id;
hl_check_ns(); hl_check_ns();
@ -500,10 +500,9 @@ Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err)
Integer nvim_strwidth(String text, Error *err) Integer nvim_strwidth(String text, Error *err)
FUNC_API_SINCE(1) FUNC_API_SINCE(1)
{ {
if (text.size > INT_MAX) { VALIDATE((text.size <= INT_MAX), "text length (too long)", {
api_set_error(err, kErrorTypeValidation, "String is too long");
return 0; return 0;
} });
return (Integer)mb_string2cells(text.data); return (Integer)mb_string2cells(text.data);
} }
@ -575,9 +574,7 @@ ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, E
{ {
bool is_lua = api_object_to_bool(opts->is_lua, "is_lua", false, err); bool is_lua = api_object_to_bool(opts->is_lua, "is_lua", false, err);
bool source = api_object_to_bool(opts->do_source, "do_source", false, err); bool source = api_object_to_bool(opts->do_source, "do_source", false, err);
if (source && !nlua_is_deferred_safe()) { VALIDATE((!source || nlua_is_deferred_safe()), "'do_source' used in fast callback", {});
api_set_error(err, kErrorTypeValidation, "'do_source' cannot be used in fast callback");
}
if (ERROR_SET(err)) { if (ERROR_SET(err)) {
return (Array)ARRAY_DICT_INIT; return (Array)ARRAY_DICT_INIT;
@ -602,10 +599,9 @@ ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, E
void nvim_set_current_dir(String dir, Error *err) void nvim_set_current_dir(String dir, Error *err)
FUNC_API_SINCE(1) FUNC_API_SINCE(1)
{ {
if (dir.size >= MAXPATHL) { VALIDATE((dir.size < MAXPATHL), "directory name (too long)", {
api_set_error(err, kErrorTypeValidation, "Directory name is too long");
return; return;
} });
char string[MAXPATHL]; char string[MAXPATHL];
memcpy(string, dir.data, dir.size); memcpy(string, dir.data, dir.size);
@ -664,16 +660,14 @@ Object nvim_get_var(String name, Error *err)
{ {
dictitem_T *di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size); dictitem_T *di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
if (di == NULL) { // try to autoload script if (di == NULL) { // try to autoload script
if (!script_autoload(name.data, name.size, false) || aborting()) { VALIDATE_S((script_autoload(name.data, name.size, false) && !aborting()), "key", name.data, {
api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data);
return (Object)OBJECT_INIT; return (Object)OBJECT_INIT;
} });
di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size); di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
} }
if (di == NULL) { VALIDATE_S((di != NULL), "key (not found)", name.data, {
api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data);
return (Object)OBJECT_INIT; return (Object)OBJECT_INIT;
} });
return vim_to_object(&di->di_tv); return vim_to_object(&di->di_tv);
} }
@ -991,16 +985,14 @@ Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err)
String k = opts.items[i].key; String k = opts.items[i].key;
Object *v = &opts.items[i].value; Object *v = &opts.items[i].value;
if (strequal("on_input", k.data)) { if (strequal("on_input", k.data)) {
if (v->type != kObjectTypeLuaRef) { VALIDATE_T("on_input", kObjectTypeLuaRef, v->type, {
api_set_error(err, kErrorTypeValidation,
"%s is not a function", "on_input");
return 0; return 0;
} });
cb = v->data.luaref; cb = v->data.luaref;
v->data.luaref = LUA_NOREF; v->data.luaref = LUA_NOREF;
break; break;
} else { } else {
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); VALIDATE_S(false, "key", k.data, {});
} }
} }
@ -1075,9 +1067,7 @@ void nvim_chan_send(Integer chan, String data, Error *err)
channel_send((uint64_t)chan, data.data, data.size, channel_send((uint64_t)chan, data.data, data.size,
false, &error); false, &error);
if (error) { VALIDATE(!error, error, {});
api_set_error(err, kErrorTypeValidation, "%s", error);
}
} }
/// Gets the current list of tabpage handles. /// Gets the current list of tabpage handles.
@ -1164,10 +1154,9 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
static bool draining = false; static bool draining = false;
bool cancel = false; bool cancel = false;
if (phase < -1 || phase > 3) { VALIDATE_INT((phase >= -1 && phase <= 3), "phase", phase, {
api_set_error(err, kErrorTypeValidation, "Invalid phase: %" PRId64, phase);
return false; return false;
} });
Array args = ARRAY_DICT_INIT; Array args = ARRAY_DICT_INIT;
Object rv = OBJECT_INIT; Object rv = OBJECT_INIT;
if (phase == -1 || phase == 1) { // Start of paste-stream. if (phase == -1 || phase == 1) { // Start of paste-stream.
@ -1234,20 +1223,17 @@ void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow,
FUNC_API_CHECK_TEXTLOCK FUNC_API_CHECK_TEXTLOCK
{ {
yankreg_T *reg = xcalloc(1, sizeof(yankreg_T)); yankreg_T *reg = xcalloc(1, sizeof(yankreg_T));
if (!prepare_yankreg_from_object(reg, type, lines.size)) { VALIDATE_S((prepare_yankreg_from_object(reg, type, lines.size)), "type", type.data, {
api_set_error(err, kErrorTypeValidation, "Invalid type: '%s'", type.data);
goto cleanup; goto cleanup;
} });
if (lines.size == 0) { if (lines.size == 0) {
goto cleanup; // Nothing to do. goto cleanup; // Nothing to do.
} }
for (size_t i = 0; i < lines.size; i++) { for (size_t i = 0; i < lines.size; i++) {
if (lines.items[i].type != kObjectTypeString) { VALIDATE_T("line", kObjectTypeString, lines.items[i].type, {
api_set_error(err, kErrorTypeValidation,
"Invalid lines (expected array of strings)");
goto cleanup; goto cleanup;
} });
String line = lines.items[i].data.string; String line = lines.items[i].data.string;
reg->y_array[i] = xmemdupz(line.data, line.size); reg->y_array[i] = xmemdupz(line.data, line.size);
memchrsub(reg->y_array[i], NUL, NL, line.size); memchrsub(reg->y_array[i], NUL, NL, line.size);
@ -1351,8 +1337,9 @@ Dictionary nvim_get_context(Dict(context) *opts, Error *err)
if (opts->types.type == kObjectTypeArray) { if (opts->types.type == kObjectTypeArray) {
types = opts->types.data.array; types = opts->types.data.array;
} else if (opts->types.type != kObjectTypeNil) { } else if (opts->types.type != kObjectTypeNil) {
api_set_error(err, kErrorTypeValidation, "invalid value for key: types"); VALIDATE_T("types", kObjectTypeArray, opts->types.type, {
return (Dictionary)ARRAY_DICT_INIT; return (Dictionary)ARRAY_DICT_INIT;
});
} }
int int_types = types.size > 0 ? 0 : kCtxAll; int int_types = types.size > 0 ? 0 : kCtxAll;
@ -1373,8 +1360,9 @@ Dictionary nvim_get_context(Dict(context) *opts, Error *err)
} else if (strequal(s, "funcs")) { } else if (strequal(s, "funcs")) {
int_types |= kCtxFuncs; int_types |= kCtxFuncs;
} else { } else {
api_set_error(err, kErrorTypeValidation, "unexpected type: %s", s); VALIDATE_S(false, "type", s, {
return (Dictionary)ARRAY_DICT_INIT; return (Dictionary)ARRAY_DICT_INIT;
});
} }
} }
} }
@ -1651,34 +1639,20 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Arena *arena, Error *er
size_t i; // also used for freeing the variables size_t i; // also used for freeing the variables
for (i = 0; i < calls.size; i++) { for (i = 0; i < calls.size; i++) {
if (calls.items[i].type != kObjectTypeArray) { VALIDATE_T("calls item", kObjectTypeArray, calls.items[i].type, {
api_set_error(err,
kErrorTypeValidation,
"Items in calls array must be arrays");
goto theend; goto theend;
} });
Array call = calls.items[i].data.array; Array call = calls.items[i].data.array;
if (call.size != 2) { VALIDATE((call.size == 2), "Items in calls array must be arrays of size 2", {
api_set_error(err,
kErrorTypeValidation,
"Items in calls array must be arrays of size 2");
goto theend; goto theend;
} });
VALIDATE_T("name", kObjectTypeString, call.items[0].type, {
if (call.items[0].type != kObjectTypeString) {
api_set_error(err,
kErrorTypeValidation,
"Name must be String");
goto theend; goto theend;
} });
String name = call.items[0].data.string; String name = call.items[0].data.string;
VALIDATE_T("args", kObjectTypeArray, call.items[1].type, {
if (call.items[1].type != kObjectTypeArray) {
api_set_error(err,
kErrorTypeValidation,
"Args must be Array");
goto theend; goto theend;
} });
Array args = call.items[1].data.array; Array args = call.items[1].data.array;
MsgpackRpcRequestHandler handler = MsgpackRpcRequestHandler handler =
@ -1937,10 +1911,9 @@ void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, Di
Error *err) Error *err)
FUNC_API_SINCE(6) FUNC_API_SINCE(6)
{ {
if (opts.size > 0) { VALIDATE((opts.size == 0), "opts dict isn't empty", {
api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
return; return;
} });
if (finish) { if (finish) {
insert = true; insert = true;
@ -1961,13 +1934,10 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, E
g = &pum_grid; g = &pum_grid;
} else if (grid > 1) { } else if (grid > 1) {
win_T *wp = get_win_by_grid_handle((handle_T)grid); win_T *wp = get_win_by_grid_handle((handle_T)grid);
if (wp != NULL && wp->w_grid_alloc.chars != NULL) { VALIDATE_INT((wp != NULL && wp->w_grid_alloc.chars != NULL), "grid handle", grid, {
g = &wp->w_grid_alloc;
} else {
api_set_error(err, kErrorTypeValidation,
"No grid with the given handle");
return ret; return ret;
} });
g = &wp->w_grid_alloc;
} }
if (row < 0 || row >= g->rows if (row < 0 || row >= g->rows
@ -2009,20 +1979,16 @@ Boolean nvim_del_mark(String name, Error *err)
FUNC_API_SINCE(8) FUNC_API_SINCE(8)
{ {
bool res = false; bool res = false;
if (name.size != 1) { VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
api_set_error(err, kErrorTypeValidation,
"Mark name must be a single character");
return res; return res;
} });
// Only allow file/uppercase marks // Only allow file/uppercase marks
// TODO(muniter): Refactor this ASCII_ISUPPER macro to a proper function // TODO(muniter): Refactor this ASCII_ISUPPER macro to a proper function
if (ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)) { VALIDATE_S((ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)),
res = set_mark(NULL, name, 0, 0, err); "mark name (must be file/uppercase)", name.data, {
} else { return res;
api_set_error(err, kErrorTypeValidation, });
"Only file/uppercase marks allowed, invalid mark name: '%c'", res = set_mark(NULL, name, 0, 0, err);
*name.data);
}
return res; return res;
} }
@ -2043,16 +2009,13 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
{ {
Array rv = ARRAY_DICT_INIT; Array rv = ARRAY_DICT_INIT;
if (name.size != 1) { VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
api_set_error(err, kErrorTypeValidation,
"Mark name must be a single character");
return rv; return rv;
} else if (!(ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data))) { });
api_set_error(err, kErrorTypeValidation, VALIDATE_S((ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)),
"Only file/uppercase marks allowed, invalid mark name: '%c'", "mark name (must be file/uppercase)", name.data, {
*name.data);
return rv; return rv;
} });
xfmark_T *mark = mark_get_global(false, *name.data); // false avoids loading the mark buffer xfmark_T *mark = mark_get_global(false, *name.data); // false avoids loading the mark buffer
pos_T pos = mark->fmark.mark; pos_T pos = mark->fmark.mark;
@ -2137,27 +2100,28 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
if (str.size < 2 || memcmp(str.data, "%!", 2) != 0) { if (str.size < 2 || memcmp(str.data, "%!", 2) != 0) {
const char *const errmsg = check_stl_option(str.data); const char *const errmsg = check_stl_option(str.data);
if (errmsg) { VALIDATE(!errmsg, errmsg, {
api_set_error(err, kErrorTypeValidation, "%s", errmsg);
return result; return result;
} });
} }
if (HAS_KEY(opts->winid)) { if (HAS_KEY(opts->winid)) {
if (opts->winid.type != kObjectTypeInteger) { VALIDATE_T("winid", kObjectTypeInteger, opts->winid.type, {
api_set_error(err, kErrorTypeValidation, "winid must be an integer");
return result; return result;
} });
window = (Window)opts->winid.data.integer; window = (Window)opts->winid.data.integer;
} }
if (HAS_KEY(opts->fillchar)) { if (HAS_KEY(opts->fillchar)) {
if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size == 0 VALIDATE_T("fillchar", kObjectTypeString, opts->fillchar.type, {
|| ((size_t)utf_ptr2len(opts->fillchar.data.string.data)
!= opts->fillchar.data.string.size)) {
api_set_error(err, kErrorTypeValidation, "fillchar must be a single character");
return result; return result;
} });
VALIDATE((opts->fillchar.data.string.size != 0
&& ((size_t)utf_ptr2len(opts->fillchar.data.string.data)
== opts->fillchar.data.string.size)),
"Invalid fillchar (not a single character)", {
return result;
});
fillchar = utf_ptr2char(opts->fillchar.data.string.data); fillchar = utf_ptr2char(opts->fillchar.data.string.data);
} }
if (HAS_KEY(opts->highlights)) { if (HAS_KEY(opts->highlights)) {
@ -2211,10 +2175,9 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
} }
if (HAS_KEY(opts->maxwidth)) { if (HAS_KEY(opts->maxwidth)) {
if (opts->maxwidth.type != kObjectTypeInteger) { VALIDATE_T("maxwidth", kObjectTypeInteger, opts->maxwidth.type, {
api_set_error(err, kErrorTypeValidation, "maxwidth must be an integer");
return result; return result;
} });
maxwidth = (int)opts->maxwidth.data.integer; maxwidth = (int)opts->maxwidth.data.integer;
} else { } else {

View File

@ -33,6 +33,7 @@
#include "nvim/api/extmark.h" #include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/private/validate.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/autocmd.h" #include "nvim/autocmd.h"
#include "nvim/buffer.h" #include "nvim/buffer.h"
@ -5624,10 +5625,9 @@ long get_sidescrolloff_value(win_T *wp)
Dictionary get_vimoption(String name, Error *err) Dictionary get_vimoption(String name, Error *err)
{ {
int opt_idx = findoption_len((const char *)name.data, name.size); int opt_idx = findoption_len((const char *)name.data, name.size);
if (opt_idx < 0) { VALIDATE_S(opt_idx >= 0, "option (not found)", name.data, {
api_set_error(err, kErrorTypeValidation, "no such option: '%s'", name.data);
return (Dictionary)ARRAY_DICT_INIT; return (Dictionary)ARRAY_DICT_INIT;
} });
return vimoption2dict(&options[opt_idx]); return vimoption2dict(&options[opt_idx]);
} }

View File

@ -11,6 +11,7 @@
#include "klib/kvec.h" #include "klib/kvec.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/private/validate.h"
#include "nvim/api/ui.h" #include "nvim/api/ui.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/autocmd.h" #include "nvim/autocmd.h"
@ -608,7 +609,7 @@ Array ui_array(void)
return all_uis; return all_uis;
} }
void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error) void ui_grid_resize(handle_T grid_handle, int width, int height, Error *err)
{ {
if (grid_handle == DEFAULT_GRID_HANDLE) { if (grid_handle == DEFAULT_GRID_HANDLE) {
screen_resize(width, height); screen_resize(width, height);
@ -616,11 +617,9 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error)
} }
win_T *wp = get_win_by_grid_handle(grid_handle); win_T *wp = get_win_by_grid_handle(grid_handle);
if (wp == NULL) { VALIDATE_INT((wp != NULL), "window handle", (int64_t)grid_handle, {
api_set_error(error, kErrorTypeValidation,
"No window with the given handle");
return; return;
} });
if (wp->w_floating) { if (wp->w_floating) {
if (width != wp->w_width || height != wp->w_height) { if (width != wp->w_width || height != wp->w_height) {

View File

@ -21,7 +21,7 @@ describe('autocmd api', function()
callback = "NotAllowed", callback = "NotAllowed",
}) })
eq("specify either 'callback' or 'command', not both", rv) eq("Cannot use both 'callback' and 'command'", rv)
end) end)
it('doesnt leak when you use ++once', function() it('doesnt leak when you use ++once', function()
@ -66,7 +66,7 @@ describe('autocmd api', function()
pattern = "*.py", pattern = "*.py",
}) })
eq("cannot pass both: 'pattern' and 'buffer' for the same autocmd", rv) eq("Cannot use both 'pattern' and 'buffer' for the same autocmd", rv)
end) end)
it('does not allow passing invalid buffers', function() it('does not allow passing invalid buffers', function()
@ -407,8 +407,8 @@ describe('autocmd api', function()
pattern = "<buffer=2>", pattern = "<buffer=2>",
}}, aus) }}, aus)
eq("Invalid value for 'buffer': must be an integer or array of integers", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = "foo" })) eq("Invalid buffer: expected Integer or Array, got String", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = "foo" }))
eq("Invalid value for 'buffer': must be an integer", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { "foo", 42 } })) eq("Invalid buffer: expected Integer, got String", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { "foo", 42 } }))
eq("Invalid buffer id: 42", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { 42 } })) eq("Invalid buffer id: 42", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { 42 } }))
local bufs = {} local bufs = {}
@ -416,7 +416,7 @@ describe('autocmd api', function()
table.insert(bufs, meths.create_buf(true, false)) table.insert(bufs, meths.create_buf(true, false))
end end
eq("Too many buffers. Please limit yourself to 256 or fewer", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = bufs })) eq("Too many buffers (maximum of 256)", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = bufs }))
end) end)
it('should return autocmds when group is specified by id', function() it('should return autocmds when group is specified by id', function()
@ -578,7 +578,7 @@ describe('autocmd api', function()
]], {})) ]], {}))
eq(false, success) eq(false, success)
matches('invalid augroup: NotDefined', code) matches("Invalid group: 'NotDefined'", code)
end) end)
it('raises error for undefined augroup id', function() it('raises error for undefined augroup id', function()
@ -596,7 +596,7 @@ describe('autocmd api', function()
]], {})) ]], {}))
eq(false, success) eq(false, success)
matches('invalid augroup: 1', code) matches('Invalid group: 1', code)
end) end)
it('raises error for invalid group type', function() it('raises error for invalid group type', function()
@ -611,7 +611,7 @@ describe('autocmd api', function()
]], {})) ]], {}))
eq(false, success) eq(false, success)
matches("'group' must be a string or an integer", code) matches("Invalid group: expected String or Integer, got Boolean", code)
end) end)
it('raises error for invalid pattern array', function() it('raises error for invalid pattern array', function()
@ -625,7 +625,7 @@ describe('autocmd api', function()
]], {})) ]], {}))
eq(false, success) eq(false, success)
matches("All entries in 'pattern' must be strings", code) matches("Invalid 'pattern' item type: expected String, got Array", code)
end) end)
end) end)

View File

@ -762,7 +762,7 @@ describe('API: buffer events:', function()
it('returns a proper error on nonempty options dict', function() it('returns a proper error on nonempty options dict', function()
clear() clear()
local b = editoriginal(false) local b = editoriginal(false)
eq("unexpected key: builtin", pcall_err(buffer, 'attach', b, false, {builtin="asfd"})) eq("Invalid key: 'builtin'", pcall_err(buffer, 'attach', b, false, {builtin="asfd"}))
end) end)
it('nvim_buf_attach returns response after delay #8634', function() it('nvim_buf_attach returns response after delay #8634', function()

View File

@ -543,7 +543,7 @@ describe('nvim_create_user_command', function()
end) end)
it('does not allow invalid command names', function() it('does not allow invalid command names', function()
matches("'name' must begin with an uppercase letter", pcall_err(exec_lua, [[ matches("Invalid command name %(must begin with an uppercase letter%): 'test'", pcall_err(exec_lua, [[
vim.api.nvim_create_user_command('test', 'echo "hi"', {}) vim.api.nvim_create_user_command('test', 'echo "hi"', {})
]])) ]]))

View File

@ -106,7 +106,7 @@ describe('API/extmarks', function()
end_col = 0, end_col = 0,
end_row = 1 end_row = 1
}) })
eq("end_col value outside range", eq("Invalid end_col: '(out of range)'",
pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = 1, end_row = 1 })) pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = 1, end_row = 1 }))
end) end)
@ -1344,10 +1344,10 @@ describe('API/extmarks', function()
it('throws consistent error codes', function() it('throws consistent error codes', function()
local ns_invalid = ns2 + 1 local ns_invalid = ns2 + 1
eq("Invalid ns_id", pcall_err(set_extmark, ns_invalid, marks[1], positions[1][1], positions[1][2])) eq("Invalid ns_id: 3", pcall_err(set_extmark, ns_invalid, marks[1], positions[1][1], positions[1][2]))
eq("Invalid ns_id", pcall_err(curbufmeths.del_extmark, ns_invalid, marks[1])) eq("Invalid ns_id: 3", pcall_err(curbufmeths.del_extmark, ns_invalid, marks[1]))
eq("Invalid ns_id", pcall_err(get_extmarks, ns_invalid, positions[1], positions[2])) eq("Invalid ns_id: 3", pcall_err(get_extmarks, ns_invalid, positions[1], positions[2]))
eq("Invalid ns_id", pcall_err(get_extmark_by_id, ns_invalid, marks[1])) eq("Invalid ns_id: 3", pcall_err(get_extmark_by_id, ns_invalid, marks[1]))
end) end)
it('when col = line-length, set the mark on eol', function() it('when col = line-length, set the mark on eol', function()
@ -1362,13 +1362,13 @@ describe('API/extmarks', function()
it('when col = line-length, set the mark on eol', function() it('when col = line-length, set the mark on eol', function()
local invalid_col = init_text:len() + 1 local invalid_col = init_text:len() + 1
eq("col value outside range", pcall_err(set_extmark, ns, marks[1], 0, invalid_col)) eq("Invalid col: '(out of range)'", pcall_err(set_extmark, ns, marks[1], 0, invalid_col))
end) end)
it('fails when line > line_count', function() it('fails when line > line_count', function()
local invalid_col = init_text:len() + 1 local invalid_col = init_text:len() + 1
local invalid_lnum = 3 local invalid_lnum = 3
eq('line value outside range', pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col)) eq("Invalid line: '(out of range)'", pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col))
eq({}, get_extmark_by_id(ns, marks[1])) eq({}, get_extmark_by_id(ns, marks[1]))
end) end)

View File

@ -1155,7 +1155,7 @@ describe('API', function()
describe('nvim_put', function() describe('nvim_put', function()
it('validates args', function() it('validates args', function()
eq('Invalid lines (expected array of strings)', eq("Invalid line: expected String, got Integer",
pcall_err(request, 'nvim_put', {42}, 'l', false, false)) pcall_err(request, 'nvim_put', {42}, 'l', false, false))
eq("Invalid type: 'x'", eq("Invalid type: 'x'",
pcall_err(request, 'nvim_put', {'foo'}, 'x', false, false)) pcall_err(request, 'nvim_put', {'foo'}, 'x', false, false))
@ -1410,6 +1410,15 @@ describe('API', function()
ok(not nvim('get_option_value', 'equalalways', {})) ok(not nvim('get_option_value', 'equalalways', {}))
end) end)
it('validation', function()
eq("Invalid scope (expected 'local' or 'global')",
pcall_err(nvim, 'get_option_value', 'scrolloff', {scope = 'bogus'}))
eq("Invalid scope (expected 'local' or 'global')",
pcall_err(nvim, 'set_option_value', 'scrolloff', 1, {scope = 'bogus'}))
eq("Invalid scope: expected String, got Integer",
pcall_err(nvim, 'get_option_value', 'scrolloff', {scope = 42}))
end)
it('can get local values when global value is set', function() it('can get local values when global value is set', function()
eq(0, nvim('get_option_value', 'scrolloff', {})) eq(0, nvim('get_option_value', 'scrolloff', {}))
eq(-1, nvim('get_option_value', 'scrolloff', {scope = 'local'})) eq(-1, nvim('get_option_value', 'scrolloff', {scope = 'local'}))
@ -1780,9 +1789,9 @@ describe('API', function()
it('validates args', function() it('validates args', function()
eq("Invalid key: 'blah'", eq("Invalid key: 'blah'",
pcall_err(nvim, 'get_context', {blah={}})) pcall_err(nvim, 'get_context', {blah={}}))
eq('invalid value for key: types', eq("Invalid types: expected Array, got Integer",
pcall_err(nvim, 'get_context', {types=42})) pcall_err(nvim, 'get_context', {types=42}))
eq('unexpected type: zub', eq("Invalid type: 'zub'",
pcall_err(nvim, 'get_context', {types={'jumps', 'zub', 'zam',}})) pcall_err(nvim, 'get_context', {types={'jumps', 'zub', 'zam',}}))
end) end)
it('returns map of current editor state', function() it('returns map of current editor state', function()
@ -2223,15 +2232,14 @@ describe('API', function()
eq(5, meths.get_var('avar')) eq(5, meths.get_var('avar'))
end) end)
it('throws error on malformed arguments', function() it('validation', function()
local req = { local req = {
{'nvim_set_var', {'avar', 1}}, {'nvim_set_var', {'avar', 1}},
{'nvim_set_var'}, {'nvim_set_var'},
{'nvim_set_var', {'avar', 2}}, {'nvim_set_var', {'avar', 2}},
} }
local status, err = pcall(meths.call_atomic, req) eq('Items in calls array must be arrays of size 2',
eq(false, status) pcall_err(meths.call_atomic, req))
ok(err:match('Items in calls array must be arrays of size 2') ~= nil)
-- call before was done, but not after -- call before was done, but not after
eq(1, meths.get_var('avar')) eq(1, meths.get_var('avar'))
@ -2239,18 +2247,16 @@ describe('API', function()
{ 'nvim_set_var', { 'bvar', { 2, 3 } } }, { 'nvim_set_var', { 'bvar', { 2, 3 } } },
12, 12,
} }
status, err = pcall(meths.call_atomic, req) eq("Invalid calls item: expected Array, got Integer",
eq(false, status) pcall_err(meths.call_atomic, req))
ok(err:match('Items in calls array must be arrays') ~= nil)
eq({2,3}, meths.get_var('bvar')) eq({2,3}, meths.get_var('bvar'))
req = { req = {
{'nvim_set_current_line', 'little line'}, {'nvim_set_current_line', 'little line'},
{'nvim_set_var', {'avar', 3}}, {'nvim_set_var', {'avar', 3}},
} }
status, err = pcall(meths.call_atomic, req) eq("Invalid args: expected Array, got String",
eq(false, status) pcall_err(meths.call_atomic, req))
ok(err:match('Args must be Array') ~= nil)
-- call before was done, but not after -- call before was done, but not after
eq(1, meths.get_var('avar')) eq(1, meths.get_var('avar'))
eq({''}, meths.buf_get_lines(0, 0, -1, true)) eq({''}, meths.buf_get_lines(0, 0, -1, true))
@ -2750,7 +2756,7 @@ describe('API', function()
describe('nvim_get_option_info', function() describe('nvim_get_option_info', function()
it('should error for unknown options', function() it('should error for unknown options', function()
eq("no such option: 'bogus'", pcall_err(meths.get_option_info, 'bogus')) eq("Invalid option (not found): 'bogus'", pcall_err(meths.get_option_info, 'bogus'))
end) end)
it('should return the same options for short and long name', function() it('should return the same options for short and long name', function()
@ -3031,10 +3037,10 @@ describe('API', function()
eq(true, meths.del_mark('F')) eq(true, meths.del_mark('F'))
eq({0, 0}, meths.buf_get_mark(buf, 'F')) eq({0, 0}, meths.buf_get_mark(buf, 'F'))
end) end)
it('fails when invalid marks are used', function() it('validation', function()
eq(false, pcall(meths.del_mark, 'f')) eq("Invalid mark name (must be file/uppercase): 'f'", pcall_err(meths.del_mark, 'f'))
eq(false, pcall(meths.del_mark, '!')) eq("Invalid mark name (must be file/uppercase): '!'", pcall_err(meths.del_mark, '!'))
eq(false, pcall(meths.del_mark, 'fail')) eq("Invalid mark name (must be a single char): 'fail'", pcall_err(meths.del_mark, 'fail'))
end) end)
end) end)
describe('nvim_get_mark', function() describe('nvim_get_mark', function()
@ -3048,10 +3054,10 @@ describe('API', function()
assert(string.find(mark[4], "mybuf$")) assert(string.find(mark[4], "mybuf$"))
eq({2, 2, buf.id, mark[4]}, mark) eq({2, 2, buf.id, mark[4]}, mark)
end) end)
it('fails when invalid marks are used', function() it('validation', function()
eq(false, pcall(meths.del_mark, 'f')) eq("Invalid mark name (must be file/uppercase): 'f'", pcall_err(meths.get_mark, 'f', {}))
eq(false, pcall(meths.del_mark, '!')) eq("Invalid mark name (must be file/uppercase): '!'", pcall_err(meths.get_mark, '!', {}))
eq(false, pcall(meths.del_mark, 'fail')) eq("Invalid mark name (must be a single char): 'fail'", pcall_err(meths.get_mark, 'fail', {}))
end) end)
it('returns the expected when mark is not set', function() it('returns the expected when mark is not set', function()
eq(true, meths.del_mark('A')) eq(true, meths.del_mark('A'))
@ -3113,15 +3119,15 @@ describe('API', function()
meths.eval_statusline('a%=b', { fillchar = '\031', maxwidth = 5 })) meths.eval_statusline('a%=b', { fillchar = '\031', maxwidth = 5 }))
end) end)
it('rejects multiple-character fillchar', function() it('rejects multiple-character fillchar', function()
eq('fillchar must be a single character', eq('Invalid fillchar (not a single character)',
pcall_err(meths.eval_statusline, '', { fillchar = 'aa' })) pcall_err(meths.eval_statusline, '', { fillchar = 'aa' }))
end) end)
it('rejects empty string fillchar', function() it('rejects empty string fillchar', function()
eq('fillchar must be a single character', eq('Invalid fillchar (not a single character)',
pcall_err(meths.eval_statusline, '', { fillchar = '' })) pcall_err(meths.eval_statusline, '', { fillchar = '' }))
end) end)
it('rejects non-string fillchar', function() it('rejects non-string fillchar', function()
eq('fillchar must be a single character', eq("Invalid fillchar: expected String, got Integer",
pcall_err(meths.eval_statusline, '', { fillchar = 1 })) pcall_err(meths.eval_statusline, '', { fillchar = 1 }))
end) end)
it('rejects invalid string', function() it('rejects invalid string', function()

View File

@ -1458,7 +1458,7 @@ describe('lua stdlib', function()
]] ]]
eq('', funcs.luaeval "vim.bo.filetype") eq('', funcs.luaeval "vim.bo.filetype")
eq(true, funcs.luaeval "vim.bo[BUF].modifiable") eq(true, funcs.luaeval "vim.bo[BUF].modifiable")
matches("no such option: 'nosuchopt'$", matches("Invalid option %(not found%): 'nosuchopt'$",
pcall_err(exec_lua, 'return vim.bo.nosuchopt')) pcall_err(exec_lua, 'return vim.bo.nosuchopt'))
matches("Expected lua string$", matches("Expected lua string$",
pcall_err(exec_lua, 'return vim.bo[0][0].autoread')) pcall_err(exec_lua, 'return vim.bo[0][0].autoread'))
@ -1479,7 +1479,7 @@ describe('lua stdlib', function()
eq(0, funcs.luaeval "vim.wo.cole") eq(0, funcs.luaeval "vim.wo.cole")
eq(0, funcs.luaeval "vim.wo[0].cole") eq(0, funcs.luaeval "vim.wo[0].cole")
eq(0, funcs.luaeval "vim.wo[1001].cole") eq(0, funcs.luaeval "vim.wo[1001].cole")
matches("no such option: 'notanopt'$", matches("Invalid option %(not found%): 'notanopt'$",
pcall_err(exec_lua, 'return vim.wo.notanopt')) pcall_err(exec_lua, 'return vim.wo.notanopt'))
matches("Expected lua string$", matches("Expected lua string$",
pcall_err(exec_lua, 'return vim.wo[0][0].list')) pcall_err(exec_lua, 'return vim.wo[0][0].list'))

View File

@ -751,8 +751,8 @@ describe('Buffer highlighting', function()
it('validates contents', function() it('validates contents', function()
-- this used to leak memory -- this used to leak memory
eq('Chunk is not an array', pcall_err(set_virtual_text, id1, 0, {"texty"}, {})) eq('Invalid chunk: expected Array, got String', pcall_err(set_virtual_text, id1, 0, {"texty"}, {}))
eq('Chunk is not an array', pcall_err(set_virtual_text, id1, 0, {{"very"}, "texty"}, {})) eq('Invalid chunk: expected Array, got String', pcall_err(set_virtual_text, id1, 0, {{"very"}, "texty"}, {}))
end) end)
it('can be retrieved', function() it('can be retrieved', function()