api: api_info()['version']

API level is disconnected from NVIM version. The API metadata holds the
current API level, and the lowest backwards-compatible level supported
by this instance.

Release 0.1.6 is the first release that reports the Nvim version and API
level.

    metadata['version'] = {
      major: 0,
      minor: 1,
      patch: 6,
      api_level: 1,
      api_compatible: 0,
      api_prerelease: false,
    }

The API level may remain unchanged across Nvim releases if the API has
not changed.

When changing the API,
    - set NVIM_API_PRERELEASE to true
    - increment NVIM_API_LEVEL (at most once per Nvim version)
    - adjust NVIM_API_LEVEL_COMPAT if backwards-compatibility was broken

api_level_0.mpack was generated from Nvim 0.1.5 with:
    nvim --api-info
This commit is contained in:
Justin M. Keyes 2016-10-26 15:20:00 +02:00
parent f25797f869
commit c5f5f427c6
11 changed files with 100 additions and 95 deletions

View File

@ -59,16 +59,15 @@ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY
STRINGS "Debug" "Dev" "Release" "MinSizeRel" "RelWithDebInfo")
# If not in a git repo (e.g., a tarball) these tokens define the complete
# version string, else it is combined with the result of `git describe`.
# version string, else they are combined with the result of `git describe`.
set(NVIM_VERSION_MAJOR 0)
set(NVIM_VERSION_MINOR 1)
set(NVIM_VERSION_PATCH 6)
set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers
# Neovim API version. When changing the API, bump CURRENT if
# PRERELEASE is false, and set PRERELEASE as true
set(NVIM_API_CURRENT 1)
set(NVIM_API_COMPATIBILITY 0)
# API level
set(NVIM_API_LEVEL 1) # Bump this after any API change.
set(NVIM_API_LEVEL_COMPAT 0) # Adjust this after a _breaking_ API change.
set(NVIM_API_PRERELEASE true)
file(TO_CMAKE_PATH ${CMAKE_CURRENT_LIST_DIR}/.git FORCED_GIT_DIR)

View File

@ -7,9 +7,9 @@
#define NVIM_VERSION_PRERELEASE "@NVIM_VERSION_PRERELEASE@"
#cmakedefine NVIM_VERSION_MEDIUM "@NVIM_VERSION_MEDIUM@"
#define NVIM_API_CURRENT @NVIM_API_CURRENT@
#define NVIM_API_COMPATIBILITY @NVIM_API_COMPATIBILITY@
#cmakedefine NVIM_API_PRERELEASE
#define NVIM_API_LEVEL @NVIM_API_LEVEL@
#define NVIM_API_LEVEL_COMPAT @NVIM_API_LEVEL_COMPAT@
#define NVIM_API_PRERELEASE @NVIM_API_PRERELEASE@
#define NVIM_VERSION_CFLAGS "@NVIM_VERSION_CFLAGS@"
#define NVIM_VERSION_BUILD_TYPE "@NVIM_VERSION_BUILD_TYPE@"

View File

@ -51,10 +51,10 @@ Tabpage -> enum value kObjectTypeTabpage
Nvim exposes metadata about the API as a Dictionary with the following keys:
api_level API version compatibility information
functions calling signature of the API functions
types The custom handle types defined by Nvim
error_types The possible kinds of errors an API function can exit with.
version Nvim version, API level/compatibility
functions API function signatures
types Custom handle types defined by Nvim
error_types Possible error types returned by API functions
This metadata is mostly useful for external programs accessing the API via
RPC, see |rpc-api|.

View File

@ -168,9 +168,8 @@ API metadata object ~
API clients exist to hide msgpack-rpc details. The API metadata object
contains information that makes this task easier (see also |rpc-types|):
- The "api_level" key contais API compatibility information. The "current"
key holds the API version supported Neovim. The "compatibility" key holds
the oldest supported API version.
- The "version" key contains the Nvim version, API level, and API
backwards-compatibility level.
- The "functions" key contains a list of metadata objects for individual
functions.
- Each function metadata object has |rpc-types| information about the return

