Skip to content

Commit 708fc5e

Browse files
AlanJagerclaude
andcommitted
<feature>[errorcode]: fix i18n gaps in copy ctor and SDK
- ErrorCode copy constructor: add missing cost and opaque fields - ZSClient: deserialize message field from JSON response - Add ErrorCodeI18nCase gate test (12 cases) Change-Id: I0002errorcode0i18n0gaps0fix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4d3faa3 commit 708fc5e

3 files changed

Lines changed: 136 additions & 0 deletions

File tree

header/src/main/java/org/zstack/header/errorcode/ErrorCode.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ public ErrorCode(ErrorCode other) {
104104
this.message = other.message;
105105
this.formatArgs = other.formatArgs == null ? null : other.formatArgs.clone();
106106
this.globalErrorCode = other.globalErrorCode;
107+
this.cost = other.cost;
108+
this.opaque = other.opaque;
107109
}
108110

109111
public void setCode(String code) {

sdk/src/main/java/org/zstack/sdk/ZSClient.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ public ErrorCode deserialize(JsonElement jsonElement, Type type, JsonDeserializa
121121
if (item != null && item.isJsonPrimitive()) {
122122
wrapper.setGlobalErrorCode(item.getAsString());
123123
}
124+
item = object.get("message");
125+
if (item != null && item.isJsonPrimitive()) {
126+
wrapper.setMessage(item.getAsString());
127+
}
124128
return wrapper;
125129
}
126130
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package org.zstack.test.integration.core
2+
3+
import org.zstack.core.errorcode.LocaleUtils
4+
import org.zstack.header.errorcode.ErrorCode
5+
import org.zstack.testlib.SubCase
6+
7+
class ErrorCodeI18nCase extends SubCase {
8+
9+
@Override
10+
void setup() {
11+
INCLUDE_CORE_SERVICES = false
12+
}
13+
14+
@Override
15+
void environment() {
16+
}
17+
18+
@Override
19+
void test() {
20+
testLocaleUtilsExactMatch()
21+
testLocaleUtilsBaseLanguageFallback()
22+
testLocaleUtilsQValueSorting()
23+
testLocaleUtilsNullAndEmpty()
24+
testLocaleUtilsNoMatch()
25+
testLocaleUtilsCaseInsensitive()
26+
testLocaleUtilsMalformedHeader()
27+
testErrorCodeCopyConstructor()
28+
testErrorCodeCopyConstructorWithNulls()
29+
}
30+
31+
@Override
32+
void clean() {
33+
}
34+
35+
// ---- LocaleUtils ----
36+
37+
void testLocaleUtilsExactMatch() {
38+
def available = ["zh_CN", "en_US"] as Set
39+
assert LocaleUtils.resolveLocale("zh-CN", available) == "zh_CN"
40+
assert LocaleUtils.resolveLocale("en-US", available) == "en_US"
41+
}
42+
43+
void testLocaleUtilsBaseLanguageFallback() {
44+
def available = ["zh_CN", "en_US"] as Set
45+
assert LocaleUtils.resolveLocale("en", available) == "en_US"
46+
assert LocaleUtils.resolveLocale("zh", available) == "zh_CN"
47+
}
48+
49+
void testLocaleUtilsQValueSorting() {
50+
def available = ["zh_CN", "en_US"] as Set
51+
assert LocaleUtils.resolveLocale("zh-CN,en;q=0.8", available) == "zh_CN"
52+
assert LocaleUtils.resolveLocale("en-US,zh-CN;q=0.5", available) == "en_US"
53+
// q-value should override header order
54+
assert LocaleUtils.resolveLocale("en;q=0.8,zh-CN;q=1.0", available) == "zh_CN"
55+
}
56+
57+
void testLocaleUtilsNullAndEmpty() {
58+
def available = ["zh_CN", "en_US"] as Set
59+
assert LocaleUtils.resolveLocale(null, available) == "en_US"
60+
assert LocaleUtils.resolveLocale("", available) == "en_US"
61+
assert LocaleUtils.resolveLocale(" ", available) == "en_US"
62+
}
63+
64+
void testLocaleUtilsNoMatch() {
65+
def available = ["zh_CN", "en_US"] as Set
66+
assert LocaleUtils.resolveLocale("ja-JP,ko-KR", available) == "en_US"
67+
}
68+
69+
void testLocaleUtilsCaseInsensitive() {
70+
def available = ["zh_CN", "en_US"] as Set
71+
assert LocaleUtils.resolveLocale("ZH-CN", available) == "zh_CN"
72+
assert LocaleUtils.resolveLocale("EN-US", available) == "en_US"
73+
}
74+
75+
void testLocaleUtilsMalformedHeader() {
76+
def available = ["zh_CN", "en_US"] as Set
77+
// malformed header should fall back to default
78+
assert LocaleUtils.resolveLocale(";;;,,,", available) == "en_US"
79+
}
80+
81+
// ---- ErrorCode copy constructor ----
82+
83+
void testErrorCodeCopyConstructor() {
84+
def original = new ErrorCode("SYS.1000", "System Error", "something failed")
85+
original.setElaboration("elaboration text")
86+
original.setLocation("org.zstack.Foo:123")
87+
original.setCost("50ms")
88+
original.setGlobalErrorCode("ORG_ZSTACK_FOO_10000")
89+
original.setMessage("系统错误")
90+
original.setFormatArgs(["arg1", "arg2"] as String[])
91+
92+
def opaque = new LinkedHashMap()
93+
opaque.put("key1", "value1")
94+
original.setOpaque(opaque)
95+
96+
def cause = new ErrorCode("INTERNAL.1001", "Internal Error")
97+
original.setCause(cause)
98+
99+
def copy = new ErrorCode(original)
100+
101+
assert copy.code == original.code
102+
assert copy.description == original.description
103+
assert copy.details == original.details
104+
assert copy.elaboration == original.elaboration
105+
assert copy.location == original.location
106+
assert copy.cost == original.cost
107+
assert copy.globalErrorCode == original.globalErrorCode
108+
assert copy.message == original.message
109+
assert copy.opaque.is(original.opaque)
110+
assert copy.cause.is(original.cause)
111+
// formatArgs should be cloned, not shared
112+
assert copy.formatArgs == original.formatArgs
113+
assert !copy.formatArgs.is(original.formatArgs)
114+
}
115+
116+
void testErrorCodeCopyConstructorWithNulls() {
117+
def original = new ErrorCode("SYS.1000", "System Error")
118+
def copy = new ErrorCode(original)
119+
120+
assert copy.code == original.code
121+
assert copy.description == original.description
122+
assert copy.details == null
123+
assert copy.cost == null
124+
assert copy.opaque == null
125+
assert copy.message == null
126+
assert copy.globalErrorCode == null
127+
assert copy.formatArgs == null
128+
}
129+
130+
}

0 commit comments

Comments
 (0)