[Tarantool-patches] [PATCH v2 2/5] error: Add the custom error type

Leonid Vasiliev lvasiliev at tarantool.org
Fri Apr 10 11:10:40 MSK 2020


A possibility to create an error with a custom subtype was added.

In accordance with https://github.com/tarantool/tarantool/issues/4398
a custom error type has been added to the box.error.
Now, it's possible to create an error with a custom subtype (string value)
for use it in applications.

@TarantoolBot document
Title: error.custom_type
A custom error type has been added to the box.error.
Now, it's possible to create an error with a custom subtype (string value)
for use it in applications.

To API has been added:
- For create CustomError: box.error.new(type(str), format, ...)
                          box.error.new({type = str,
                                         reason = str})

- error.type - CustomType error return custom type
    other errors return "base" error tybe
- base_type - return "base" errror type
Example:

err_custom = box.error.new("My Custom Type", "Reason")
Now:
err_custom.type == "My Custom Type"
err_custom.base_type == "CustomType"
err_custom.message == "Reason"

Needed for #4398
---
 src/box/errcode.h             |   1 +
 src/box/error.cc              |  59 +++++++++++++++++
 src/box/error.h               |  38 ++++++++++-
 src/box/lua/error.cc          | 147 ++++++++++++++++++++++++++++++------------
 src/lua/error.lua             |  11 ++++
 test/app/fiber.result         |   5 +-
 test/box/error.result         |  10 +--
 test/engine/func_index.result |  18 +++---
 8 files changed, 234 insertions(+), 55 deletions(-)

