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

Leonid Vasiliev lvasiliev at tarantool.org
Tue Mar 24 15:46:00 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.

Example:

err_custom = box.error.new(box.error.CUSTOM_ERROR, "My Custom Type", "Reason")
Now:
err_custom.type == "CustomError"
err_custom.custom_type == "My Custom Type"
err_custom.message == "User custom error: Reason"

Needed for #4398
---
 src/box/errcode.h      |  1 +
 src/box/error.cc       | 56 ++++++++++++++++++++++++++++++
 src/box/error.h        | 32 ++++++++++++++++++
 src/box/lua/error.cc   | 92 ++++++++++++++++++++++++++++++++++++++------------
 src/lua/error.lua      | 25 +++++++++-----
 test/box/misc.result   | 19 +++++++++++
 test/box/misc.test.lua |  9 +++++
 7 files changed, 204 insertions(+), 30 deletions(-)

diff --git a/src/box/errcode.h b/src/box/errcode.h
index 4441717..a2f23dd 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,		"User 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 47dce33..25e7eff 100644
--- a/src/box/error.cc
+++ b/src/box/error.cc
@@ -45,6 +45,16 @@ box_error_type(const box_error_t *e)
 	return e->type->name;
 }
 
+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;
+}
+
 uint32_t
 box_error_code(const box_error_t *e)
 {
@@ -86,6 +96,17 @@ box_error_set(const char *file, unsigned line, uint32_t code,
 	return -1;
 }
 
+int
+box_custom_error_set(const char *file, unsigned line,
+		     const char *custom, const char *reason)
+{
+	struct error *e = BuildCustomError(file, line, custom);
+	strncpy(e->errmsg, reason, DIAG_ERRMSG_MAX);
+	e->errmsg[DIAG_ERRMSG_MAX - 1] = '\0';
+	diag_add_error(&fiber()->diag, e);
+	return -1;
+}
+
 /* }}} */
 
 struct rmean *rmean_error = NULL;
@@ -253,3 +274,38 @@ 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, 63);
+		m_custom_type[63] = '\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 b8c7cf7..3e0beb8 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;
@@ -70,6 +73,14 @@ const char *
 box_error_type(const box_error_t *error);
 
 /**
+ * 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);
+
+/**
  * Return IPROTO error code
  * \param error error
  * \return enum box_error_code
@@ -129,6 +140,10 @@ int
 box_error_set(const char *file, unsigned line, uint32_t code,
 	      const char *format, ...);
 
+int
+box_custom_error_set(const char *file, unsigned line,
+                     const char *custom, const char *reason);
+
 /**
  * A backward-compatible API define.
  */
@@ -140,6 +155,7 @@ box_error_set(const char *file, unsigned line, uint32_t code,
 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" */
@@ -266,6 +282,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 fc53a40..708d338 100644
--- a/src/box/lua/error.cc
+++ b/src/box/lua/error.cc
@@ -43,31 +43,35 @@ extern "C" {
 #include "box/error.h"
 
 static void
-luaT_error_create(lua_State *L, int top_base)
-{
-	uint32_t code = 0;
-	const char *reason = NULL;
-	const char *file = "";
-	unsigned line = 0;
-	lua_Debug info;
+luaT_error_read_args(lua_State *L, int top_base, uint32_t *code,
+		     const char **custom_type, const char **reason) {
 	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);
+		*code = lua_tonumber(L, top_base);
+		*reason = tnt_errcode_desc(*code);
 		if (top > top_base) {
+			int shift = 1;
+			if (*code == ER_CUSTOM_ERROR) {
+				*custom_type = lua_tostring(L, top_base + 1);
+				shift = 2;
+			}
+
 			/* Call string.format(reason, ...) to format message */
 			lua_getglobal(L, "string");
 			if (lua_isnil(L, -1))
-				goto raise;
+				return;
 			lua_getfield(L, -1, "format");
 			if (lua_isnil(L, -1))
-				goto raise;
-			lua_pushstring(L, reason);
-			for (int i = top_base + 1; i <= top; i++)
+				return;
+
+			lua_pushstring(L, *reason);
+			int nargs = 1;
+			for (int i = top_base + shift; i <= top; ++i, ++nargs)
 				lua_pushvalue(L, i);
-			lua_call(L, top - top_base + 1, 1);
-			reason = lua_tostring(L, -1);
-		} else if (strchr(reason, '%') != NULL) {
+
+			lua_call(L, nargs, 1);
+			*reason = lua_tostring(L, -1);
+		} else if (strchr(*reason, '%') != NULL) {
 			/* Missing arguments to format string */
 			luaL_error(L, "box.error(): bad arguments");
 		}
@@ -75,12 +79,15 @@ luaT_error_create(lua_State *L, int top_base)
 		if (lua_istable(L, top_base)) {
 			/* A special case that rethrows raw error (used by net.box) */
 			lua_getfield(L, top_base, "code");
-			code = lua_tonumber(L, -1);
+			*code = lua_tonumber(L, -1);
 			lua_pop(L, 1);
+			if (*code == ER_CUSTOM_ERROR) {
+				lua_getfield(L, top_base, "custom_type");
+				*custom_type = lua_tostring(L, -1);
+				lua_pop(L, 1);
+			}
 			lua_getfield(L, top_base, "reason");
-			reason = lua_tostring(L, -1);
-			if (reason == NULL)
-				reason = "";
+			*reason = lua_tostring(L, -1);
 			lua_pop(L, 1);
 		} else if (luaL_iserror(L, top_base)) {
 			lua_error(L);
@@ -90,7 +97,21 @@ luaT_error_create(lua_State *L, int top_base)
 		luaL_error(L, "box.error(): bad arguments");
 	}
 
-raise:
+	return;
+}
+
+static void
+luaT_error_create(lua_State *L, int top_base)
+{
+	uint32_t code = 0;
+	const char *custom_type = NULL;
+	const char *reason = NULL;
+
+	luaT_error_read_args(L, top_base, &code, &custom_type, &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;
@@ -101,8 +122,16 @@ raise:
 		}
 		line = info.currentline;
 	}
+
+	if (reason == NULL)
+		reason = "";
 	say_debug("box.error() at %s:%i", file, line);
-	box_error_set(file, line, code, "%s", reason);
+	if (code == ER_CUSTOM_ERROR) {
+		box_custom_error_set(file, line,
+				     custom_type ? custom_type : "", reason);
+	} else {
+		box_error_set(file, line, code, "%s", reason);
+	}
 }
 
 static int
@@ -145,6 +174,21 @@ luaT_error_new(lua_State *L)
 }
 
 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 a custom type");
