Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
* This file is a part of the ModelEngine Project.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

package modelengine.fitframework.validation;

import jakarta.validation.MessageInterpolator;
import modelengine.fitframework.inspection.Validation;
import modelengine.fitframework.util.ObjectUtils;
import modelengine.fitframework.util.i18n.LocaleContextHolder;

import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator;

import java.util.Locale;

/**
* 检验消息处理的代理类。
* <p>
* 从 {@link LocaleContextHolder} 中获取当前线程设置的 {@link Locale} 并委托 {@link MessageInterpolator} 去处理消息。
* </p>
*
* @author 阮睿
* @since 2025-07-31
*/
public class LocaleContextMessageInterpolator implements MessageInterpolator {
private final MessageInterpolator targetInterpolator;
private Locale locale;

/**
* 构造函数。
*
* @param targetInterpolator 表示目标检验消息处理对象的 {@link MessageInterpolator}。
*/
public LocaleContextMessageInterpolator(MessageInterpolator targetInterpolator) {
this.targetInterpolator = targetInterpolator;
this.locale = Locale.getDefault();
}

/**
* 构造函数,默认使用 {@link ParameterMessageInterpolator} 作为目标检验消息处理对象。
*/
public LocaleContextMessageInterpolator() {
this.targetInterpolator = new ParameterMessageInterpolator();
this.locale = Locale.getDefault();
}

/**
* 构造函数。
*
* @param locale 表示当前设置默认的 {@link Locale}。
*/
public LocaleContextMessageInterpolator(Locale locale) {
this.targetInterpolator = new ParameterMessageInterpolator();
this.locale = ObjectUtils.getIfNull(locale, Locale::getDefault);
}

/**
* 构造函数。
*
* @param targetInterpolator 表示目标检验消息处理对象的 {@link MessageInterpolator}。
* @param locale 表示当前设置默认的 {@link Locale}。
*/
public LocaleContextMessageInterpolator(MessageInterpolator targetInterpolator, Locale locale) {
this.targetInterpolator = targetInterpolator;
this.locale = ObjectUtils.getIfNull(locale, Locale::getDefault);
}

/**
* 设置默认的 {@link Locale}。
*
* @param locale 默认设置的 {@link Locale}。
*/
public void setLocale(Locale locale) {
this.locale = ObjectUtils.getIfNull(locale, Locale::getDefault);
}

@Override
public String interpolate(String messageTemplate, Context context) {
if (LocaleContextHolder.getLocale() != null) {
return this.targetInterpolator.interpolate(messageTemplate, context, LocaleContextHolder.getLocale());
}
return this.targetInterpolator.interpolate(messageTemplate, context, this.locale);
}

@Override
public String interpolate(String messageTemplate, Context context, Locale locale) {
Validation.notNull(locale, "Locale cannot be null.");
return this.targetInterpolator.interpolate(messageTemplate, context, locale);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@
public class ValidationHandler implements AutoCloseable {
private final ValidatorFactory validatorFactory;
private final Validator validator;
private final LocaleMessageInterpolator messageInterpolator;
private final LocaleContextMessageInterpolator messageInterpolator;

public ValidationHandler() {
this.messageInterpolator = new LocaleMessageInterpolator();
this.messageInterpolator = new LocaleContextMessageInterpolator();
this.validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.messageInterpolator(this.messageInterpolator)
Expand All @@ -58,7 +58,7 @@ public ValidationHandler() {
/**
* 设置校验信息语言。
*
* @param locale 校验语言 {@link Locale}。
* @param locale 表示校验语言的 {@link Locale}。
*/
public void setLocale(Locale locale) {
this.messageInterpolator.setLocale(locale);
Expand Down Expand Up @@ -163,4 +163,4 @@ private boolean isJakartaConstraintAnnotation(Annotation annotation) {
return "jakarta.validation".equals(packageName) && "Constraint".equals(className);
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved.
* This file is a part of the ModelEngine Project.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

package modelengine.fitframework.validation;

import static org.assertj.core.api.Assertions.assertThat;

import modelengine.fit.http.client.HttpClassicClientResponse;
import modelengine.fit.http.entity.Entity;
import modelengine.fit.http.entity.ObjectEntity;
import modelengine.fitframework.annotation.Fit;
import modelengine.fitframework.test.annotation.MvcTest;
import modelengine.fitframework.test.domain.mvc.MockMvc;
import modelengine.fitframework.test.domain.mvc.request.MockMvcRequestBuilders;
import modelengine.fitframework.test.domain.mvc.request.MockRequestBuilder;
import modelengine.fitframework.validation.data.Company;
import modelengine.fitframework.validation.data.LocaleValidationController;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.Map;

/**
* 表示评估国际化校验的测试类。
*
* @author 阮睿
* @since 2025-08-01
*/
@MvcTest(classes = {LocaleValidationController.class})
@DisplayName("测试地区化验证消息功能")
public class LocaleValidationControllerTest {
@Fit
private MockMvc mockMvc;

private HttpClassicClientResponse<?> response;

@AfterEach
void teardown() throws IOException {
if (this.response != null) {
this.response.close();
}
}

@Test
@DisplayName("测试法文地区的验证消息")
void shouldReturnFrenchValidationMessage() {
Company invalidCompany = new Company(null);

MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/locale/simple")
.header("Accept-Language", "fr")
.jsonEntity(invalidCompany)
.responseType(Map.class);

this.response = this.mockMvc.perform(requestBuilder);
// 获取JSON格式的错误信息
String errorMessage = "";
if (this.response.entity().isPresent()) {
Entity entity = this.response.entity().get();
if (entity instanceof ObjectEntity) {
ObjectEntity<?> objectEntity = (ObjectEntity<?>) entity;
Object errorObj = objectEntity.object();
if (errorObj instanceof Map) {
Map<String, Object> errorMap = (Map<String, Object>) errorObj;
errorMessage =
errorMap.get("error") != null ? errorMap.get("error").toString() : errorMap.toString();
} else {
errorMessage = errorObj.toString();
}
}
}

assertThat(errorMessage).isEqualTo("validateSimpleParam.company.employees: ne doit pas être nul");
assertThat(this.response.statusCode()).isEqualTo(500);
}

@Test
@DisplayName("测试英文地区的验证消息")
void shouldReturnEnglishValidationMessage() {
Company invalidCompany = new Company(null);

MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/locale/simple")
.header("Accept-Language", "en-us")
.jsonEntity(invalidCompany)
.responseType(Map.class);

this.response = this.mockMvc.perform(requestBuilder);
// 获取JSON格式的错误信息
String errorMessage = "";
if (this.response.entity().isPresent()) {
Entity entity = this.response.entity().get();
if (entity instanceof ObjectEntity) {
ObjectEntity<?> objectEntity = (ObjectEntity<?>) entity;
Object errorObj = objectEntity.object();
if (errorObj instanceof Map) {
Map<String, Object> errorMap = (Map<String, Object>) errorObj;
errorMessage =
errorMap.get("error") != null ? errorMap.get("error").toString() : errorMap.toString();
} else {
errorMessage = errorObj.toString();
}
}
}

assertThat(errorMessage).isEqualTo("validateSimpleParam.company.employees: must not be null");
assertThat(this.response.statusCode()).isEqualTo(500);
}

@Test
@DisplayName("测试URL参数指定地区")
void shouldUseLocaleFromUrlParam() {
Company invalidCompany = new Company(null);

MockRequestBuilder requestBuilder = MockMvcRequestBuilders.post("/validation/locale/simple")
.param("locale", "en-US")
.jsonEntity(invalidCompany)
.responseType(Map.class);

this.response = this.mockMvc.perform(requestBuilder);

// 获取JSON格式的错误信息
String errorMessage = "";
if (this.response.entity().isPresent()) {
Entity entity = this.response.entity().get();
if (entity instanceof ObjectEntity) {
ObjectEntity<?> objectEntity = (ObjectEntity<?>) entity;
Object errorObj = objectEntity.object();
if (errorObj instanceof Map) {
Map<String, Object> errorMap = (Map<String, Object>) errorObj;
errorMessage =
errorMap.get("error") != null ? errorMap.get("error").toString() : errorMap.toString();
} else {
errorMessage = errorObj.toString();
}
}
}

assertThat(errorMessage).isEqualTo("validateSimpleParam.company.employees: must not be null");
assertThat(this.response.cookies().get("locale").isPresent());
assertThat(this.response.cookies().get("locale").get().value()).isEqualTo("en-US");
assertThat(this.response.statusCode()).isEqualTo(500);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,8 @@ class StudentGroupValidationTests {
public void givenParametersThenGroupValidateHappened() {
// 测试学生年龄验证 - 现在会抛出异常,因为使用了学生分组
Method method = ReflectionUtils.getDeclaredMethod(GroupValidateService.StudentValidateService.class,
"validateStudentAge", int.class);
"validateStudentAge",
int.class);
Method handleValidatedMethod = ReflectionUtils.getDeclaredMethod(ValidationHandler.class,
"handle",
JoinPoint.class,
Expand Down Expand Up @@ -755,4 +756,21 @@ void testRangeBigDecimalValidation() {
ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {new BigDecimal("5.5")});
assertThat(exception.getMessage()).contains("需要在10和100之间");
}
}

@Nested
@DisplayName("测试 Locale 默认值为 null 时的情况")
public class ValidationHandlerNullTest {
@BeforeEach
void setUp() {
ValidationHandlerTest.this.handler.setLocale(null);
}

@Test
@DisplayName("测试@Null注解")
void testNullValidation() {
Method method = ReflectionUtils.getDeclaredMethod(ValidateService.class, "testNull", String.class);
ConstraintViolationException exception = invokeHandleMethod(method, new Object[] {"not null"});
assertThat(exception.getMessage()).isNotNull();
}
}
}
Loading