diff --git a/src/box/errcode.h b/src/box/errcode.h
index 4441717..9f5f993 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -265,6 +265,7 @@ struct errcode_record {
 	/*210 */_(ER_SQL_PREPARE,		"Failed to prepare SQL statement: %s") \
 	/*211 */_(ER_WRONG_QUERY_ID,		"Prepared statement with id %u does not exist") \
 	/*212 */_(ER_SEQUENCE_NOT_STARTED,		"Sequence '%s' is not started") \
+	/*213 */_(ER_CUSTOM_ERROR,		"%s") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/error.cc b/src/box/error.cc
index 233b312..8179e52 100644
--- a/src/box/error.cc
+++ b/src/box/error.cc
@@ -125,6 +125,31 @@ box_error_add(const char *file, unsigned line, uint32_t code,
 
 /* }}} */
 
+const char *
+box_custom_error_type(const box_error_t *e)
+{
+	CustomError *custom_error = type_cast(CustomError, e);
+	if (custom_error)
+		return custom_error->custom_type();
+
+	return NULL;
+}
+
+struct error *
+box_custom_error_new(const char *file, unsigned line,
+		     const char *custom, const char *fmt, ...)
+{
+	struct error *e = BuildCustomError(file, line, custom);
+	CustomError *custom_error = type_cast(CustomError, e);
+	if (custom_error != NULL) {
+		va_list ap;
+		va_start(ap, fmt);
+		error_vformat_msg(e, fmt, ap);
+		va_end(ap);
+	}
+	return e;
+}
+
 struct rmean *rmean_error = NULL;
 
 const char *rmean_error_strings[RMEAN_ERROR_LAST] = {
@@ -290,3 +315,37 @@ BuildAccessDeniedError(const char *file, unsigned int line,
 		return e;
 	}
 }
+
+static struct method_info customerror_methods[] = {
+	make_method(&type_CustomError, "custom_type", &CustomError::custom_type),
+	METHODS_SENTINEL
+};
+
+const struct type_info type_CustomError =
+	make_type("CustomError", &type_ClientError,
+		  customerror_methods);
+
+CustomError::CustomError(const char *file, unsigned int line,
+			 const char *custom_type)
+	:ClientError(&type_CustomError, file, line, ER_CUSTOM_ERROR)
+{
+	error_format_msg(this, tnt_errcode_desc(m_errcode),
+			 custom_type ?: "");
+
+	if (custom_type) {
+		strncpy(m_custom_type, custom_type, sizeof(m_custom_type) - 1);
+		m_custom_type[sizeof(m_custom_type) - 1] = '\0';
+	} else {
+		m_custom_type[0] = '\0';
+	}
+}
+
+struct error *
+BuildCustomError(const char *file, unsigned int line, const char *custom_type)
+{
+	try {
+		return new CustomError(file, line, custom_type);
+	} catch (OutOfMemory *e) {
+		return e;
+	}
+}
diff --git a/src/box/error.h b/src/box/error.h
index ca5d5b2..5013488 100644
--- a/src/box/error.h
+++ b/src/box/error.h
@@ -53,6 +53,9 @@ struct error *
 BuildXlogGapError(const char *file, unsigned line,
 		  const struct vclock *from, const struct vclock *to);
 
+struct error *
+BuildCustomError(const char *file, unsigned int line, const char *custom_type);
+
 /** \cond public */
 
 struct error;
@@ -138,6 +141,14 @@ box_error_set(const char *file, unsigned line, uint32_t code,
 /** \endcond public */
 
 /**
+ * Return the error custom type,
+ * \param error
+ * \return pointer to custom error type. On error, return NULL
+ */
+const char *
+box_custom_error_type(const box_error_t *e);
+
+/**
  * Add error to the diagnostic area. In contrast to box_error_set()
  * it does not replace previous error being set, but rather link
  * them into list.
@@ -155,15 +166,24 @@ box_error_add(const char *file, unsigned line, uint32_t code,
 
 /**
  * Construct error object without setting it in the diagnostics
- * area. On the memory allocation fail returns NULL.
+ * area. On the memory allocation fail returns  OutOfMemory error.
  */
 struct error *
 box_error_new(const char *file, unsigned line, uint32_t code,
 	      const char *fmt, ...);
 
+/**
+ * Construct error object without setting it in the diagnostics
+ * area. On the memory allocation fail returns OutOfMemory error.
+ */
+struct error *
+box_custom_error_new(const char *file, unsigned line,
+		     const char *custom, const char *fmt, ...);
+
 extern const struct type_info type_ClientError;
 extern const struct type_info type_XlogError;
 extern const struct type_info type_AccessDeniedError;
+extern const struct type_info type_CustomError;
 
 #if defined(__cplusplus)
 } /* extern "C" */
@@ -290,6 +310,22 @@ struct XlogGapError: public XlogError
 	virtual void raise() { throw this; }
 };
 
+class CustomError: public ClientError
+{
+public:
+	CustomError(const char *file, unsigned int line,
+		    const char *custom_type);
+
+	const char*
+	custom_type()
+	{
+		return m_custom_type;
+	}
+private:
+	/** Custom type name */
+	char m_custom_type[64];
+};
+
 #endif /* defined(__cplusplus) */
 
 #endif /* TARANTOOL_BOX_ERROR_H_INCLUDED */
diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc
index 4432bc8..1bcce1f 100644
--- a/src/box/lua/error.cc
+++ b/src/box/lua/error.cc
@@ -42,64 +42,101 @@ extern "C" {
 #include "lua/utils.h"
 #include "box/error.h"
 
-/**
- * Parse Lua arguments (they can come as single table
- * f({code : number, reason : string}) or as separate members
- * f(code, reason)) and construct struct error with given values.
- * In case one of arguments is missing its corresponding field
- * in struct error is filled with default value.
- */
-static struct error *
-luaT_error_create(lua_State *L, int top_base)
+struct error_args {
+	uint32_t code;
+	const char *reason;
+	const char *custom;
+	bool tb_mode;
+	bool tb_parsed;
+};
+
+static int
+luaT_error_parse_args(struct lua_State *L, int top_base,
+		      struct error_args *args)
 {
-	uint32_t code = 0;
-	const char *reason = NULL;
-	bool tb_parsed = false;
-	bool tb_mode = false;
-	const char *file = "";
-	unsigned line = 0;
-	lua_Debug info;
 	int top = lua_gettop(L);
-	if (top >= top_base && lua_type(L, top_base) == LUA_TNUMBER) {
-		code = lua_tonumber(L, top_base);
-		reason = tnt_errcode_desc(code);
+	int top_type = lua_type(L, top_base);
+	if (top >= top_base &&
+	    (top_type == LUA_TNUMBER || top_type == LUA_TSTRING)) {
+		if (top_type == LUA_TNUMBER) {
+			args->code = lua_tonumber(L, top_base);
+		} else if (top_type == LUA_TSTRING) {
+			args->code = ER_CUSTOM_ERROR;
+			args->custom = lua_tostring(L, top_base);
+		} else {
+			return -1;
+		}
+		args->reason = tnt_errcode_desc(args->code);
 		if (top > top_base) {
 			/* Call string.format(reason, ...) to format message */
 			lua_getglobal(L, "string");
 			if (lua_isnil(L, -1))
-				goto raise;
+				return 0;
 			lua_getfield(L, -1, "format");
 			if (lua_isnil(L, -1))
-				goto raise;
-			lua_pushstring(L, reason);
+				return 0;
+			lua_pushstring(L, args->reason);
 			for (int i = top_base + 1; i <= top; i++)
 				lua_pushvalue(L, i);
 			lua_call(L, top - top_base + 1, 1);
-			reason = lua_tostring(L, -1);
-		} else if (strchr(reason, '%') != NULL) {
+			args->reason = lua_tostring(L, -1);
+		} else if (strchr(args->reason, '%') != NULL) {
 			/* Missing arguments to format string */
-			return NULL;
+			return -1;
 		}
-	} else if (top == top_base && lua_istable(L, top_base)) {
+	} else if (top == top_base && top_type == LUA_TTABLE) {
 		lua_getfield(L, top_base, "code");
-		code = lua_tonumber(L, -1);
+		if (lua_isnil(L, -1) == 0)
+			args->code = lua_tonumber(L, -1);
 		lua_pop(L, 1);
 		lua_getfield(L, top_base, "reason");
-		reason = lua_tostring(L, -1);
-		if (reason == NULL)
-			reason = "";
+		if (lua_isnil(L, -1) == 0)
+			args->reason = lua_tostring(L, -1);
+		lua_pop(L, 1);
+		lua_getfield(L, top_base, "type");
+		if (lua_isnil(L, -1) == 0)
+			args->custom = lua_tostring(L, -1);
 		lua_pop(L, 1);
 		lua_getfield(L, top_base, "traceback");
 		if (lua_isboolean(L, -1)) {
-			tb_parsed = true;
-			tb_mode = lua_toboolean(L, -1);
+			args->tb_parsed = true;
+			args->tb_mode = lua_toboolean(L, -1);
 		}
 		lua_pop(L, -1);
 	} else {
+		return -1;
+	}
+
+	return 0;
+}
+
+/**
+ * Parse Lua arguments (they can come as single table
+ * f({code : number, reason : string}) or as separate members
+ * f(code, reason)) and construct struct error with given values.
+ * In case one of arguments is missing its corresponding field
+ * in struct error is filled with default value.
+ */
+static struct error *
+luaT_error_create(lua_State *L, int top_base)
+{
+	struct error_args args;
+	args.code = UINT32_MAX;
+	args.reason = NULL;
+	args.custom = NULL;
+	args.tb_mode = false;
+	args.tb_parsed = false;
+
+	if (luaT_error_parse_args(L, top_base, &args) != 0) {
 		return NULL;
 	}
 
-raise:
+	if (args.reason == NULL)
+		args.reason = "";
+
+	const char *file = "";
+	unsigned line = 0;
+	lua_Debug info;
 	if (lua_getstack(L, 1, &info) && lua_getinfo(L, "Sl", &info)) {
 		if (*info.short_src) {
 			file = info.short_src;
@@ -111,9 +148,16 @@ raise:
 		line = info.currentline;
 	}
 
-	struct error *err = box_error_new(file, line, code, "%s", reason);
-	if (tb_parsed)
-		err->traceback_mode = tb_mode;
+	struct error *err = NULL;
+	if (args.custom) {
+		err = box_custom_error_new(file, line, args.custom,
+					   "%s", args.reason);
+	} else {
+		err = box_error_new(file, line, args.code, "%s", args.reason);
+	}
+
+	if (args.tb_parsed)
+		err->traceback_mode = args.tb_mode;
 
 	return err;
 }
@@ -162,17 +206,36 @@ luaT_error_last(lua_State *L)
 static int
 luaT_error_new(lua_State *L)
 {
-	if (lua_gettop(L) == 0)
-		return luaL_error(L, "Usage: box.error.new(code, args)");
+	if (lua_gettop(L) == 0) {
+		return luaL_error(L, "Usage: box.error.new(code, args) or "\
+				     "box.error.new(type, args)");
+	}
 	struct error *e = luaT_error_create(L, 1);
-	if (e == NULL)
-		return luaL_error(L, "Usage: box.error.new(code, args)");
+	if (e == NULL) {
+		return luaL_error(L, "Usage: box.error.new(code, args) or "\
+				     "box.error.new(type, args)");
+	}
 	lua_settop(L, 0);
 	luaT_pusherror(L, e);
 	return 1;
 }
 
 static int
+luaT_error_custom_type(lua_State *L)
+{
+	struct error *e = luaL_checkerror(L, -1);
+
+	const char *custom_type = box_custom_error_type(e);
+	if (custom_type == NULL) {
+		lua_pushfstring(L, "The error has't custom type");
+		return 1;
+	}
+
+	lua_pushstring(L, custom_type);
+	return 1;
+}
+
+static int
 luaT_error_clear(lua_State *L)
 {
 	if (lua_gettop(L) >= 1)
@@ -328,6 +391,10 @@ box_lua_error_init(struct lua_State *L) {
 			lua_pushcfunction(L, luaT_error_cfg);
 			lua_setfield(L, -2, "cfg");
 		}
+		{
+			lua_pushcfunction(L, luaT_error_custom_type);
+			lua_setfield(L, -2, "custom_type");
+		}
 		lua_setfield(L, -2, "__index");
 	}
 	lua_setmetatable(L, -2);
diff --git a/src/lua/error.lua b/src/lua/error.lua
index 46d2866..5b32607 100644
--- a/src/lua/error.lua
+++ b/src/lua/error.lua
@@ -78,6 +78,16 @@ local function reflection_get(err, method)
 end
 
 local function error_type(err)
+    local res
+    if ffi.string(err._type.name) == 'CustomError' then
+        res = box.error.custom_type(err)
+    else
+        res = ffi.string(err._type.name)
+    end
+    return res
+end
+
+local function error_base_type(err)
     return ffi.string(err._type.name)
 end
 
@@ -141,6 +151,7 @@ local error_fields = {
     ["errno"]       = error_errno;
     ["prev"]        = error_prev;
     ["traceback"]   = error_traceback;
+    ["base_type"]    = error_base_type
 }
 
 local function error_unpack(err)
diff --git a/test/app/fiber.result b/test/app/fiber.result
index fc6b3c9..00d915c 100644
--- a/test/app/fiber.result
+++ b/test/app/fiber.result
@@ -1040,11 +1040,12 @@ e:unpack();
 ---
 - traceback: Traceback is absent
   code: 1
+  base_type: ClientError
+  type: ClientError
+  message: Illegal parameters, oh my
   trace:
   - file: '[string "function err() box.error(box.error.ILLEGAL_PA..."]'
     line: 1
-  type: ClientError
-  message: Illegal parameters, oh my
 ...
 flag = false;
 ---
diff --git a/test/box/error.result b/test/box/error.result
index 22788a1..611044b 100644
--- a/test/box/error.result
+++ b/test/box/error.result
@@ -36,11 +36,12 @@ e:unpack()
  | ---
  | - traceback: Traceback is absent
  |   code: 1
+ |   base_type: ClientError
+ |   type: ClientError
+ |   message: Illegal parameters, bla bla
  |   trace:
  |   - file: '[C]'
  |     line: 4294967295
- |   type: ClientError
- |   message: Illegal parameters, bla bla
  | ...
 e.type
  | ---
@@ -106,7 +107,7 @@ e
  | ...
 box.error.new()
  | ---
- | - error: 'Usage: box.error.new(code, args)'
+ | - error: 'Usage: box.error.new(code, args) or box.error.new(type, args)'
  | ...
 
 --
@@ -431,6 +432,7 @@ t;
  |   210: box.error.SQL_PREPARE
  |   211: box.error.WRONG_QUERY_ID
  |   212: box.error.SEQUENCE_NOT_STARTED
+ |   213: box.error.CUSTOM_ERROR
  | ...
 
 test_run:cmd("setopt delimiter ''");
@@ -489,7 +491,7 @@ assert(box.error.last() == nil)
 --
 box.error.new(err)
  | ---
- | - error: 'Usage: box.error.new(code, args)'
+ | - error: 'Usage: box.error.new(code, args) or box.error.new(type, args)'
  | ...
 
 -- box.error() is supposed to re-throw last diagnostic error.
diff --git a/test/engine/func_index.result b/test/engine/func_index.result
index 28befca..aee3ca4 100644
--- a/test/engine/func_index.result
+++ b/test/engine/func_index.result
@@ -278,14 +278,15 @@ e:unpack()
  | ---
  | - traceback: Traceback is absent
  |   code: 198
- |   trace:
- |   - file: <filename>
- |     line: <line>
+ |   base_type: ClientError
  |   type: ClientError
- |   message: 'Failed to build a key for functional index ''idx'' of space ''withdata'':
- |     can''t evaluate function'
  |   prev: '[string "return function(tuple)                 local ..."]:1: attempt to
  |     call global ''require'' (a nil value)'
+ |   message: 'Failed to build a key for functional index ''idx'' of space ''withdata'':
+ |     can''t evaluate function'
+ |   trace:
+ |   - file: <filename>
+ |     line: <line>
  | ...
 e = e.prev
  | ---
@@ -293,12 +294,13 @@ e = e.prev
 e:unpack()
  | ---
  | - traceback: Traceback is absent
- |   trace:
- |   - file: <filename>
- |     line: <line>
+ |   base_type: LuajitError
  |   type: LuajitError
  |   message: '[string "return function(tuple)                 local ..."]:1: attempt
  |     to call global ''require'' (a nil value)'
+ |   trace:
+ |   - file: <filename>
+ |     line: <line>
  | ...
 e = e.prev
  | ---
-- 
2.7.4



More information about the Tarantool-patches mailing list