View File

@ -18,9 +18,9 @@
#include "nvim/map.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/version.h"
#include "nvim/eval/typval_encode.h"
#include "nvim/lib/kvec.h"
#include "auto/versiondef.h"
/// Helper structure for vim_to_object
typedef struct {
@ -764,10 +764,10 @@ Dictionary api_metadata(void)
static Dictionary metadata = ARRAY_DICT_INIT;
if (!metadata.size) {
PUT(metadata, "version", DICTIONARY_OBJ(version_dict()));
init_function_metadata(&metadata);
init_error_type_metadata(&metadata);
init_type_metadata(&metadata);
init_api_level_metadata(&metadata);
}
return copy_object(DICTIONARY_OBJ(metadata)).data.dictionary;
@ -827,17 +827,6 @@ static void init_type_metadata(Dictionary *metadata)
PUT(*metadata, "types", DICTIONARY_OBJ(types));
}
static void init_api_level_metadata(Dictionary *metadata)
{
Dictionary version = ARRAY_DICT_INIT;
PUT(version, "current", INTEGER_OBJ(NVIM_API_CURRENT));
PUT(version, "compatibility", INTEGER_OBJ(NVIM_API_COMPATIBILITY));
#ifdef NVIM_API_PRERELEASE
PUT(version, "prerelease", BOOLEAN_OBJ(true));
#endif
PUT(*metadata, "api_level", DICTIONARY_OBJ(version));
}
/// Creates a deep clone of an object
Object copy_object(Object obj)

View File