+		return 1;
+	}
+
+	lua_pushstring(L, custom_type);
+	return 1;
+}
+
+static int
 luaT_error_clear(lua_State *L)
 {
 	if (lua_gettop(L) >= 1)
@@ -268,6 +312,10 @@ box_lua_error_init(struct lua_State *L) {
 			lua_pushcfunction(L, luaT_error_new);
 			lua_setfield(L, -2, "new");
 		}
+		{
+			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 765ce73..26abec8 100644
--- a/src/lua/error.lua
+++ b/src/lua/error.lua
@@ -104,6 +104,10 @@ local function error_errno(err)
     return e
 end
 
+local function error_custom_type(err)
+    return box.error.custom_type(err)
+end
+
 local error_fields = {
     ["type"]        = error_type,
     ["message"]     = error_message,
@@ -148,11 +152,16 @@ local function error_serialize(err)
     return error_message(err)
 end
 
+local function error_is_custom(err)
+    return ffi.string(err._type.name) == 'CustomError'
+end
+
 local error_methods = {
-    ["unpack"] = error_unpack;
-    ["raise"] = error_raise;
-    ["match"] = error_match; -- Tarantool 1.6 backward compatibility
-    ["__serialize"] = error_serialize;
+    ["unpack"] = error_unpack,
+    ["raise"] = error_raise,
+    ["match"] = error_match, -- Tarantool 1.6 backward compatibility
+    ["is_custom"] = error_is_custom,
+    ["__serialize"] = error_serialize
 }
 
 local function error_index(err, key)
@@ -181,11 +190,11 @@ local function error_concat(lhs, rhs)
 end
 
 local error_mt = {
-    __index = error_index;
-    __tostring = error_message;
-    __concat = error_concat;
+    __index = error_index,
+    __tostring = error_message,
+    __concat = error_concat
 };
 
-ffi.metatype('struct error', error_mt);
+ffi.metatype('struct error', error_mt)
 
 return error
diff --git a/test/box/misc.result b/test/box/misc.result
index f432d51..20d01ef 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -308,6 +308,24 @@ type(err.errno)
 ---
 - nil
 ...
+--
+-- gh-4398-expose-error-module
+--
+err_custom = box.error.new(box.error.CUSTOM_ERROR, "My Custom Type", "Reason")
+---
+...
+err_custom.type == "CustomError"
+---
+- true
+...
+err_custom.custom_type == "My Custom Type"
+---
+- true
+...
+err_custom.message == "User custom error: Reason"
+---
+- true
+...
 ----------------
 -- # box.stat
 ----------------
@@ -655,6 +673,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 ''");
 ---
diff --git a/test/box/misc.test.lua b/test/box/misc.test.lua
index e82ac2c..92b3a12 100644
--- a/test/box/misc.test.lua
+++ b/test/box/misc.test.lua
@@ -99,6 +99,15 @@ type(err.errno)
 err = box.error.new(box.error.PROC_LUA, "errno")
 type(err.errno)
 
+--
+-- gh-4398-expose-error-module
+--
+
+err_custom = box.error.new(box.error.CUSTOM_ERROR, "My Custom Type", "Reason")
+err_custom.type == "CustomError"
+err_custom.custom_type == "My Custom Type"
+err_custom.message == "User custom error: Reason"
+
 ----------------
 -- # box.stat
 ----------------
-- 
2.7.4



More information about the Tarantool-patches mailing list