feat(domain): add SlugValidator with comprehensive validation rules

This commit is contained in:
vsxd 2026-03-12 01:53:08 +08:00
parent 95c8c31561
commit 351829e1a9
3 changed files with 98 additions and 0 deletions

View file

@ -15,5 +15,10 @@
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,36 @@
package com.iflytek.skillhub.domain.namespace;
import java.util.Set;
import java.util.regex.Pattern;
public class SlugValidator {
private static final int MIN_LENGTH = 2;
private static final int MAX_LENGTH = 64;
private static final Pattern SLUG_PATTERN = Pattern.compile("^[a-z0-9]([a-z0-9-]*[a-z0-9])?$");
private static final Set<String> RESERVED_SLUGS = Set.of(
"admin", "api", "dashboard", "search", "auth",
"me", "global", "system", "static", "assets", "health"
);
public static void validate(String slug) {
if (slug == null || slug.isBlank()) {
throw new IllegalArgumentException("Slug cannot be null or blank");
}
if (slug.length() < MIN_LENGTH || slug.length() > MAX_LENGTH) {
throw new IllegalArgumentException(
String.format("Slug length must be between %d and %d characters", MIN_LENGTH, MAX_LENGTH));
}
if (!SLUG_PATTERN.matcher(slug).matches()) {
throw new IllegalArgumentException(
"Slug must contain only lowercase alphanumeric characters and hyphens, " +
"and must start and end with an alphanumeric character");
}
if (slug.contains("--")) {
throw new IllegalArgumentException("Slug cannot contain consecutive hyphens");
}
if (RESERVED_SLUGS.contains(slug)) {
throw new IllegalArgumentException("Slug '" + slug + "' is reserved and cannot be used");
}
}
}

View file

@ -0,0 +1,57 @@
package com.iflytek.skillhub.domain.namespace;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class SlugValidatorTest {
@Test
void shouldAcceptValidSlug() {
assertDoesNotThrow(() -> SlugValidator.validate("my-namespace"));
assertDoesNotThrow(() -> SlugValidator.validate("ab"));
assertDoesNotThrow(() -> SlugValidator.validate("test123"));
assertDoesNotThrow(() -> SlugValidator.validate("my-team-2024"));
}
@Test
void shouldRejectTooShort() {
Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("a"));
assertTrue(ex.getMessage().contains("length"));
}
@Test
void shouldRejectTooLong() {
String longSlug = "a".repeat(65);
Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate(longSlug));
assertTrue(ex.getMessage().contains("length"));
}
@Test
void shouldRejectUpperCase() {
Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("MyNamespace"));
assertTrue(ex.getMessage().contains("lowercase"));
}
@Test
void shouldRejectStartingWithHyphen() {
Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("-namespace"));
assertTrue(ex.getMessage().contains("alphanumeric"));
}
@Test
void shouldRejectEndingWithHyphen() {
Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("namespace-"));
assertTrue(ex.getMessage().contains("alphanumeric"));
}
@Test
void shouldRejectDoubleHyphen() {
Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("my--namespace"));
assertTrue(ex.getMessage().contains("consecutive"));
}
@Test
void shouldRejectReservedWords() {
assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("admin"));
assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("api"));
assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("global"));
assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("system"));
}
@Test
void shouldRejectSpecialCharacters() {
Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("my_namespace"));
assertTrue(ex.getMessage().contains("lowercase") || ex.getMessage().contains("alphanumeric"));
}
}