@ -7,6 +7,7 @@
#include <assert.h>
#include <limits.h>
#include "nvim/api/private/helpers.h"
#include "nvim/vim.h"
#include "nvim/ascii.h"
#include "nvim/iconv.h"
@ -2514,6 +2515,17 @@ bool has_vim_patch(int n)
return false;
}
Dictionary version_dict(void) {
Dictionary d = ARRAY_DICT_INIT;
PUT(d, "major", INTEGER_OBJ(NVIM_VERSION_MAJOR));
PUT(d, "minor", INTEGER_OBJ(NVIM_VERSION_MINOR));
PUT(d, "patch", INTEGER_OBJ(NVIM_VERSION_PATCH));
PUT(d, "api_level", INTEGER_OBJ(NVIM_API_LEVEL));
PUT(d, "api_compatible", INTEGER_OBJ(NVIM_API_LEVEL_COMPAT));
PUT(d, "api_prerelease", BOOLEAN_OBJ(NVIM_API_PRERELEASE));
return d;
}
void ex_version(exarg_T *eap)
{
// Ignore a ":version 9.99" command.

View File

@ -1,65 +0,0 @@
local helpers = require('test.functional.helpers')(after_each)
local lfs = require('lfs')
local mpack = require('mpack')
local clear, eq, neq = helpers.clear, helpers.eq, helpers.neq
local read_mpack_file = function(fname)
local fd = io.open(fname, 'rb')
local data = fd:read('*a')
fd:close()
local unpack = mpack.Unpacker()
return unpack(data)
end
-- ignore metadata in API function spec
local remove_function_metadata = function(fspec)
fspec['can_fail'] = nil
fspec['async'] = nil
fspec['method'] = nil
fspec['since'] = nil
fspec['deprecated_since'] = nil
fspec['receives_channel_id'] = nil
for idx,_ in ipairs(fspec['parameters']) do
fspec['parameters'][idx][2] = ''
end
end
clear()
local api_level = helpers.call('api_info')['api_level']
describe('api compatibility', function()
before_each(clear)
it("version metadata is sane", function()
local info = helpers.call('api_info')
local current = info['api_level']['current']
local compatibility = info['api_level']['compatibility']
neq(current, nil)
neq(compatibility, nil)
assert(current >= compatibility)
end)
for ver = api_level['compatibility'], api_level['current'] do
local path = 'test/functional/fixtures/api-info/' .. tostring(ver) .. '.mpack'
it('are backwards compatible with api level '..ver, function()
if lfs.attributes(path,"mode") ~= "file" then
pending("No fixture found, skipping test")
return
end
local old_api = read_mpack_file(path)
local api = helpers.call('api_info')
for _, fspec in ipairs(old_api['functions']) do
remove_function_metadata(fspec)
for _, fspec_new in ipairs(api['functions']) do
if fspec['name'] == fspec_new['name'] then
remove_function_metadata(fspec_new)
eq(fspec, fspec_new)
end
end
end
end)
end
end)

View File

@ -0,0 +1,71 @@
local helpers = require('test.functional.helpers')(after_each)
local mpack = require('mpack')
local clear, funcs, eq = helpers.clear, helpers.funcs, helpers.eq
local function read_mpack_file(fname)
local fd = io.open(fname, 'rb')
local data = fd:read('*a')
fd:close()
local unpack = mpack.Unpacker()
return unpack(data)
end
-- Remove metadata that is not essential to backwards-compatibility.
local function remove_function_metadata(fspec)
fspec['can_fail'] = nil
fspec['async'] = nil
fspec['method'] = nil
fspec['since'] = nil
fspec['deprecated_since'] = nil
fspec['receives_channel_id'] = nil
for idx, _ in ipairs(fspec['parameters']) do
fspec['parameters'][idx][2] = '' -- Remove parameter name.
end
return fspec
end
describe("api_info()['version']", function()
before_each(clear)
it("returns API level", function()
local version = helpers.call('api_info')['version']
local current = version['api_level']
local compat = version['api_compatible']
eq("number", type(current))
eq("number", type(compat))
assert(current >= compat)
end)
it("returns Nvim version", function()
local version = helpers.call('api_info')['version']
local major = version['major']
local minor = version['minor']
local patch = version['patch']
eq("number", type(major))
eq("number", type(minor))
eq("number", type(patch))
eq(1, funcs.has("nvim-"..major.."."..minor.."."..patch))
eq(0, funcs.has("nvim-"..major.."."..minor.."."..(patch + 1)))
eq(0, funcs.has("nvim-"..major.."."..(minor + 1).."."..patch))
eq(0, funcs.has("nvim-"..(major + 1).."."..minor.."."..patch))
end)
it("api_compatible level is valid", function()
local api = helpers.call('api_info')
local compat = api['version']['api_compatible']
local path = 'test/functional/fixtures/api_level_'
..tostring(compat)..'.mpack'
-- Verify that the current API function signatures match those of the API
-- level for which we claim compatibility.
local old_api = read_mpack_file(path)
for _, fn_old in ipairs(old_api['functions']) do
for _, fn_new in ipairs(api['functions']) do
if fn_old['name'] == fn_new['name'] then
eq(remove_function_metadata(fn_old),
remove_function_metadata(fn_new))
end
end
end
end)
end)

View File

@ -106,7 +106,7 @@ describe('api functions', function()
it('have metadata accessible with api_info()', function()
local api_keys = eval("sort(keys(api_info()))")
eq({'api_level', 'error_types', 'functions', 'types'}, api_keys)
eq({'error_types', 'functions', 'types', 'version'}, api_keys)
end)
it('are highlighted by vim.vim syntax file', function()

View File

@ -460,7 +460,7 @@ describe('msgpackparse() function', function()
eval(cmd)
eval(cmd) -- do it again (try to force segfault)
local api_info = eval(cmd) -- do it again
eq({'api_level', 'error_types', 'functions', 'types'}, api_info)
eq({'error_types', 'functions', 'types', 'version'}, api_info)
end)
it('fails when called with no arguments', function()