From ed410721a404d7ae7d51aca4402e4fbd722ed3e0 Mon Sep 17 00:00:00 2001 From: jimin Date: Wed, 21 Feb 2024 08:51:02 +0800 Subject: [PATCH] refactor: standardize Seata Exception (#6269) --- changes/en-us/2.x.md | 3 + changes/zh-cn/2.x.md | 3 + .../AbstractRemoteResourceBundle.java | 23 +++ .../seata/common/exception/ErrorCode.java | 102 +++++++++++ .../common/exception/FrameworkErrorCode.java | 1 + .../common/exception/ResourceBundleUtil.java | 172 ++++++++++++++++++ .../exception/SeataRuntimeException.java | 72 ++++++++ .../resources/error/ErrorCode_en.properties | 19 ++ .../exception/ResourceBundleUtilTest.java | 133 ++++++++++++++ .../resources/error/ErrorCode_en.properties | 23 +++ 10 files changed, 551 insertions(+) create mode 100644 common/src/main/java/org/apache/seata/common/exception/AbstractRemoteResourceBundle.java create mode 100644 common/src/main/java/org/apache/seata/common/exception/ErrorCode.java create mode 100644 common/src/main/java/org/apache/seata/common/exception/ResourceBundleUtil.java create mode 100644 common/src/main/java/org/apache/seata/common/exception/SeataRuntimeException.java create mode 100644 common/src/main/resources/error/ErrorCode_en.properties create mode 100644 common/src/test/java/org/apache/seata/common/exception/ResourceBundleUtilTest.java create mode 100644 common/src/test/resources/error/ErrorCode_en.properties diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index cbe9b989389..9e250c4f8b0 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -94,6 +94,9 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6356](https://github.com/apache/incubator-seata/pull/6356)] remove authentication from the health check page - [[#6360](https://github.com/apache/incubator-seata/pull/6360)] optimize 401 issues for some links +### refactor: +- [[#6269](https://github.com/apache/incubator-seata/pull/6269)] standardize Seata Exception + ### security: - [[#6069](https://github.com/apache/incubator-seata/pull/6069)] Upgrade Guava dependencies to fix security vulnerabilities - [[#6145](https://github.com/apache/incubator-seata/pull/6145)] upgrade jettison to 1.5.4 diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index be3c681cc82..b1c34383cf1 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -95,6 +95,9 @@ - [[#6350](https://github.com/apache/incubator-seata/pull/6350)] 移除 enableDegrade 配置 +### refactor: +- [[#6269](https://github.com/apache/incubator-seata/pull/6269)] 统一Seata异常规范 + ### security: - [[#6069](https://github.com/apache/incubator-seata/pull/6069)] 升级Guava依赖版本,修复安全漏洞 - [[#6144](https://github.com/apache/incubator-seata/pull/6144)] 升级Nacos依赖版本至1.4.6 diff --git a/common/src/main/java/org/apache/seata/common/exception/AbstractRemoteResourceBundle.java b/common/src/main/java/org/apache/seata/common/exception/AbstractRemoteResourceBundle.java new file mode 100644 index 00000000000..3abb5d63be4 --- /dev/null +++ b/common/src/main/java/org/apache/seata/common/exception/AbstractRemoteResourceBundle.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.common.exception; + +import java.util.ResourceBundle; + +public abstract class AbstractRemoteResourceBundle extends ResourceBundle { + +} diff --git a/common/src/main/java/org/apache/seata/common/exception/ErrorCode.java b/common/src/main/java/org/apache/seata/common/exception/ErrorCode.java new file mode 100644 index 00000000000..72d454d0783 --- /dev/null +++ b/common/src/main/java/org/apache/seata/common/exception/ErrorCode.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.common.exception; + +/** + * The enum Error code. + */ +public enum ErrorCode { + + /** + * 0001 ~ 0099 Configuration related errors + */ + ERR_CONFIG(ErrorType.Config, 0001); + /** + * The error code of the transaction exception. + */ + + private int code; + private ErrorType type; + + ErrorCode(ErrorType type, int code) { + this.code = code; + this.type = type; + } + + /** + * Gets code. + * + * @return the code + */ + public int getCode() { + return code; + } + + /** + * Gets type. + * + * @return the type + */ + public String getType() { + return type.name(); + } + + /** + * Gets message. + * + * @param params the params + * @return the message + */ + public String getMessage(String... params) { + return ResourceBundleUtil.getInstance().getMessage(this.name(), this.getCode(), this.getType(), params); + } + + /** + * The enum Error type. + */ + enum ErrorType { + /** + * Config error type. + */ + Config, + /** + * Network error type. + */ + Network, + /** + * Tm error type. + */ + TM, + /** + * Rm error type. + */ + RM, + /** + * Tc error type. + */ + TC, + /** + * Datasource error type. + */ + Datasource, + /** + * Other error type. + */ + Other; + } + +} diff --git a/common/src/main/java/org/apache/seata/common/exception/FrameworkErrorCode.java b/common/src/main/java/org/apache/seata/common/exception/FrameworkErrorCode.java index 87e3f1986ea..0c75c2f2f66 100644 --- a/common/src/main/java/org/apache/seata/common/exception/FrameworkErrorCode.java +++ b/common/src/main/java/org/apache/seata/common/exception/FrameworkErrorCode.java @@ -20,6 +20,7 @@ * The enum Framework error code. * */ +@Deprecated public enum FrameworkErrorCode { /** * 0001 ~ 0099 Configuration related errors diff --git a/common/src/main/java/org/apache/seata/common/exception/ResourceBundleUtil.java b/common/src/main/java/org/apache/seata/common/exception/ResourceBundleUtil.java new file mode 100644 index 00000000000..a7e2248b48d --- /dev/null +++ b/common/src/main/java/org/apache/seata/common/exception/ResourceBundleUtil.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.common.exception; + +import java.text.MessageFormat; +import java.util.HashSet; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.apache.seata.common.loader.EnhancedServiceLoader; + +public class ResourceBundleUtil { + + private static final Locale LOCALE = new Locale("en", "US"); + private static final ResourceBundleUtil INSTANCE = new ResourceBundleUtil("error/ErrorCode", LOCALE); + private ResourceBundle localBundle; + private AbstractRemoteResourceBundle remoteBundle; + + public static final String DEFAULT_PLACEHOLDER_PREFIX = "${"; + public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}"; + + public static ResourceBundleUtil getInstance() { + return INSTANCE; + } + + public ResourceBundleUtil(String bundleName, Locale local) { + + this.localBundle = ResourceBundle.getBundle(bundleName, local); + try { + this.remoteBundle = EnhancedServiceLoader.load(AbstractRemoteResourceBundle.class); + } catch (Throwable e) { + //ignore + } + } + + public String getMessage(String key, String... params) { + if (StringUtils.isBlank(key)) { + return null; + } + StringBuilder sb = new StringBuilder(); + sb.append(getFormattedMessage(key)); + String msg = parseStringValue(sb.toString(), new HashSet()); + if (params == null || params.length == 0) { + return msg; + } + if (StringUtils.isBlank(msg)) { + return msg; + } + return MessageFormat.format(msg, (Object[])params); + } + + public String getMessage(String key, int code, String type, String... params) { + if (StringUtils.isBlank(key)) { + return null; + } + StringBuilder sb = new StringBuilder(); + + sb.append(getFormattedMessage("ERR_PREFIX")).append(" ").append(getFormattedMessage(key)).append(" ").append( + getFormattedMessage("ERR_POSTFIX")); + String msg = sb.toString(); + msg = parseStringValue(msg, new HashSet()); + msg = StringUtils.replace(msg, "{code}", String.valueOf(code)); + msg = StringUtils.replace(msg, "{type}", String.valueOf(type)); + msg = StringUtils.replace(msg, "{key}", key); + + if (params == null || params.length == 0) { + return msg; + } + if (StringUtils.isBlank(msg)) { + return msg; + } + return MessageFormat.format(msg, (Object[])params); + } + + protected String getFormattedMessage(String key) { + String value = StringUtils.EMPTY; + if (remoteBundle != null && remoteBundle.containsKey(key)) { + value = remoteBundle.getString(key); + } + if (StringUtils.isEmpty(value)) { + value = localBundle.getString(key); + } + return value; + } + + protected String parseStringValue(String strVal, Set visitedPlaceholders) { + StringBuffer buf = new StringBuffer(strVal); + int startIndex = strVal.indexOf(DEFAULT_PLACEHOLDER_PREFIX); + while (startIndex != -1) { + int endIndex = findPlaceholderEndIndex(buf, startIndex); + if (endIndex != -1) { + String placeholder = buf.substring(startIndex + DEFAULT_PLACEHOLDER_PREFIX.length(), endIndex); + if (!visitedPlaceholders.add(placeholder)) { + throw new SeataRuntimeException(ErrorCode.ERR_CONFIG, + "Duplicate placeholders exist '" + placeholder + "' in bundle."); + } + placeholder = parseStringValue(placeholder, visitedPlaceholders); + try { + String propVal = resolvePlaceholder(placeholder); + if (propVal != null) { + propVal = parseStringValue(propVal, visitedPlaceholders); + buf.replace(startIndex, endIndex + DEFAULT_PLACEHOLDER_SUFFIX.length(), propVal); + startIndex = buf.indexOf(DEFAULT_PLACEHOLDER_PREFIX, startIndex + propVal.length()); + } else { + throw new SeataRuntimeException(ErrorCode.ERR_CONFIG, + "Could not resolve placeholder '" + placeholder + "'"); + } + } catch (Exception ex) { + throw new SeataRuntimeException(ErrorCode.ERR_CONFIG, + "Could not resolve placeholder '" + placeholder + "'"); + } + visitedPlaceholders.remove(placeholder); + } else { + startIndex = -1; + } + } + + return buf.toString(); + } + + private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { + int index = startIndex + DEFAULT_PLACEHOLDER_PREFIX.length(); + int withinNestedPlaceholder = 0; + while (index < buf.length()) { + if (matchSubString(buf, index, DEFAULT_PLACEHOLDER_SUFFIX)) { + if (withinNestedPlaceholder > 0) { + withinNestedPlaceholder--; + index = index + DEFAULT_PLACEHOLDER_SUFFIX.length(); + } else { + return index; + } + } else if (matchSubString(buf, index, DEFAULT_PLACEHOLDER_PREFIX)) { + withinNestedPlaceholder++; + index = index + DEFAULT_PLACEHOLDER_PREFIX.length(); + } else { + index++; + } + } + return -1; + } + + private boolean matchSubString(CharSequence str, int index, CharSequence substring) { + for (int j = 0; j < substring.length(); j++) { + int i = index + j; + if (i >= str.length() || str.charAt(i) != substring.charAt(j)) { + return false; + } + } + return true; + } + + private String resolvePlaceholder(String placeholder) { + return getFormattedMessage(placeholder); + } + +} diff --git a/common/src/main/java/org/apache/seata/common/exception/SeataRuntimeException.java b/common/src/main/java/org/apache/seata/common/exception/SeataRuntimeException.java new file mode 100644 index 00000000000..908ca3e93a1 --- /dev/null +++ b/common/src/main/java/org/apache/seata/common/exception/SeataRuntimeException.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.common.exception; + +import java.sql.SQLException; + +public class SeataRuntimeException extends RuntimeException { + private int vendorCode; + private String sqlState; + public SeataRuntimeException(ErrorCode errorCode, String... params) { + super(errorCode.getMessage(params)); + this.vendorCode = errorCode.getCode(); + } + + public SeataRuntimeException(ErrorCode errorCode, Throwable cause, String... params) { + super(errorCode.getMessage(params), cause); + buildSQLMessage(cause); + } + + @Override + public String toString() { + return super.getLocalizedMessage(); + } + + @Override + public String getMessage() { + if (super.getMessage() != null) { + return super.getMessage(); + } else if (getCause() != null) { + Throwable ca = getCause(); + if (ca != null) { + return ca.getMessage(); + } else { + return null; + } + } else { + return null; + } + } + + private void buildSQLMessage(Throwable e) { + if (e instanceof SQLException) { + this.vendorCode = ((SQLException) e).getErrorCode(); + this.sqlState = ((SQLException) e).getSQLState(); + } else if (e instanceof SeataRuntimeException) { + this.vendorCode = ((SeataRuntimeException) e).getVendorCode(); + this.sqlState = ((SeataRuntimeException) e).getSqlState(); + } + } + + public int getVendorCode() { + return vendorCode; + } + + public String getSqlState() { + return sqlState; + } +} diff --git a/common/src/main/resources/error/ErrorCode_en.properties b/common/src/main/resources/error/ErrorCode_en.properties new file mode 100644 index 00000000000..fd59148996f --- /dev/null +++ b/common/src/main/resources/error/ErrorCode_en.properties @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +ERR_PREFIX=ERR-CODE: [Seata-{code}][{key}] +ERR_POSTFIX=More: [https://seata.apache.org/docs/next/overview/faq#{code}] +ERR_CONFIG=config error, {0} \ No newline at end of file diff --git a/common/src/test/java/org/apache/seata/common/exception/ResourceBundleUtilTest.java b/common/src/test/java/org/apache/seata/common/exception/ResourceBundleUtilTest.java new file mode 100644 index 00000000000..918ac5f85e4 --- /dev/null +++ b/common/src/test/java/org/apache/seata/common/exception/ResourceBundleUtilTest.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.common.exception; + +import java.util.HashSet; +import java.util.MissingResourceException; +import java.util.Set; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +class ResourceBundleUtilTest { + + @Test + void getInstance() { + Assertions.assertNotNull(ResourceBundleUtil.getInstance()); + } + + @Test + void getMessage() { + ResourceBundleUtil resourceBundleUtil = ResourceBundleUtil.getInstance(); + String emptyKeyMsg = resourceBundleUtil.getMessage("", "param1"); + Assertions.assertNull(emptyKeyMsg); + Assertions.assertThrows(MissingResourceException.class, new Executable() { + @Override + public void execute() throws Throwable { + resourceBundleUtil.getMessage("NotExist"); + } + }); + String configErrorMsgWithoutParams = resourceBundleUtil.getMessage("ERR_CONFIG"); + Assertions.assertEquals("config error, {0}", configErrorMsgWithoutParams); + + String configErrorMsgWithParams = resourceBundleUtil.getMessage("ERR_CONFIG", "vgroup_mapping_test"); + Assertions.assertEquals("config error, vgroup_mapping_test", configErrorMsgWithParams); + } + + @Test + void testGetMessage() { + ResourceBundleUtil resourceBundleUtil = ResourceBundleUtil.getInstance(); + String emptyKeyMsg = resourceBundleUtil.getMessage("", ErrorCode.ERR_CONFIG.getCode(), + ErrorCode.ERR_CONFIG.getType()); + Assertions.assertNull(emptyKeyMsg); + String errorConfigMsg = resourceBundleUtil.getMessage(ErrorCode.ERR_CONFIG.name(), + ErrorCode.ERR_CONFIG.getCode(), ErrorCode.ERR_CONFIG.getType()); + Assertions.assertEquals("ERR-CODE: [Seata-1][ERR_CONFIG] config error, {0} More: [https://seata.apache" + + ".org/docs/next/overview/faq#1]", errorConfigMsg); + String errorConfigMsgWithParams = resourceBundleUtil.getMessage(ErrorCode.ERR_CONFIG.name(), + ErrorCode.ERR_CONFIG.getCode(), ErrorCode.ERR_CONFIG.getType(), "vgroup_mapping_test"); + Assertions.assertEquals( + "ERR-CODE: [Seata-1][ERR_CONFIG] config error, vgroup_mapping_test More: [https://seata.apache" + + ".org/docs/next/overview/faq#1]", errorConfigMsgWithParams); + } + + @Test + void getFormattedMessage() { + ResourceBundleUtil resourceBundleUtil = ResourceBundleUtil.getInstance(); + Assertions.assertThrows(MissingResourceException.class, new Executable() { + @Override + public void execute() throws Throwable { + resourceBundleUtil.getFormattedMessage("NotExist"); + } + }); + String configErrorMsg = resourceBundleUtil.getFormattedMessage("ERR_CONFIG"); + Assertions.assertEquals("config error, {0}", configErrorMsg); + } + + @Test + void parseStringValue() { + ResourceBundleUtil resourceBundleUtil = ResourceBundleUtil.getInstance(); + String strVal = "str val without placeholder"; + String parseValue = resourceBundleUtil.parseStringValue(strVal, new HashSet<>()); + Assertions.assertEquals(strVal, parseValue); + strVal = "str val without placeholder ${"; + parseValue = resourceBundleUtil.parseStringValue(strVal, new HashSet<>()); + Assertions.assertEquals(strVal, parseValue); + strVal = "str val without placeholder }"; + parseValue = resourceBundleUtil.parseStringValue(strVal, new HashSet<>()); + Assertions.assertEquals(strVal, parseValue); + + final String strValWithEmptyPlaceHolder = "str val with placeholder ${}"; + Assertions.assertThrows(SeataRuntimeException.class, new Executable() { + @Override + public void execute() throws Throwable { + resourceBundleUtil.parseStringValue(strValWithEmptyPlaceHolder, new HashSet<>()); + } + }, "Could not resolve placeholder 'str val with placeholder ${}'"); + String strValWithPlaceHolder = "str val with placeholder ${ERR_CONFIG}"; + Set holderSet = new HashSet<>(); + parseValue = resourceBundleUtil.parseStringValue(strValWithPlaceHolder, holderSet); + Assertions.assertEquals("str val with placeholder config error, {0}", parseValue); + Assertions.assertEquals(0, holderSet.size()); + + String multiSamePlaceHolder = "str val with placeholder ${ERR_CONFIG},${ERR_CONFIG}"; + parseValue = resourceBundleUtil.parseStringValue(multiSamePlaceHolder, holderSet); + Assertions.assertEquals("str val with placeholder config error, {0},config error, {0}", parseValue); + + final String strValWithEmptyPlaceHolderValue = "str val with placeholder ${ERR_NOT_EXIST}"; + Assertions.assertDoesNotThrow(new Executable() { + @Override + public void execute() throws Throwable { + Set placeholderSet = new HashSet<>(); + resourceBundleUtil.parseStringValue(strValWithEmptyPlaceHolderValue, placeholderSet); + Assertions.assertEquals(0, placeholderSet.size()); + } + }); + + final String strValWithNestPlaceHolderValue = "str val with placeholder ${${${ERROR_LOOP}}}"; + Set placeholderSet = new HashSet<>(); + parseValue = resourceBundleUtil.parseStringValue(strValWithNestPlaceHolderValue, new HashSet<>()); + Assertions.assertEquals("str val with placeholder ERROR_LOOP", parseValue); + Assertions.assertEquals(0, placeholderSet.size()); + + String strValWithNestPlaceHolder = "str val with placeholder ${${ERR_NEST2}}"; + parseValue = resourceBundleUtil.parseStringValue(strValWithNestPlaceHolder, new HashSet<>()); + Assertions.assertEquals("str val with placeholder ERR NEST TEST", parseValue); + Assertions.assertEquals(0, placeholderSet.size()); + } +} \ No newline at end of file diff --git a/common/src/test/resources/error/ErrorCode_en.properties b/common/src/test/resources/error/ErrorCode_en.properties new file mode 100644 index 00000000000..65a0322edb3 --- /dev/null +++ b/common/src/test/resources/error/ErrorCode_en.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +ERR_PREFIX=ERR-CODE: [Seata-{code}][{key}] +ERR_POSTFIX=More: [https://seata.apache.org/docs/next/overview/faq#{code}] +ERR_CONFIG=config error, {0} +ERR_NOT_EXIST= +ERROR_LOOP=ERROR_LOOP +ERR_NEST1=ERR NEST TEST +ERR_NEST2=ERR_NEST1 \ No newline at end of file