This is an automated email from the ASF dual-hosted git repository.

terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new 86c1519e4d6 Introduce Apache BVal rule configuration validation 
(#39880)
86c1519e4d6 is described below

commit 86c1519e4d6deaf68e18ee47196e81da2e114f62
Author: Liang Zhang <[email protected]>
AuthorDate: Thu Sep 17 23:15:56 2026 +0800

    Introduce Apache BVal rule configuration validation (#39880)
    
    * Require automatic review and Git commit commands
    
    * Introduce Apache BVal rule configuration validation
    
    * Introduce Apache BVal rule configuration validation
    
    * Introduce Apache BVal rule configuration validation
---
 infra/common/pom.xml                               |  9 +++
 .../rule/validator/RuleConfigurationValidator.java | 44 ++++++++-----
 .../builder/database/DatabaseRulesBuilder.java     |  6 +-
 .../rule/builder/global/GlobalRulesBuilder.java    |  6 +-
 .../validator/RuleConfigurationValidatorTest.java  | 72 ++++++++++++++++++++++
 .../builder/database/DatabaseRulesBuilderTest.java | 18 ++++++
 .../fixture/FixtureDatabaseRuleConfiguration.java  |  7 +++
 .../fixture/FixtureGlobalRuleConfiguration.java    |  7 +++
 .../ToggleFixtureDatabaseRuleConfiguration.java    | 13 +++-
 .../builder/global/GlobalRulesBuilderTest.java     | 18 ++++++
 ...YamlRuntimeDatabaseConfigurationsValidator.java |  8 +--
 mcp/pom.xml                                        | 17 -----
 mcp/support/pom.xml                                |  4 +-
 .../configuration/MCPConfigurationValidator.java   | 29 +++++++--
 .../MCPCompletionTargetDescriptorValidator.java    |  2 +
 .../MCPResourceNavigationDescriptorValidator.java  |  4 ++
 .../yaml/YamlMCPCompletionTargetDescriptor.java    |  2 -
 .../yaml/YamlMCPResourceNavigationDescriptor.java  |  3 -
 .../validator/MCPPromptGuidanceMetaValidator.java  |  4 +-
 .../UniqueMCPPromptArgumentNamesValidator.java     |  9 +--
 ...MCPCompletionTargetDescriptorValidatorTest.java | 12 +++-
 .../MCPDescriptorCatalogYamlSwapperTest.java       | 22 -------
 ...PResourceNavigationDescriptorValidatorTest.java | 31 ++++++++--
 .../rule/DatabaseRuleConfigurationManager.java     |  2 +
 .../manager/rule/GlobalConfigurationManager.java   |  2 +
 .../manager/resource/StorageUnitManagerTest.java   |  9 ++-
 .../rule/DatabaseRuleConfigurationManagerTest.java | 19 ++++++
 .../rule/GlobalConfigurationManagerTest.java       | 17 +++++
 pom.xml                                            |  6 ++
 29 files changed, 310 insertions(+), 92 deletions(-)

diff --git a/infra/common/pom.xml b/infra/common/pom.xml
index d2bef8e0a00..79f8d3ccd9a 100644
--- a/infra/common/pom.xml
+++ b/infra/common/pom.xml
@@ -48,6 +48,15 @@
             <version>${project.version}</version>
         </dependency>
         
+        <dependency>
+            <groupId>jakarta.validation</groupId>
+            <artifactId>jakarta.validation-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.bval</groupId>
+            <artifactId>bval-jsr</artifactId>
+        </dependency>
+        
         <dependency>
             <groupId>org.apache.shardingsphere</groupId>
             <artifactId>shardingsphere-test-infra-framework</artifactId>
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/configuration/MCPConfigurationValidator.java
 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/config/rule/validator/RuleConfigurationValidator.java
similarity index 50%
copy from 
mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/configuration/MCPConfigurationValidator.java
copy to 
infra/common/src/main/java/org/apache/shardingsphere/infra/config/rule/validator/RuleConfigurationValidator.java
index 77135342abc..6a0e229b192 100644
--- 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/configuration/MCPConfigurationValidator.java
+++ 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/config/rule/validator/RuleConfigurationValidator.java
@@ -15,47 +15,57 @@
  * limitations under the License.
  */
 
-package org.apache.shardingsphere.mcp.support.configuration;
+package org.apache.shardingsphere.infra.config.rule.validator;
 
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
+import org.apache.bval.jsr.ApacheValidationProvider;
+import org.apache.shardingsphere.infra.config.rule.RuleConfiguration;
 import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
-import org.hibernate.validator.HibernateValidator;
-import 
org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator;
+import 
org.apache.shardingsphere.infra.exception.kernel.metadata.rule.InvalidRuleConfigurationException;
 
 import javax.validation.ConstraintViolation;
 import javax.validation.Validation;
 import javax.validation.Validator;
+import java.util.Collection;
 import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
- * MCP configuration validator.
+ * Rule configuration validator.
  */
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
-public final class MCPConfigurationValidator {
+public final class RuleConfigurationValidator {
     
-    private static final Validator VALIDATOR = 
Validation.byProvider(HibernateValidator.class)
-            .configure().messageInterpolator(new 
ParameterMessageInterpolator()).buildValidatorFactory().getValidator();
+    private static final Validator VALIDATOR = 
Validation.byProvider(ApacheValidationProvider.class).configure().buildValidatorFactory().getValidator();
     
     /**
-     * Validate MCP configuration.
+     * Validate rule configurations.
      *
-     * @param config configuration
-     * @param configName configuration name
+     * @param ruleConfigs rule configurations
      */
-    public static void validate(final Object config, final String configName) {
-        ShardingSpherePreconditions.checkNotNull(config, () -> new 
IllegalArgumentException(String.format("%s cannot be null.", configName)));
-        Set<ConstraintViolation<Object>> violations = 
VALIDATOR.validate(config);
-        ShardingSpherePreconditions.checkMustEmpty(violations, () -> new 
IllegalArgumentException(violations.stream()
-                .map(each -> formatViolation(configName, 
each)).sorted().collect(Collectors.joining("; "))));
+    public static void validate(final Collection<RuleConfiguration> 
ruleConfigs) {
+        for (RuleConfiguration each : ruleConfigs) {
+            validate(each);
+        }
+    }
+    
+    /**
+     * Validate rule configuration.
+     *
+     * @param ruleConfig rule configuration
+     */
+    public static void validate(final RuleConfiguration ruleConfig) {
+        Set<ConstraintViolation<RuleConfiguration>> violations = 
VALIDATOR.validate(ruleConfig);
+        ShardingSpherePreconditions.checkMustEmpty(violations, () -> new 
InvalidRuleConfigurationException(
+                ruleConfig.getClass().getSimpleName(), 
violations.stream().map(RuleConfigurationValidator::formatViolation).sorted().collect(Collectors.joining(";
 "))));
     }
     
-    private static String formatViolation(final String configName, final 
ConstraintViolation<Object> violation) {
+    private static String formatViolation(final 
ConstraintViolation<RuleConfiguration> violation) {
         if (violation.getPropertyPath().toString().isEmpty()) {
             return violation.getMessage();
         }
         String message = violation.getMessage();
-        return String.format("%s property `%s` %s%s", configName, 
violation.getPropertyPath(), message, message.endsWith(".") ? "" : ".");
+        return String.format("Property `%s` %s%s", 
violation.getPropertyPath(), message, message.endsWith(".") ? "" : ".");
     }
 }
diff --git 
a/infra/common/src/main/java/org/apache/shardingsphere/infra/rule/builder/database/DatabaseRulesBuilder.java
 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/rule/builder/database/DatabaseRulesBuilder.java
index 40abab78286..2113f3f5dc0 100644
--- 
a/infra/common/src/main/java/org/apache/shardingsphere/infra/rule/builder/database/DatabaseRulesBuilder.java
+++ 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/rule/builder/database/DatabaseRulesBuilder.java
@@ -27,6 +27,7 @@ import 
org.apache.shardingsphere.infra.config.rule.checker.DatabaseRuleConfigura
 import 
org.apache.shardingsphere.infra.config.rule.function.DistributedRuleConfiguration;
 import 
org.apache.shardingsphere.infra.config.rule.function.EnhancedRuleConfiguration;
 import 
org.apache.shardingsphere.infra.config.rule.scope.DatabaseRuleConfiguration;
+import 
org.apache.shardingsphere.infra.config.rule.validator.RuleConfigurationValidator;
 import org.apache.shardingsphere.infra.instance.ComputeNodeInstanceContext;
 import 
org.apache.shardingsphere.infra.metadata.database.resource.ResourceMetaData;
 import org.apache.shardingsphere.infra.rule.ShardingSphereRule;
@@ -63,8 +64,10 @@ public final class DatabaseRulesBuilder {
     @SuppressWarnings({"unchecked", "rawtypes"})
     public static Collection<ShardingSphereRule> build(final String 
databaseName, final DatabaseType protocolType, final DatabaseConfiguration 
databaseConfig,
                                                        final 
ComputeNodeInstanceContext computeNodeInstanceContext, final ResourceMetaData 
resourceMetaData) {
+        Map<RuleConfiguration, DatabaseRuleBuilder> ruleBuilderMap = 
getRuleBuilderMap(databaseConfig);
+        RuleConfigurationValidator.validate(ruleBuilderMap.keySet());
         Collection<ShardingSphereRule> result = new LinkedList<>();
-        for (Entry<RuleConfiguration, DatabaseRuleBuilder> entry : 
getRuleBuilderMap(databaseConfig).entrySet()) {
+        for (Entry<RuleConfiguration, DatabaseRuleBuilder> entry : 
ruleBuilderMap.entrySet()) {
             DatabaseRuleConfigurationChecker configChecker = 
OrderedSPILoader.getServicesByClass(
                     DatabaseRuleConfigurationChecker.class, 
Collections.singleton(entry.getKey().getClass())).get(entry.getKey().getClass());
             if (null != configChecker) {
@@ -90,6 +93,7 @@ public final class DatabaseRulesBuilder {
     @SuppressWarnings({"unchecked", "rawtypes"})
     public static ShardingSphereRule build(final String databaseName, final 
DatabaseType protocolType, final Collection<ShardingSphereRule> rules, final 
RuleConfiguration ruleConfig,
                                            final ComputeNodeInstanceContext 
computeNodeInstanceContext, final ResourceMetaData resourceMetaData) {
+        RuleConfigurationValidator.validate(ruleConfig);
         DatabaseRuleBuilder databaseRuleBuilder = 
OrderedSPILoader.getServices(DatabaseRuleBuilder.class, 
Collections.singleton(ruleConfig)).get(ruleConfig);
         DatabaseRuleConfigurationChecker configChecker =
                 
OrderedSPILoader.getServicesByClass(DatabaseRuleConfigurationChecker.class, 
Collections.singleton(ruleConfig.getClass())).get(ruleConfig.getClass());
diff --git 
a/infra/common/src/main/java/org/apache/shardingsphere/infra/rule/builder/global/GlobalRulesBuilder.java
 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/rule/builder/global/GlobalRulesBuilder.java
index 0a28e081dc4..0a9c41b5c4d 100644
--- 
a/infra/common/src/main/java/org/apache/shardingsphere/infra/rule/builder/global/GlobalRulesBuilder.java
+++ 
b/infra/common/src/main/java/org/apache/shardingsphere/infra/rule/builder/global/GlobalRulesBuilder.java
@@ -21,6 +21,7 @@ import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import org.apache.shardingsphere.infra.config.props.ConfigurationProperties;
 import org.apache.shardingsphere.infra.config.rule.RuleConfiguration;
+import 
org.apache.shardingsphere.infra.config.rule.validator.RuleConfigurationValidator;
 import 
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
 import org.apache.shardingsphere.infra.rule.ShardingSphereRule;
 import org.apache.shardingsphere.infra.spi.type.ordered.OrderedSPILoader;
@@ -50,8 +51,10 @@ public final class GlobalRulesBuilder {
     @SuppressWarnings({"unchecked", "rawtypes"})
     public static Collection<ShardingSphereRule> buildRules(final 
Collection<RuleConfiguration> globalRuleConfigs,
                                                             final 
Collection<ShardingSphereDatabase> databases, final ConfigurationProperties 
props) {
+        Map<RuleConfiguration, GlobalRuleBuilder> ruleBuilderMap = 
getRuleBuilderMap(globalRuleConfigs);
+        RuleConfigurationValidator.validate(ruleBuilderMap.keySet());
         Collection<ShardingSphereRule> result = new LinkedList<>();
-        for (Entry<RuleConfiguration, GlobalRuleBuilder> entry : 
getRuleBuilderMap(globalRuleConfigs).entrySet()) {
+        for (Entry<RuleConfiguration, GlobalRuleBuilder> entry : 
ruleBuilderMap.entrySet()) {
             result.add(entry.getValue().build(entry.getKey(), databases, 
props));
         }
         return result;
@@ -89,6 +92,7 @@ public final class GlobalRulesBuilder {
      */
     @SuppressWarnings("unchecked")
     public static Collection<ShardingSphereRule> buildSingleRules(final 
RuleConfiguration globalRuleConfig, final Collection<ShardingSphereDatabase> 
databases, final ConfigurationProperties props) {
+        RuleConfigurationValidator.validate(globalRuleConfig);
         return OrderedSPILoader.getServices(GlobalRuleBuilder.class, 
Collections.singleton(globalRuleConfig)).entrySet()
                 .stream().map(each -> each.getValue().build(each.getKey(), 
databases, props)).collect(Collectors.toList());
     }
diff --git 
a/infra/common/src/test/java/org/apache/shardingsphere/infra/config/rule/validator/RuleConfigurationValidatorTest.java
 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/config/rule/validator/RuleConfigurationValidatorTest.java
new file mode 100644
index 00000000000..9fbc864c17a
--- /dev/null
+++ 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/config/rule/validator/RuleConfigurationValidatorTest.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.shardingsphere.infra.config.rule.validator;
+
+import org.apache.shardingsphere.infra.config.rule.RuleConfiguration;
+import 
org.apache.shardingsphere.infra.exception.kernel.metadata.rule.InvalidRuleConfigurationException;
+import org.junit.jupiter.api.Test;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+import java.util.Arrays;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class RuleConfigurationValidatorTest {
+    
+    @Test
+    void assertValidateRuleConfiguration() {
+        assertDoesNotThrow(() -> RuleConfigurationValidator.validate(new 
FixtureRuleConfiguration("fixture")));
+    }
+    
+    @Test
+    void assertValidateRuleConfigurationWithViolation() {
+        InvalidRuleConfigurationException actual = 
assertThrows(InvalidRuleConfigurationException.class,
+                () -> RuleConfigurationValidator.validate(new 
FixtureRuleConfiguration("")));
+        assertThat(actual.getMessage(), is("Invalid 'FixtureRuleConfiguration' 
rule, error message is: Property `nested.name` is required."));
+    }
+    
+    @Test
+    void assertValidateRuleConfigurationsWithViolation() {
+        assertThrows(InvalidRuleConfigurationException.class,
+                () -> RuleConfigurationValidator.validate(Arrays.asList(new 
FixtureRuleConfiguration("fixture"), new FixtureRuleConfiguration(""))));
+    }
+    
+    private static final class FixtureRuleConfiguration implements 
RuleConfiguration {
+        
+        @Valid
+        private final NestedConfiguration nested;
+        
+        private FixtureRuleConfiguration(final String name) {
+            nested = new NestedConfiguration(name);
+        }
+    }
+    
+    private static final class NestedConfiguration {
+        
+        @NotBlank(message = "is required")
+        private final String name;
+        
+        private NestedConfiguration(final String name) {
+            this.name = name;
+        }
+    }
+}
diff --git 
a/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/database/DatabaseRulesBuilderTest.java
 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/database/DatabaseRulesBuilderTest.java
index e826de16d97..454aedcc19e 100644
--- 
a/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/database/DatabaseRulesBuilderTest.java
+++ 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/database/DatabaseRulesBuilderTest.java
@@ -20,6 +20,7 @@ package org.apache.shardingsphere.infra.rule.builder.database;
 import 
org.apache.shardingsphere.infra.config.database.impl.DataSourceProvidedDatabaseConfiguration;
 import org.apache.shardingsphere.infra.config.rule.RuleConfiguration;
 import 
org.apache.shardingsphere.infra.config.rule.checker.DatabaseRuleConfigurationChecker;
+import 
org.apache.shardingsphere.infra.exception.kernel.metadata.rule.InvalidRuleConfigurationException;
 import org.apache.shardingsphere.infra.fixture.FixtureRule;
 import org.apache.shardingsphere.infra.fixture.FixtureRuleConfiguration;
 import 
org.apache.shardingsphere.infra.metadata.database.resource.ResourceMetaData;
@@ -43,6 +44,7 @@ import java.util.List;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.isA;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.anyCollection;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -88,11 +90,27 @@ class DatabaseRulesBuilderTest {
         }
     }
     
+    @Test
+    void assertBuildWithInvalidRuleConfiguration() {
+        ToggleFixtureDatabaseRuleConfiguration ruleConfig = new 
ToggleFixtureDatabaseRuleConfiguration(false);
+        ruleConfig.setName("");
+        assertThrows(InvalidRuleConfigurationException.class, () -> 
DatabaseRulesBuilder.build("foo_db", null,
+                new 
DataSourceProvidedDatabaseConfiguration(Collections.emptyMap(), 
Collections.singleton(ruleConfig)), null, EMPTY_RESOURCE_META_DATA));
+    }
+    
     @Test
     void assertBuildSingleRule() {
         assertThat(DatabaseRulesBuilder.build("foo_db", null, 
Collections.emptyList(), new FixtureDatabaseRuleConfiguration(), null, 
EMPTY_RESOURCE_META_DATA), isA(FixtureRule.class));
     }
     
+    @Test
+    void assertBuildSingleRuleWithInvalidRuleConfiguration() {
+        FixtureDatabaseRuleConfiguration ruleConfig = new 
FixtureDatabaseRuleConfiguration();
+        ruleConfig.setName("");
+        assertThrows(InvalidRuleConfigurationException.class,
+                () -> DatabaseRulesBuilder.build("foo_db", null, 
Collections.emptyList(), ruleConfig, null, EMPTY_RESOURCE_META_DATA));
+    }
+    
     @Test
     @SuppressWarnings("unchecked")
     void assertBuildSingleRuleWithChecker() {
diff --git 
a/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/fixture/FixtureDatabaseRuleConfiguration.java
 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/fixture/FixtureDatabaseRuleConfiguration.java
index 50e7f4df814..b1936509e2e 100644
--- 
a/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/fixture/FixtureDatabaseRuleConfiguration.java
+++ 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/fixture/FixtureDatabaseRuleConfiguration.java
@@ -17,7 +17,14 @@
 
 package org.apache.shardingsphere.infra.rule.builder.fixture;
 
+import lombok.Setter;
 import 
org.apache.shardingsphere.infra.config.rule.scope.DatabaseRuleConfiguration;
 
+import javax.validation.constraints.NotBlank;
+
 public final class FixtureDatabaseRuleConfiguration implements 
DatabaseRuleConfiguration {
+    
+    @Setter
+    @NotBlank(message = "is required")
+    private String name = "fixture";
 }
diff --git 
a/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/fixture/FixtureGlobalRuleConfiguration.java
 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/fixture/FixtureGlobalRuleConfiguration.java
index 653e775527c..61d2d64d3f7 100644
--- 
a/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/fixture/FixtureGlobalRuleConfiguration.java
+++ 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/fixture/FixtureGlobalRuleConfiguration.java
@@ -17,7 +17,14 @@
 
 package org.apache.shardingsphere.infra.rule.builder.fixture;
 
+import lombok.Setter;
 import 
org.apache.shardingsphere.infra.config.rule.scope.GlobalRuleConfiguration;
 
+import javax.validation.constraints.NotBlank;
+
 public final class FixtureGlobalRuleConfiguration implements 
GlobalRuleConfiguration {
+    
+    @Setter
+    @NotBlank(message = "is required")
+    private String name = "fixture";
 }
diff --git 
a/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/fixture/ToggleFixtureDatabaseRuleConfiguration.java
 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/fixture/ToggleFixtureDatabaseRuleConfiguration.java
index d4b260a24fd..76f27fc6bd1 100644
--- 
a/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/fixture/ToggleFixtureDatabaseRuleConfiguration.java
+++ 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/fixture/ToggleFixtureDatabaseRuleConfiguration.java
@@ -17,15 +17,24 @@
 
 package org.apache.shardingsphere.infra.rule.builder.fixture;
 
-import lombok.AllArgsConstructor;
 import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
 import 
org.apache.shardingsphere.infra.config.rule.function.EnhancedRuleConfiguration;
 import 
org.apache.shardingsphere.infra.config.rule.scope.DatabaseRuleConfiguration;
 
-@AllArgsConstructor
+import javax.validation.constraints.AssertFalse;
+import javax.validation.constraints.NotBlank;
+
+@RequiredArgsConstructor
 public final class ToggleFixtureDatabaseRuleConfiguration implements 
DatabaseRuleConfiguration, EnhancedRuleConfiguration {
     
     @Getter
+    @AssertFalse(message = "must be false")
     private final boolean empty;
     
+    @Setter
+    @NotBlank(message = "is required")
+    private String name = "fixture";
+    
 }
diff --git 
a/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/global/GlobalRulesBuilderTest.java
 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/global/GlobalRulesBuilderTest.java
index 77009b04b50..8ccf0326eea 100644
--- 
a/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/global/GlobalRulesBuilderTest.java
+++ 
b/infra/common/src/test/java/org/apache/shardingsphere/infra/rule/builder/global/GlobalRulesBuilderTest.java
@@ -19,6 +19,7 @@ package org.apache.shardingsphere.infra.rule.builder.global;
 
 import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
 import org.apache.shardingsphere.infra.config.props.ConfigurationProperties;
+import 
org.apache.shardingsphere.infra.exception.kernel.metadata.rule.InvalidRuleConfigurationException;
 import 
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
 import 
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabaseFactory;
 import org.apache.shardingsphere.infra.rule.ShardingSphereRule;
@@ -34,6 +35,7 @@ import java.util.Properties;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.isA;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.Mockito.mock;
 
 class GlobalRulesBuilderTest {
@@ -45,12 +47,28 @@ class GlobalRulesBuilderTest {
         assertThat(rules.iterator().next(), isA(FixtureGlobalRule.class));
     }
     
+    @Test
+    void assertBuildRulesWithInvalidRuleConfiguration() {
+        FixtureGlobalRuleConfiguration ruleConfig = new 
FixtureGlobalRuleConfiguration();
+        ruleConfig.setName("");
+        assertThrows(InvalidRuleConfigurationException.class,
+                () -> 
GlobalRulesBuilder.buildRules(Collections.singletonList(ruleConfig), 
Collections.singleton(buildDatabase()), mock()));
+    }
+    
     @Test
     void assertBuildSingleRules() {
         Collection<ShardingSphereRule> rules = 
GlobalRulesBuilder.buildSingleRules(new FixtureGlobalRuleConfiguration(), 
Collections.singleton(buildDatabase()), mock());
         assertThat(rules.size(), is(1));
     }
     
+    @Test
+    void assertBuildSingleRulesWithInvalidRuleConfiguration() {
+        FixtureGlobalRuleConfiguration ruleConfig = new 
FixtureGlobalRuleConfiguration();
+        ruleConfig.setName("");
+        assertThrows(InvalidRuleConfigurationException.class,
+                () -> GlobalRulesBuilder.buildSingleRules(ruleConfig, 
Collections.singleton(buildDatabase()), mock()));
+    }
+    
     private ShardingSphereDatabase buildDatabase() {
         return ShardingSphereDatabaseFactory.create("foo_db", 
TypedSPILoader.getService(DatabaseType.class, "FIXTURE"), new 
ConfigurationProperties(new Properties()));
     }
diff --git 
a/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/validator/YamlRuntimeDatabaseConfigurationsValidator.java
 
b/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/validator/YamlRuntimeDatabaseConfigurationsValidator.java
index 9df166968e4..b2ecbf3e2af 100644
--- 
a/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/validator/YamlRuntimeDatabaseConfigurationsValidator.java
+++ 
b/mcp/bootstrap/src/main/java/org/apache/shardingsphere/mcp/bootstrap/config/yaml/validator/YamlRuntimeDatabaseConfigurationsValidator.java
@@ -27,16 +27,16 @@ import java.util.Map.Entry;
 /**
  * YAML runtime database configurations validator.
  */
-public final class YamlRuntimeDatabaseConfigurationsValidator implements 
ConstraintValidator<ValidYamlRuntimeDatabaseConfigurations, Map<String, 
YamlRuntimeDatabaseConfiguration>> {
+public final class YamlRuntimeDatabaseConfigurationsValidator implements 
ConstraintValidator<ValidYamlRuntimeDatabaseConfigurations, Map<?, ?>> {
     
     @Override
-    public boolean isValid(final Map<String, YamlRuntimeDatabaseConfiguration> 
value, final ConstraintValidatorContext context) {
+    public boolean isValid(final Map<?, ?> value, final 
ConstraintValidatorContext context) {
         if (null == value) {
             return true;
         }
         boolean result = true;
-        for (Entry<String, YamlRuntimeDatabaseConfiguration> entry : 
value.entrySet()) {
-            result = validateRuntimeDatabase(entry.getKey(), entry.getValue(), 
context) && result;
+        for (Entry<?, ?> entry : value.entrySet()) {
+            result = validateRuntimeDatabase((String) entry.getKey(), 
(YamlRuntimeDatabaseConfiguration) entry.getValue(), context) && result;
         }
         return result;
     }
diff --git a/mcp/pom.xml b/mcp/pom.xml
index 30b92edef73..fe96c44b44d 100644
--- a/mcp/pom.xml
+++ b/mcp/pom.xml
@@ -41,22 +41,5 @@
         <maven.compiler.source>${java.version}</maven.compiler.source>
         <maven.compiler.target>${java.version}</maven.compiler.target>
         <maven.compiler.release>${java.version}</maven.compiler.release>
-        <hibernate-validator.version>6.2.5.Final</hibernate-validator.version>
-        <jakarta-validation-api.version>2.0.2</jakarta-validation-api.version>
     </properties>
-    
-    <dependencyManagement>
-        <dependencies>
-            <dependency>
-                <groupId>jakarta.validation</groupId>
-                <artifactId>jakarta.validation-api</artifactId>
-                <version>${jakarta-validation-api.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.hibernate.validator</groupId>
-                <artifactId>hibernate-validator</artifactId>
-                <version>${hibernate-validator.version}</version>
-            </dependency>
-        </dependencies>
-    </dependencyManagement>
 </project>
diff --git a/mcp/support/pom.xml b/mcp/support/pom.xml
index 720b3686e05..abd04b31090 100644
--- a/mcp/support/pom.xml
+++ b/mcp/support/pom.xml
@@ -67,8 +67,8 @@
             <artifactId>jakarta.validation-api</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.hibernate.validator</groupId>
-            <artifactId>hibernate-validator</artifactId>
+            <groupId>org.apache.bval</groupId>
+            <artifactId>bval-jsr</artifactId>
         </dependency>
     </dependencies>
 </project>
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/configuration/MCPConfigurationValidator.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/configuration/MCPConfigurationValidator.java
index 77135342abc..6fc38a701d2 100644
--- 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/configuration/MCPConfigurationValidator.java
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/configuration/MCPConfigurationValidator.java
@@ -19,13 +19,15 @@ package org.apache.shardingsphere.mcp.support.configuration;
 
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
+import org.apache.bval.jsr.ApacheValidationProvider;
 import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
-import org.hibernate.validator.HibernateValidator;
-import 
org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator;
 
 import javax.validation.ConstraintViolation;
+import javax.validation.ElementKind;
+import javax.validation.Path;
 import javax.validation.Validation;
 import javax.validation.Validator;
+import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -35,8 +37,7 @@ import java.util.stream.Collectors;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public final class MCPConfigurationValidator {
     
-    private static final Validator VALIDATOR = 
Validation.byProvider(HibernateValidator.class)
-            .configure().messageInterpolator(new 
ParameterMessageInterpolator()).buildValidatorFactory().getValidator();
+    private static final Validator VALIDATOR = 
Validation.byProvider(ApacheValidationProvider.class).configure().buildValidatorFactory().getValidator();
     
     /**
      * Validate MCP configuration.
@@ -48,7 +49,7 @@ public final class MCPConfigurationValidator {
         ShardingSpherePreconditions.checkNotNull(config, () -> new 
IllegalArgumentException(String.format("%s cannot be null.", configName)));
         Set<ConstraintViolation<Object>> violations = 
VALIDATOR.validate(config);
         ShardingSpherePreconditions.checkMustEmpty(violations, () -> new 
IllegalArgumentException(violations.stream()
-                .map(each -> formatViolation(configName, 
each)).sorted().collect(Collectors.joining("; "))));
+                .map(each -> formatViolation(configName, 
each)).distinct().sorted().collect(Collectors.joining("; "))));
     }
     
     private static String formatViolation(final String configName, final 
ConstraintViolation<Object> violation) {
@@ -56,6 +57,22 @@ public final class MCPConfigurationValidator {
             return violation.getMessage();
         }
         String message = violation.getMessage();
-        return String.format("%s property `%s` %s%s", configName, 
violation.getPropertyPath(), message, message.endsWith(".") ? "" : ".");
+        return String.format("%s property `%s` %s%s", configName, 
formatPropertyPath(violation.getPropertyPath()), message, message.endsWith(".") 
? "" : ".");
+    }
+    
+    private static String formatPropertyPath(final Path propertyPath) {
+        String result = propertyPath.toString();
+        for (Path.Node each : propertyPath) {
+            if (ElementKind.CONTAINER_ELEMENT != each.getKind()) {
+                continue;
+            }
+            Path.ContainerElementNode containerElementNode = 
each.as(Path.ContainerElementNode.class);
+            if (!Map.class.equals(containerElementNode.getContainerClass())) {
+                continue;
+            }
+            String node = String.format("[%s].%s", each.getKey(), 
each.getName());
+            result = result.replace(node, String.format("<%s>%s", 0 == 
containerElementNode.getTypeArgumentIndex() ? "K" : "V", node));
+        }
+        return result;
     }
 }
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPCompletionTargetDescriptorValidator.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPCompletionTargetDescriptorValidator.java
index bc1664c0f1c..602dfa27ac9 100644
--- 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPCompletionTargetDescriptorValidator.java
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPCompletionTargetDescriptorValidator.java
@@ -55,6 +55,8 @@ public final class MCPCompletionTargetDescriptorValidator {
         Map<String, MCPResourceDescriptor> resourceDescriptors = 
resources.stream().collect(Collectors.toMap(MCPResourceDescriptor::getUriTemplate,
 each -> each));
         Map<String, MCPCompletionTargetDescriptor> registered = new 
LinkedHashMap<>(descriptors.size(), 1F);
         for (MCPCompletionTargetDescriptor each : descriptors) {
+            ShardingSpherePreconditions.checkState(each.getArguments().size() 
== new HashSet<>(each.getArguments()).size(), () -> new IllegalStateException(
+                    String.format("Completion target `%s:%s` must not contain 
duplicate arguments.", each.getReferenceType(), each.getReference())));
             validateCompletionReference(each, promptNames, 
resourceDescriptors.keySet());
             validatePromptCompletionArguments(each, promptArguments);
             validateResourceCompletionArguments(each, resourceDescriptors);
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationDescriptorValidator.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationDescriptorValidator.java
index c8f5eb2b512..e430429b10f 100644
--- 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationDescriptorValidator.java
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationDescriptorValidator.java
@@ -48,6 +48,10 @@ public final class MCPResourceNavigationDescriptorValidator {
                     () -> new IllegalStateException(String.format("Resource 
navigation references unknown source `%s`.", each.getFrom())));
             
ShardingSpherePreconditions.checkState(publicIdentifiers.contains(each.getTo()),
                     () -> new IllegalStateException(String.format("Resource 
navigation references unknown target `%s`.", each.getTo())));
+            
ShardingSpherePreconditions.checkState(each.getRequiredArguments().size() == 
new HashSet<>(each.getRequiredArguments()).size(), () -> new 
IllegalStateException(
+                    String.format("Resource navigation `%s` to `%s` must not 
contain duplicate required arguments.", each.getFrom(), each.getTo())));
+            
ShardingSpherePreconditions.checkState(each.getCarriedArguments().size() == new 
HashSet<>(each.getCarriedArguments()).size(), () -> new IllegalStateException(
+                    String.format("Resource navigation `%s` to `%s` must not 
contain duplicate carried arguments.", each.getFrom(), each.getTo())));
             
ShardingSpherePreconditions.checkState(registered.add(each.getFrom() + "->" + 
each.getTo()),
                     () -> new IllegalStateException(String.format("Duplicate 
MCP resource navigation `%s` to `%s`.", each.getFrom(), each.getTo())));
         }
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/YamlMCPCompletionTargetDescriptor.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/YamlMCPCompletionTargetDescriptor.java
index c0059e77300..8ccdcfd920c 100644
--- 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/YamlMCPCompletionTargetDescriptor.java
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/YamlMCPCompletionTargetDescriptor.java
@@ -20,7 +20,6 @@ package org.apache.shardingsphere.mcp.support.descriptor.yaml;
 import lombok.Getter;
 import lombok.Setter;
 import 
org.apache.shardingsphere.mcp.support.descriptor.yaml.validator.MCPMetadataKey;
-import org.hibernate.validator.constraints.UniqueElements;
 
 import javax.validation.constraints.Max;
 import javax.validation.constraints.NotBlank;
@@ -48,7 +47,6 @@ public final class YamlMCPCompletionTargetDescriptor {
     private String reference;
     
     @NotEmpty(message = "is required")
-    @UniqueElements(message = "must not contain duplicate values")
     private Collection<@NotBlank(message = "is required") String> arguments = 
new LinkedList<>();
     
     @Max(value = 100, message = "must not exceed 100")
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/YamlMCPResourceNavigationDescriptor.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/YamlMCPResourceNavigationDescriptor.java
index 0a020a42366..cb23b6eb6a9 100644
--- 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/YamlMCPResourceNavigationDescriptor.java
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/YamlMCPResourceNavigationDescriptor.java
@@ -20,7 +20,6 @@ package org.apache.shardingsphere.mcp.support.descriptor.yaml;
 import lombok.Getter;
 import lombok.Setter;
 import 
org.apache.shardingsphere.mcp.support.descriptor.yaml.validator.MCPDescription;
-import org.hibernate.validator.constraints.UniqueElements;
 
 import javax.validation.constraints.NotBlank;
 import java.util.Collection;
@@ -39,10 +38,8 @@ public final class YamlMCPResourceNavigationDescriptor {
     @NotBlank(message = "is required")
     private String to;
     
-    @UniqueElements(message = "must not contain duplicate values")
     private Collection<@NotBlank(message = "is required") String> 
requiredArguments = new LinkedList<>();
     
-    @UniqueElements(message = "must not contain duplicate values")
     private Collection<@NotBlank(message = "is required") String> 
carriedArguments = new LinkedList<>();
     
     @MCPDescription
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/MCPPromptGuidanceMetaValidator.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/MCPPromptGuidanceMetaValidator.java
index 8ac0f1a2c1b..9ea7ae57fe8 100644
--- 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/MCPPromptGuidanceMetaValidator.java
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/MCPPromptGuidanceMetaValidator.java
@@ -27,10 +27,10 @@ import java.util.Map;
 /**
  * MCP prompt guidance metadata validator.
  */
-public final class MCPPromptGuidanceMetaValidator implements 
ConstraintValidator<MCPPromptGuidanceMeta, Map<String, Object>> {
+public final class MCPPromptGuidanceMetaValidator implements 
ConstraintValidator<MCPPromptGuidanceMeta, Map<?, ?>> {
     
     @Override
-    public boolean isValid(final Map<String, Object> value, final 
ConstraintValidatorContext context) {
+    public boolean isValid(final Map<?, ?> value, final 
ConstraintValidatorContext context) {
         if (null == value) {
             return true;
         }
diff --git 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/UniqueMCPPromptArgumentNamesValidator.java
 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/UniqueMCPPromptArgumentNamesValidator.java
index 6b365777cea..4217242e5aa 100644
--- 
a/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/UniqueMCPPromptArgumentNamesValidator.java
+++ 
b/mcp/support/src/main/java/org/apache/shardingsphere/mcp/support/descriptor/yaml/validator/UniqueMCPPromptArgumentNamesValidator.java
@@ -28,16 +28,17 @@ import java.util.Set;
 /**
  * Unique MCP prompt argument names validator.
  */
-public final class UniqueMCPPromptArgumentNamesValidator implements 
ConstraintValidator<UniqueMCPPromptArgumentNames, 
Collection<YamlMCPPromptArgumentDescriptor>> {
+public final class UniqueMCPPromptArgumentNamesValidator implements 
ConstraintValidator<UniqueMCPPromptArgumentNames, Collection<?>> {
     
     @Override
-    public boolean isValid(final Collection<YamlMCPPromptArgumentDescriptor> 
value, final ConstraintValidatorContext context) {
+    public boolean isValid(final Collection<?> value, final 
ConstraintValidatorContext context) {
         if (null == value) {
             return true;
         }
         Set<String> names = new HashSet<>(value.size(), 1F);
-        for (YamlMCPPromptArgumentDescriptor each : value) {
-            if (null != each && null != each.getName() && 
!each.getName().isBlank() && !names.add(each.getName())) {
+        for (Object each : value) {
+            YamlMCPPromptArgumentDescriptor argument = 
(YamlMCPPromptArgumentDescriptor) each;
+            if (null != argument && null != argument.getName() && 
!argument.getName().isBlank() && !names.add(argument.getName())) {
                 return false;
             }
         }
diff --git 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPCompletionTargetDescriptorValidatorTest.java
 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPCompletionTargetDescriptorValidatorTest.java
index 1c5808cab50..783e362fdc5 100644
--- 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPCompletionTargetDescriptorValidatorTest.java
+++ 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPCompletionTargetDescriptorValidatorTest.java
@@ -32,7 +32,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 class MCPCompletionTargetDescriptorValidatorTest {
     
     @Test
-    void assertValidate() {
+    void assertValidateWithUndeclaredContextArgument() {
         MCPPromptDescriptor prompt = new MCPPromptDescriptor("test_prompt", 
"Test Prompt", "Guide the model through a test prompt.",
                 List.of(new MCPPromptArgumentDescriptor("database", 
"Database", "Logical database.", false)), Map.of());
         MCPCompletionTargetDescriptor completion = new 
MCPCompletionTargetDescriptor("prompt", "test_prompt", List.of("database"), 50,
@@ -41,4 +41,14 @@ class MCPCompletionTargetDescriptorValidatorTest {
                 () -> 
MCPCompletionTargetDescriptorValidator.validate(List.of(completion), 
List.of(prompt), List.of()));
         assertThat(actual.getMessage(), is("Completion target 
`prompt:test_prompt` context argument `tenant` for `database` is not declared 
by the target."));
     }
+    
+    @Test
+    void assertValidateWithDuplicateArguments() {
+        MCPPromptDescriptor prompt = new MCPPromptDescriptor("test_prompt", 
"Test Prompt", "Guide the model through a test prompt.",
+                List.of(new MCPPromptArgumentDescriptor("database", 
"Database", "Logical database.", false)), Map.of());
+        MCPCompletionTargetDescriptor completion = new 
MCPCompletionTargetDescriptor("prompt", "test_prompt", List.of("database", 
"database"), 50, Map.of());
+        IllegalStateException actual = 
assertThrows(IllegalStateException.class,
+                () -> 
MCPCompletionTargetDescriptorValidator.validate(List.of(completion), 
List.of(prompt), List.of()));
+        assertThat(actual.getMessage(), is("Completion target 
`prompt:test_prompt` must not contain duplicate arguments."));
+    }
 }
diff --git 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogYamlSwapperTest.java
 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogYamlSwapperTest.java
index 95534f7da38..7bb9eb005bf 100644
--- 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogYamlSwapperTest.java
+++ 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPDescriptorCatalogYamlSwapperTest.java
@@ -24,7 +24,6 @@ import 
org.apache.shardingsphere.mcp.support.descriptor.yaml.YamlMCPPromptBindin
 import 
org.apache.shardingsphere.mcp.support.descriptor.yaml.YamlMCPPromptDescriptor;
 import 
org.apache.shardingsphere.mcp.support.descriptor.yaml.YamlMCPResourceAnnotations;
 import 
org.apache.shardingsphere.mcp.support.descriptor.yaml.YamlMCPResourceDescriptor;
-import 
org.apache.shardingsphere.mcp.support.descriptor.yaml.YamlMCPResourceNavigationDescriptor;
 import 
org.apache.shardingsphere.mcp.support.descriptor.yaml.YamlMCPToolAnnotations;
 import 
org.apache.shardingsphere.mcp.support.descriptor.yaml.YamlMCPToolDescriptor;
 import org.junit.jupiter.api.Test;
@@ -127,27 +126,6 @@ class MCPDescriptorCatalogYamlSwapperTest {
         assertYamlValidationError(yamlCatalog, "MCP descriptor catalog 
property `completionTargets[0].maxValues` must not exceed 100.");
     }
     
-    @Test
-    void assertSwapRejectsDuplicateCompletionArguments() {
-        YamlMCPCompletionTargetDescriptor yamlCompletionTarget = 
createYamlCompletionTargetDescriptor();
-        yamlCompletionTarget.setArguments(List.of("database", "database"));
-        YamlMCPDescriptorCatalog yamlCatalog = new YamlMCPDescriptorCatalog();
-        yamlCatalog.setCompletionTargets(List.of(yamlCompletionTarget));
-        assertYamlValidationError(yamlCatalog, "MCP descriptor catalog 
property `completionTargets[0].arguments` must not contain duplicate values.");
-    }
-    
-    @Test
-    void assertSwapRejectsDuplicateResourceNavigationArguments() {
-        YamlMCPResourceNavigationDescriptor yamlResourceNavigation = new 
YamlMCPResourceNavigationDescriptor();
-        yamlResourceNavigation.setFrom("database_gateway_search_metadata");
-        yamlResourceNavigation.setTo("shardingsphere://capabilities");
-        yamlResourceNavigation.setDescription("Navigate from metadata search 
to capabilities.");
-        yamlResourceNavigation.setRequiredArguments(List.of("database", 
"database"));
-        YamlMCPDescriptorCatalog yamlCatalog = new YamlMCPDescriptorCatalog();
-        yamlCatalog.setResourceNavigation(List.of(yamlResourceNavigation));
-        assertYamlValidationError(yamlCatalog, "MCP descriptor catalog 
property `resourceNavigation[0].requiredArguments` must not contain duplicate 
values.");
-    }
-    
     @Test
     void assertSwapRejectsUnnamespacedMetadataKey() {
         YamlMCPResourceDescriptor yamlResource = 
createYamlResourceDescriptor();
diff --git 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationDescriptorValidatorTest.java
 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationDescriptorValidatorTest.java
index 2d237ec20c8..2bb573fca7c 100644
--- 
a/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationDescriptorValidatorTest.java
+++ 
b/mcp/support/src/test/java/org/apache/shardingsphere/mcp/support/descriptor/MCPResourceNavigationDescriptorValidatorTest.java
@@ -31,17 +31,38 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 class MCPResourceNavigationDescriptorValidatorTest {
     
     @Test
-    void assertValidate() {
+    void assertValidateWithUnknownSource() {
         MCPResourceNavigationDescriptor navigation = new 
MCPResourceNavigationDescriptor("missing", "database_gateway_test_tool", 
List.of(), List.of(), "Read test tool.");
+        IllegalStateException actual = 
assertThrows(IllegalStateException.class,
+                () -> 
MCPResourceNavigationDescriptorValidator.validate(List.of(navigation), 
createCatalog()));
+        assertThat(actual.getMessage(), is("Resource navigation references 
unknown source `missing`."));
+    }
+    
+    @Test
+    void assertValidateWithDuplicateRequiredArguments() {
+        MCPResourceNavigationDescriptor navigation = new 
MCPResourceNavigationDescriptor("database_gateway_test_tool", 
"database_gateway_test_tool",
+                List.of("database", "database"), List.of(), "Read test tool.");
+        IllegalStateException actual = 
assertThrows(IllegalStateException.class,
+                () -> 
MCPResourceNavigationDescriptorValidator.validate(List.of(navigation), 
createCatalog()));
+        assertThat(actual.getMessage(), is("Resource navigation 
`database_gateway_test_tool` to `database_gateway_test_tool` must not contain 
duplicate required arguments."));
+    }
+    
+    @Test
+    void assertValidateWithDuplicateCarriedArguments() {
+        MCPResourceNavigationDescriptor navigation = new 
MCPResourceNavigationDescriptor("database_gateway_test_tool", 
"database_gateway_test_tool",
+                List.of(), List.of("database", "database"), "Read test tool.");
+        IllegalStateException actual = 
assertThrows(IllegalStateException.class,
+                () -> 
MCPResourceNavigationDescriptorValidator.validate(List.of(navigation), 
createCatalog()));
+        assertThat(actual.getMessage(), is("Resource navigation 
`database_gateway_test_tool` to `database_gateway_test_tool` must not contain 
duplicate carried arguments."));
+    }
+    
+    private MCPDescriptorCatalog createCatalog() {
         MCPToolDescriptor toolDescriptor = new 
MCPToolDescriptor("database_gateway_test_tool", "Test Tool", "Run a test tool.",
                 Map.of("type", "object", "properties", Map.of("query", 
Map.of("type", "string", "description", "Query.")),
                         "required", List.of("query"), "additionalProperties", 
false),
                 Map.of("type", "object", "properties", Map.of("status", 
Map.of("type", "string", "description", "Status.")), "examples", 
List.of(Map.of("status", "ok"))),
                 MCPToolAnnotations.builder().title("Test 
Tool").readOnlyHint(true).destructiveHint(false).idempotentHint(true).openWorldHint(true).build(),
 Map.of());
-        MCPDescriptorCatalog catalog = new MCPDescriptorCatalog(new 
MCPProtocolDescriptorCatalog(List.of(), List.of(), List.of(toolDescriptor), 
List.of()),
+        return new MCPDescriptorCatalog(new 
MCPProtocolDescriptorCatalog(List.of(), List.of(), List.of(toolDescriptor), 
List.of()),
                 new MCPShardingSphereDescriptorCatalog(List.of(), List.of(), 
List.of(), List.of(), List.of()));
-        IllegalStateException actual = 
assertThrows(IllegalStateException.class,
-                () -> 
MCPResourceNavigationDescriptorValidator.validate(List.of(navigation), 
catalog));
-        assertThat(actual.getMessage(), is("Resource navigation references 
unknown source `missing`."));
     }
 }
diff --git 
a/mode/core/src/main/java/org/apache/shardingsphere/mode/metadata/manager/rule/DatabaseRuleConfigurationManager.java
 
b/mode/core/src/main/java/org/apache/shardingsphere/mode/metadata/manager/rule/DatabaseRuleConfigurationManager.java
index fecb43e7639..0b286ea4823 100644
--- 
a/mode/core/src/main/java/org/apache/shardingsphere/mode/metadata/manager/rule/DatabaseRuleConfigurationManager.java
+++ 
b/mode/core/src/main/java/org/apache/shardingsphere/mode/metadata/manager/rule/DatabaseRuleConfigurationManager.java
@@ -22,6 +22,7 @@ import lombok.SneakyThrows;
 import org.apache.shardingsphere.infra.config.rule.RuleConfiguration;
 import 
org.apache.shardingsphere.infra.config.rule.checker.DatabaseRuleConfigurationEmptyChecker;
 import 
org.apache.shardingsphere.infra.config.rule.scope.DatabaseRuleConfiguration;
+import 
org.apache.shardingsphere.infra.config.rule.validator.RuleConfigurationValidator;
 import org.apache.shardingsphere.infra.instance.ComputeNodeInstanceContext;
 import org.apache.shardingsphere.infra.rule.PartialRuleUpdateSupported;
 import org.apache.shardingsphere.infra.rule.ShardingSphereRule;
@@ -62,6 +63,7 @@ public final class DatabaseRuleConfigurationManager {
             refreshMetadata(databaseName, ruleConfig, false, rules);
             return;
         }
+        RuleConfigurationValidator.validate(ruleConfig);
         Optional<ShardingSphereRule> toBeChangedRule = 
rules.stream().filter(each -> 
each.getConfiguration().getClass().equals(ruleConfig.getClass())).findFirst();
         if (toBeChangedRule.isPresent() && toBeChangedRule.get() instanceof 
PartialRuleUpdateSupported) {
             boolean needRefreshSchemas = ((PartialRuleUpdateSupported) 
toBeChangedRule.get()).partialUpdate(ruleConfig);
diff --git 
a/mode/core/src/main/java/org/apache/shardingsphere/mode/metadata/manager/rule/GlobalConfigurationManager.java
 
b/mode/core/src/main/java/org/apache/shardingsphere/mode/metadata/manager/rule/GlobalConfigurationManager.java
index d32362a165c..51580f14b95 100644
--- 
a/mode/core/src/main/java/org/apache/shardingsphere/mode/metadata/manager/rule/GlobalConfigurationManager.java
+++ 
b/mode/core/src/main/java/org/apache/shardingsphere/mode/metadata/manager/rule/GlobalConfigurationManager.java
@@ -24,6 +24,7 @@ import 
org.apache.shardingsphere.infra.config.props.MetadataIdentifierCaseSensit
 import 
org.apache.shardingsphere.infra.config.props.temporary.TemporaryConfigurationProperties;
 import 
org.apache.shardingsphere.infra.config.props.temporary.TemporaryConfigurationPropertyKey;
 import org.apache.shardingsphere.infra.config.rule.RuleConfiguration;
+import 
org.apache.shardingsphere.infra.config.rule.validator.RuleConfigurationValidator;
 import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
 import org.apache.shardingsphere.infra.rule.ShardingSphereRule;
 import org.apache.shardingsphere.infra.rule.builder.global.GlobalRulesBuilder;
@@ -55,6 +56,7 @@ public final class GlobalConfigurationManager {
         if (null == ruleConfig) {
             return;
         }
+        RuleConfigurationValidator.validate(ruleConfig);
         Collection<ShardingSphereRule> rules = removeGlobalRule(ruleConfig, 
metaDataContexts.getMetaData().getGlobalRuleMetaData().getRules());
         rules.addAll(GlobalRulesBuilder.buildSingleRules(ruleConfig, 
metaDataContexts.getMetaData().getAllDatabases(), 
metaDataContexts.getMetaData().getProps()));
         
metaDataContexts.getMetaData().getGlobalRuleMetaData().getRules().clear();
diff --git 
a/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/manager/resource/StorageUnitManagerTest.java
 
b/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/manager/resource/StorageUnitManagerTest.java
index 8c9a267aaa7..bafe3c8c14d 100644
--- 
a/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/manager/resource/StorageUnitManagerTest.java
+++ 
b/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/manager/resource/StorageUnitManagerTest.java
@@ -77,7 +77,8 @@ class StorageUnitManagerTest {
         SwitchingResource switchingResource = new 
SwitchingResource(Collections.emptyMap(), Collections.emptyMap(), 
Collections.emptyList(), Collections.emptyMap());
         
when(resourceSwitchManager.switchByRegisterStorageUnit(any(ResourceMetaData.class),
 any(Map.class), anyBoolean())).thenReturn(switchingResource);
         ShardingSphereDatabase reloadDatabase = 
mock(ShardingSphereDatabase.class);
-        
when(reloadDatabase.getAllSchemas()).thenReturn(Collections.singleton(new 
ShardingSphereSchema("foo_schema", mock(DatabaseType.class))));
+        ShardingSphereSchema schema = new ShardingSphereSchema("foo_schema", 
mock(DatabaseType.class));
+        
when(reloadDatabase.getAllSchemas()).thenReturn(Collections.singleton(schema));
         
when(reloadDatabase.getProtocolType()).thenReturn(TypedSPILoader.getService(DatabaseType.class,
 "FIXTURE"));
         MetaDataContexts reloadMetaDataContexts = mock(MetaDataContexts.class, 
RETURNS_DEEP_STUBS);
         
when(reloadMetaDataContexts.getMetaData().getDatabase(DATABASE_NAME)).thenReturn(reloadDatabase);
@@ -132,7 +133,8 @@ class StorageUnitManagerTest {
         MetaDataContexts reloadMetaDataContexts = mock(MetaDataContexts.class, 
RETURNS_DEEP_STUBS);
         
when(reloadMetaDataContexts.getMetaData().getDatabase(DATABASE_NAME)).thenReturn(reloadDatabase);
         when(reloadMetaDataContexts.getMetaData().getProps()).thenReturn(new 
ConfigurationProperties(new Properties()));
-        
when(reloadDatabase.getAllSchemas()).thenReturn(Collections.singleton(new 
ShardingSphereSchema("foo_schema", mock(DatabaseType.class))));
+        ShardingSphereSchema schema = new ShardingSphereSchema("foo_schema", 
mock(DatabaseType.class));
+        
when(reloadDatabase.getAllSchemas()).thenReturn(Collections.singleton(schema));
         try (
                 MockedConstruction<MetaDataContextsFactory> ignored = 
mockConstruction(MetaDataContextsFactory.class,
                         (mock, context) -> 
when(mock.createBySwitchResource(DATABASE_NAME, switchingResource, 
metaDataContexts)).thenReturn(reloadMetaDataContexts))) {
@@ -164,7 +166,8 @@ class StorageUnitManagerTest {
         MetaDataContexts reloadMetaDataContexts = mock(MetaDataContexts.class, 
RETURNS_DEEP_STUBS);
         
when(reloadMetaDataContexts.getMetaData().getDatabase(DATABASE_NAME)).thenReturn(reloadDatabase);
         when(reloadMetaDataContexts.getMetaData().getProps()).thenReturn(new 
ConfigurationProperties(new Properties()));
-        
when(reloadDatabase.getAllSchemas()).thenReturn(Collections.singleton(new 
ShardingSphereSchema("foo_schema", mock(DatabaseType.class))));
+        ShardingSphereSchema schema = new ShardingSphereSchema("foo_schema", 
mock(DatabaseType.class));
+        
when(reloadDatabase.getAllSchemas()).thenReturn(Collections.singleton(schema));
         try (
                 MockedConstruction<MetaDataContextsFactory> ignored = 
mockConstruction(MetaDataContextsFactory.class,
                         (mock, context) -> 
when(mock.createBySwitchResource(DATABASE_NAME, switchingResource, 
metaDataContexts)).thenReturn(reloadMetaDataContexts))) {
diff --git 
a/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/manager/rule/DatabaseRuleConfigurationManagerTest.java
 
b/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/manager/rule/DatabaseRuleConfigurationManagerTest.java
index a8a40bfa1d6..f9d10430885 100644
--- 
a/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/manager/rule/DatabaseRuleConfigurationManagerTest.java
+++ 
b/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/manager/rule/DatabaseRuleConfigurationManagerTest.java
@@ -20,6 +20,8 @@ package org.apache.shardingsphere.mode.metadata.manager.rule;
 import org.apache.shardingsphere.infra.config.rule.RuleConfiguration;
 import 
org.apache.shardingsphere.infra.config.rule.checker.DatabaseRuleConfigurationEmptyChecker;
 import 
org.apache.shardingsphere.infra.config.rule.scope.DatabaseRuleConfiguration;
+import 
org.apache.shardingsphere.infra.config.rule.validator.RuleConfigurationValidator;
+import 
org.apache.shardingsphere.infra.exception.kernel.metadata.rule.InvalidRuleConfigurationException;
 import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData;
 import org.apache.shardingsphere.infra.rule.PartialRuleUpdateSupported;
 import org.apache.shardingsphere.infra.rule.ShardingSphereRule;
@@ -32,6 +34,7 @@ import 
org.apache.shardingsphere.test.infra.framework.extension.mock.StaticMockS
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
 
 import java.io.Serializable;
 import java.sql.SQLException;
@@ -49,6 +52,7 @@ import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -89,6 +93,21 @@ class DatabaseRuleConfigurationManagerTest {
         }
     }
     
+    @Test
+    void assertRefreshWithInvalidRuleConfiguration() {
+        RuleConfiguration ruleConfig = mock(RuleConfiguration.class);
+        ShardingSphereRule partialRule = mock(ShardingSphereRule.class, 
withSettings().extraInterfaces(PartialRuleUpdateSupported.class));
+        MetaDataContexts metaDataContexts = mock(MetaDataContexts.class, 
RETURNS_DEEP_STUBS);
+        
when(metaDataContexts.getMetaData().getDatabase(DATABASE_NAME).getRuleMetaData()).thenReturn(new
 RuleMetaData(Collections.singleton(partialRule)));
+        DatabaseRuleConfigurationManager manager = new 
DatabaseRuleConfigurationManager(metaDataContexts, mock(), mock());
+        try (MockedStatic<RuleConfigurationValidator> mockedValidator = 
mockStatic(RuleConfigurationValidator.class)) {
+            mockedValidator.when(() -> 
RuleConfigurationValidator.validate(ruleConfig)).thenThrow(new 
InvalidRuleConfigurationException("fixture", "invalid"));
+            assertThrows(InvalidRuleConfigurationException.class, () -> 
manager.refresh(DATABASE_NAME, ruleConfig));
+        }
+        verify((PartialRuleUpdateSupported) partialRule, 
never()).partialUpdate(ruleConfig);
+        verify(metaDataContexts, never()).update(any(MetaDataContexts.class));
+    }
+    
     @Test
     void assertRefreshWithPartialUpdateSkipMetadata() throws SQLException {
         DatabaseRuleConfiguration ruleConfig = 
mockDatabaseRuleConfiguration(false);
diff --git 
a/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/manager/rule/GlobalConfigurationManagerTest.java
 
b/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/manager/rule/GlobalConfigurationManagerTest.java
index 9074b5a18bf..0bed0e2d0f8 100644
--- 
a/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/manager/rule/GlobalConfigurationManagerTest.java
+++ 
b/mode/core/src/test/java/org/apache/shardingsphere/mode/metadata/manager/rule/GlobalConfigurationManagerTest.java
@@ -22,6 +22,8 @@ import 
org.apache.shardingsphere.infra.config.props.ConfigurationProperties;
 import 
org.apache.shardingsphere.infra.config.props.temporary.TemporaryConfigurationProperties;
 import 
org.apache.shardingsphere.infra.config.props.temporary.TemporaryConfigurationPropertyKey;
 import org.apache.shardingsphere.infra.config.rule.RuleConfiguration;
+import 
org.apache.shardingsphere.infra.config.rule.validator.RuleConfigurationValidator;
+import 
org.apache.shardingsphere.infra.exception.kernel.metadata.rule.InvalidRuleConfigurationException;
 import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
 import 
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
 import 
org.apache.shardingsphere.infra.metadata.database.resource.ResourceMetaData;
@@ -39,6 +41,7 @@ import 
org.apache.shardingsphere.test.infra.framework.extension.mock.AutoMockExt
 import 
org.apache.shardingsphere.test.infra.framework.extension.mock.StaticMockSettings;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
 
 import java.io.Serializable;
 import java.util.Arrays;
@@ -56,6 +59,7 @@ import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -75,6 +79,19 @@ class GlobalConfigurationManagerTest {
         verify(metaDataContexts, 
never()).update(any(ShardingSphereMetaData.class), 
any(MetaDataPersistFacade.class));
     }
     
+    @Test
+    void assertAlterGlobalRuleConfigurationWithInvalidConfiguration() {
+        RuleConfiguration ruleConfig = mock(RuleConfiguration.class);
+        MetaDataContexts metaDataContexts = mock(MetaDataContexts.class);
+        GlobalConfigurationManager manager = new 
GlobalConfigurationManager(metaDataContexts, mock(MetaDataPersistFacade.class));
+        try (MockedStatic<RuleConfigurationValidator> mockedValidator = 
mockStatic(RuleConfigurationValidator.class)) {
+            mockedValidator.when(() -> 
RuleConfigurationValidator.validate(ruleConfig)).thenThrow(new 
InvalidRuleConfigurationException("fixture", "invalid"));
+            assertThrows(InvalidRuleConfigurationException.class, () -> 
manager.alterGlobalRuleConfiguration(ruleConfig));
+        }
+        verify(metaDataContexts, never()).getMetaData();
+        verify(metaDataContexts, 
never()).update(any(ShardingSphereMetaData.class), 
any(MetaDataPersistFacade.class));
+    }
+    
     @Test
     void assertAlterGlobalRuleConfigurationReplacesRules() {
         RuleConfiguration newConfig = mock(RuleConfiguration.class, 
withSettings().extraInterfaces(Serializable.class));
diff --git a/pom.xml b/pom.xml
index 729c37b5ffb..d49250ba1a6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -104,6 +104,7 @@
         <bytebuddy.version>1.18.8</bytebuddy.version>
         
         <jakarta.jakartaee-bom.version>8.0.0</jakarta.jakartaee-bom.version>
+        <bval.version>2.0.6</bval.version>
         
         <netty.version>4.2.9.Final</netty.version>
         <bouncycastle.version>1.84</bouncycastle.version>
@@ -311,6 +312,11 @@
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
+            <dependency>
+                <groupId>org.apache.bval</groupId>
+                <artifactId>bval-jsr</artifactId>
+                <version>${bval.version}</version>
+            </dependency>
             
             <dependency>
                 <groupId>org.freemarker</groupId>

Reply via email to