diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/core/domain/OAuth2ModuleFactory.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/core/domain/OAuth2ModuleFactory.java index 1735611132c..8d9a76cbcbd 100644 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/core/domain/OAuth2ModuleFactory.java +++ b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/security/oauth2/core/domain/OAuth2ModuleFactory.java @@ -3,6 +3,7 @@ import static tech.jhipster.lite.generator.server.springboot.mvc.security.common.domain.AuthenticationModulesFactory.authenticationModuleBuilder; import static tech.jhipster.lite.module.domain.JHipsterModule.*; +import java.util.regex.Pattern; import tech.jhipster.lite.module.domain.JHipsterModule; import tech.jhipster.lite.module.domain.docker.DockerImageVersion; import tech.jhipster.lite.module.domain.docker.DockerImages; @@ -21,6 +22,7 @@ public class OAuth2ModuleFactory { public static final String CLIENT_SCOPE_NAME = "keycloakClientScopeName"; public static final String DEFAULT_CLIENT_SCOPE_NAME = "jhipster"; + private static final Pattern NAME_FORMAT = Pattern.compile("^[a-z0-9-]+$"); private static final TextNeedleBeforeReplacer IMPORT_NEEDLE = lineBeforeText( "import org.springframework.boot.test.context.SpringBootTest;" ); @@ -52,8 +54,8 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) { var realmName = properties.getOrDefaultString(REALM_NAME, DEFAULT_REALM_NAME); var clientScopeName = properties.getOrDefaultString(CLIENT_SCOPE_NAME, DEFAULT_CLIENT_SCOPE_NAME); - Assert.field(REALM_NAME, realmName).notBlank().noWhitespace().maxLength(30).urlSafeSingleWord(); - Assert.field(CLIENT_SCOPE_NAME, clientScopeName).notBlank().noWhitespace().maxLength(30).urlSafeSingleWord(); + Assert.field(REALM_NAME, realmName).notNull().matchesPattern(NAME_FORMAT).maxLength(30); + Assert.field(CLIENT_SCOPE_NAME, clientScopeName).notNull().matchesPattern(NAME_FORMAT).maxLength(30); JHipsterModuleBuilder builder = authenticationModuleBuilder(properties); diff --git a/src/main/java/tech/jhipster/lite/module/domain/JHipsterSlug.java b/src/main/java/tech/jhipster/lite/module/domain/JHipsterSlug.java index 1138c325a7d..efbd6b9aa66 100644 --- a/src/main/java/tech/jhipster/lite/module/domain/JHipsterSlug.java +++ b/src/main/java/tech/jhipster/lite/module/domain/JHipsterSlug.java @@ -1,5 +1,6 @@ package tech.jhipster.lite.module.domain; +import java.util.regex.Pattern; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import tech.jhipster.lite.shared.error.domain.Assert; @@ -7,10 +8,12 @@ public abstract sealed class JHipsterSlug implements Comparable permits JHipsterModuleSlug, JHipsterFeatureSlug { + private static final Pattern SLUG_FORMAT = Pattern.compile("^[a-z0-9-]+$"); + private final String slug; protected JHipsterSlug(String slug) { - Assert.field("slug", slug).notBlank().urlSafeSingleWord(() -> new InvalidJHipsterSlugException(slug)); + Assert.field("slug", slug).notBlank().matchesPatternOrThrow(SLUG_FORMAT, () -> new InvalidJHipsterSlugException(slug)); this.slug = slug; } diff --git a/src/main/java/tech/jhipster/lite/module/domain/properties/JHipsterProjectBaseName.java b/src/main/java/tech/jhipster/lite/module/domain/properties/JHipsterProjectBaseName.java index f885bf6f157..159aaa5bd3c 100644 --- a/src/main/java/tech/jhipster/lite/module/domain/properties/JHipsterProjectBaseName.java +++ b/src/main/java/tech/jhipster/lite/module/domain/properties/JHipsterProjectBaseName.java @@ -1,9 +1,11 @@ package tech.jhipster.lite.module.domain.properties; +import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import tech.jhipster.lite.shared.error.domain.Assert; public record JHipsterProjectBaseName(String name) { + private static final Pattern NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9]+$"); private static final String DEFAULT_NAME = "jhipster"; public static final JHipsterProjectBaseName DEFAULT = new JHipsterProjectBaseName(DEFAULT_NAME); @@ -17,7 +19,7 @@ private static String buildName(String name) { return DEFAULT_NAME; } - Assert.field("baseName", name).namePattern(InvalidProjectBaseNameException::new); + Assert.field("baseName", name).matchesPatternOrThrow(NAME_PATTERN, InvalidProjectBaseNameException::new); return name; } diff --git a/src/main/java/tech/jhipster/lite/module/domain/resource/JHipsterModuleTag.java b/src/main/java/tech/jhipster/lite/module/domain/resource/JHipsterModuleTag.java index 1c77c75a0de..fcf8886bcbd 100644 --- a/src/main/java/tech/jhipster/lite/module/domain/resource/JHipsterModuleTag.java +++ b/src/main/java/tech/jhipster/lite/module/domain/resource/JHipsterModuleTag.java @@ -1,10 +1,13 @@ package tech.jhipster.lite.module.domain.resource; +import java.util.regex.Pattern; import tech.jhipster.lite.shared.error.domain.Assert; public record JHipsterModuleTag(String tag) { + private static final Pattern TAG_FORMAT = Pattern.compile("^[a-z0-9-]+$"); + public JHipsterModuleTag { - Assert.field("tag", tag).notNull().noWhitespace().maxLength(15).urlSafeSingleWord(() -> new InvalidJHipsterModuleTagException(tag)); + Assert.field("tag", tag).notNull().maxLength(15).matchesPatternOrThrow(TAG_FORMAT, () -> new InvalidJHipsterModuleTagException(tag)); } public String get() { diff --git a/src/main/java/tech/jhipster/lite/shared/error/domain/Assert.java b/src/main/java/tech/jhipster/lite/shared/error/domain/Assert.java index 6ea169fdd45..b47cd870734 100644 --- a/src/main/java/tech/jhipster/lite/shared/error/domain/Assert.java +++ b/src/main/java/tech/jhipster/lite/shared/error/domain/Assert.java @@ -315,9 +315,6 @@ public static InstantAsserter field(String field, Instant input) { public static final class StringAsserter { public static final Pattern PATTERN_SPACE = Pattern.compile("\\s"); - private static final Pattern TAG_FORMAT = Pattern.compile("^[a-z0-9-]+$"); - private static final Pattern NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9]+$"); - private final String field; private final String value; @@ -374,51 +371,6 @@ public StringAsserter noWhitespace() { return this; } - /** - * Ensure that the value only contains lower case letters or numbers, no whitespace or any separator. - * - * @return The current asserter - * @throws UrlSafeSingleWordException - * if the value contain any special character - */ - public StringAsserter urlSafeSingleWord() { - return urlSafeSingleWord(() -> new UrlSafeSingleWordException(field)); - } - - /** - * Ensure that the value only contains lower case letters or numbers, no - * whitespace or any separator. - * - * @param exceptionCreator - * the exception to be thrown, if the constraint is not valid. - * @return The current asserter - * @throws the exception returned by the supplier if the value contain any - * special character - */ - public StringAsserter urlSafeSingleWord(Supplier exceptionCreator) { - if (!TAG_FORMAT.matcher(value).matches()) { - throw exceptionCreator.get(); - } - return this; - } - - /** - * Ensure that the value only contains ASCII letters or numbers, no - * whitespace or any separator. - * - * @param exceptionCreator - * the exception to be thrown, if the constraint is not valid. - * @return The current asserter - * @throws the exception returned by the supplier if the value contain any - * special character - */ - public StringAsserter namePattern(Supplier exceptionCreator) { - if (!NAME_PATTERN.matcher(value).matches()) { - throw exceptionCreator.get(); - } - return this; - } - /** * Ensure that the input value is at least of the given length * @@ -465,6 +417,41 @@ public StringAsserter maxLength(int length) { return this; } + + /** + * Ensure that the given input value matches the given pattern + * + * @param pattern pattern of the {@link String} + * @return The current asserter + * @throws NotMatchingExpectedPatternException + * if the value does not match the provided pattern + */ + public StringAsserter matchesPattern(Pattern pattern) { + return matchesPatternOrThrow( + pattern, + () -> NotMatchingExpectedPatternException.builder().field(field).value(value).pattern(pattern).build() + ); + } + + /** + * Ensure that the given input value matches the given pattern + * + * @param pattern pattern of the {@link String} + * @param exceptionSupplier supplier of the exception to throw if the value does not match the pattern + * @return The current asserter + * @throws X provided exception if the value does not match the provided pattern + */ + public StringAsserter matchesPatternOrThrow(Pattern pattern, Supplier exceptionSupplier) { + if (value == null) { + return this; + } + + if (!pattern.matcher(value).matches()) { + throw exceptionSupplier.get(); + } + + return this; + } } /** diff --git a/src/main/java/tech/jhipster/lite/shared/error/domain/AssertionErrorType.java b/src/main/java/tech/jhipster/lite/shared/error/domain/AssertionErrorType.java index f2383597d59..0b5f8d6f3d7 100644 --- a/src/main/java/tech/jhipster/lite/shared/error/domain/AssertionErrorType.java +++ b/src/main/java/tech/jhipster/lite/shared/error/domain/AssertionErrorType.java @@ -7,9 +7,9 @@ public enum AssertionErrorType { NULL_ELEMENT_IN_COLLECTION, NUMBER_VALUE_TOO_HIGH, NUMBER_VALUE_TOO_LOW, + STRING_NOT_MATCHING_PATTERN, STRING_TOO_LONG, STRING_TOO_SHORT, STRING_WITH_WHITESPACES, TOO_MANY_ELEMENTS, - URL_SAFE_SINGLE_WORD, } diff --git a/src/main/java/tech/jhipster/lite/shared/error/domain/NotMatchingExpectedPatternException.java b/src/main/java/tech/jhipster/lite/shared/error/domain/NotMatchingExpectedPatternException.java new file mode 100644 index 00000000000..ddef3afc954 --- /dev/null +++ b/src/main/java/tech/jhipster/lite/shared/error/domain/NotMatchingExpectedPatternException.java @@ -0,0 +1,63 @@ +package tech.jhipster.lite.shared.error.domain; + +import java.util.Map; +import java.util.regex.Pattern; + +public final class NotMatchingExpectedPatternException extends AssertionException { + + private final String pattern; + + private NotMatchingExpectedPatternException(NotMatchingExpectedPatternExceptionBuilder builder) { + super(builder.field, builder.message()); + pattern = builder.pattern; + } + + public static NotMatchingExpectedPatternExceptionBuilder builder() { + return new NotMatchingExpectedPatternExceptionBuilder(); + } + + public static final class NotMatchingExpectedPatternExceptionBuilder { + + private String value; + private String pattern; + private String field; + + private NotMatchingExpectedPatternExceptionBuilder() {} + + NotMatchingExpectedPatternExceptionBuilder field(String field) { + this.field = field; + + return this; + } + + NotMatchingExpectedPatternExceptionBuilder value(String value) { + this.value = value; + + return this; + } + + NotMatchingExpectedPatternExceptionBuilder pattern(Pattern pattern) { + this.pattern = pattern.pattern(); + + return this; + } + + private String message() { + return "The value \"" + value + "\" in field \"" + field + "\" must match the pattern " + pattern; + } + + public NotMatchingExpectedPatternException build() { + return new NotMatchingExpectedPatternException(this); + } + } + + @Override + public AssertionErrorType type() { + return AssertionErrorType.STRING_NOT_MATCHING_PATTERN; + } + + @Override + public Map parameters() { + return Map.of("pattern", pattern); + } +} diff --git a/src/main/java/tech/jhipster/lite/shared/error/domain/UrlSafeSingleWordException.java b/src/main/java/tech/jhipster/lite/shared/error/domain/UrlSafeSingleWordException.java deleted file mode 100644 index 65bd72bf70f..00000000000 --- a/src/main/java/tech/jhipster/lite/shared/error/domain/UrlSafeSingleWordException.java +++ /dev/null @@ -1,17 +0,0 @@ -package tech.jhipster.lite.shared.error.domain; - -public class UrlSafeSingleWordException extends AssertionException { - - public UrlSafeSingleWordException(String field) { - super(field, message(field)); - } - - private static String message(String field) { - return "The field \"%s\" is not a single word containing only lower case characters and numbers".formatted(field); - } - - @Override - public AssertionErrorType type() { - return AssertionErrorType.URL_SAFE_SINGLE_WORD; - } -} diff --git a/src/main/resources/messages/assertions-errors/assertion-errors-messages.properties b/src/main/resources/messages/assertions-errors/assertion-errors-messages.properties index e11cf47ca27..af994cff457 100644 --- a/src/main/resources/messages/assertions-errors/assertion-errors-messages.properties +++ b/src/main/resources/messages/assertions-errors/assertion-errors-messages.properties @@ -31,5 +31,5 @@ assertion-error.STRING_WITH_WHITESPACES.title=String with whitespaces assertion-error.TOO_MANY_ELEMENTS.detail=There is too many elements in {{ field }}, max is {{ maxSize }} (current {{ currentSize }} element(s)) assertion-error.TOO_MANY_ELEMENTS.title=Too many elements -assertion-error.URL_SAFE_SINGLE_WORD.detail=The string {{ field }} must only have lower case characters and numbers -assertion-error.URL_SAFE_SINGLE_WORD.title=String with special characters +assertion-error.STRING_NOT_MATCHING_PATTERN.detail=The string {{ field }} must match the pattern {{ pattern }} +assertion-error.STRING_NOT_MATCHING_PATTERN.title=String not matching the expected pattern diff --git a/src/main/resources/messages/assertions-errors/assertion-errors-messages_fr.properties b/src/main/resources/messages/assertions-errors/assertion-errors-messages_fr.properties index 0a79ac3460a..3fc35f35741 100644 --- a/src/main/resources/messages/assertions-errors/assertion-errors-messages_fr.properties +++ b/src/main/resources/messages/assertions-errors/assertion-errors-messages_fr.properties @@ -32,5 +32,5 @@ assertion-error.STRING_WITH_WHITESPACES.title=Chaîne avec des espaces assertion-error.TOO_MANY_ELEMENTS.detail=Il y a trop d'éléments dans {{ field }}, le maximum est de {{ maxSize }} (il y a actuellement {{ currentSize }} élément(s)) assertion-error.TOO_MANY_ELEMENTS.title=Trop d'éléments -assertion-error.URL_SAFE_SINGLE_WORD.detail=The string {{ field }} must only have lower case characters and numbers -assertion-error.URL_SAFE_SINGLE_WORD.title=String with special characters +assertion-error.STRING_NOT_MATCHING_PATTERN.detail=La châine {{ field }} doit correspondre au modèle {{ pattern }} +assertion-error.STRING_NOT_MATCHING_PATTERN.title=Chaîne ne correspondant pas au modèle attendu diff --git a/src/test/java/tech/jhipster/lite/module/domain/JHipsterTagsTest.java b/src/test/java/tech/jhipster/lite/module/domain/JHipsterTagsTest.java deleted file mode 100644 index 99681d31129..00000000000 --- a/src/test/java/tech/jhipster/lite/module/domain/JHipsterTagsTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package tech.jhipster.lite.module.domain; - -import static org.assertj.core.api.Assertions.*; - -import org.junit.jupiter.api.Test; -import tech.jhipster.lite.UnitTest; -import tech.jhipster.lite.module.domain.resource.JHipsterModuleTags; -import tech.jhipster.lite.module.domain.resource.JHipsterModuleTags.JHipsterModuleTagsBuilder; -import tech.jhipster.lite.shared.error.domain.StringWithWhitespacesException; - -@UnitTest -class JHipsterTagsTest { - - @Test - void shouldNotBeValidWithWhitespace() { - JHipsterModuleTagsBuilder builder = JHipsterModuleTags.builder(); - - assertThatThrownBy(() -> builder.add("my tag").build()).isInstanceOf(StringWithWhitespacesException.class); - } -} diff --git a/src/test/java/tech/jhipster/lite/module/domain/resource/JHipsterModuleTagTest.java b/src/test/java/tech/jhipster/lite/module/domain/resource/JHipsterModuleTagTest.java index f241568fe2f..eba9b4d2b52 100644 --- a/src/test/java/tech/jhipster/lite/module/domain/resource/JHipsterModuleTagTest.java +++ b/src/test/java/tech/jhipster/lite/module/domain/resource/JHipsterModuleTagTest.java @@ -1,6 +1,7 @@ package tech.jhipster.lite.module.domain.resource; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.api.Test; @@ -9,7 +10,6 @@ import tech.jhipster.lite.UnitTest; import tech.jhipster.lite.shared.error.domain.MissingMandatoryValueException; import tech.jhipster.lite.shared.error.domain.StringTooLongException; -import tech.jhipster.lite.shared.error.domain.StringWithWhitespacesException; @UnitTest class JHipsterModuleTagTest { @@ -21,7 +21,7 @@ void shouldNotBuildWithoutTag() { @Test void shouldNotBuildWithWhitespace() { - assertThatThrownBy(() -> new JHipsterModuleTag("my tag")).isInstanceOf(StringWithWhitespacesException.class); + assertThatThrownBy(() -> new JHipsterModuleTag("my tag")).isInstanceOf(InvalidJHipsterModuleTagException.class); } @Test diff --git a/src/test/java/tech/jhipster/lite/shared/error/domain/AssertTest.java b/src/test/java/tech/jhipster/lite/shared/error/domain/AssertTest.java index 9081454d648..bcc4baaea08 100644 --- a/src/test/java/tech/jhipster/lite/shared/error/domain/AssertTest.java +++ b/src/test/java/tech/jhipster/lite/shared/error/domain/AssertTest.java @@ -5,12 +5,9 @@ import java.math.BigDecimal; import java.time.Instant; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; +import java.util.*; +import java.util.regex.Pattern; +import org.junit.jupiter.api.*; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import tech.jhipster.lite.UnitTest; @@ -143,16 +140,23 @@ void shouldNotValidateTooShortStringValue() { } @Test - void shouldNotValidateUrlSafeSingleWord() { - assertThatThrownBy(() -> Assert.field(FIELD_NAME, "mytag?").urlSafeSingleWord()) - .isExactlyInstanceOf(UrlSafeSingleWordException.class) + void shouldNotValidateStringNotMatchingPattern() { + assertThatThrownBy(() -> Assert.field(FIELD_NAME, "mytag?").matchesPattern(Pattern.compile("^[a-z0-9-]+$"))) + .isExactlyInstanceOf(NotMatchingExpectedPatternException.class) .hasMessageContaining(FIELD_NAME) - .hasMessageContaining("not a single word containing only lower case characters and"); + .hasMessageContaining("The value \"mytag?\" in field \"fieldName\" must match the pattern ^[a-z0-9-]+$"); } @Test - void shouldValidateUrlSafeSingleWord() { - assertThatCode(() -> Assert.field(FIELD_NAME, "my-tag").urlSafeSingleWord()).doesNotThrowAnyException(); + void shouldValidateStringNotMatchingPattern() { + assertThatCode(() -> Assert.field(FIELD_NAME, "my-tag").matchesPattern(Pattern.compile("^[a-z0-9-]+$"))).doesNotThrowAnyException(); + } + + @Test + void shouldValidateNullStringWhenVerifyingMatchingPattern() { + assertThatCode( + () -> Assert.field(FIELD_NAME, (String) null).matchesPattern(Pattern.compile("^[a-z0-9-]+$")) + ).doesNotThrowAnyException(); } @Test diff --git a/src/test/java/tech/jhipster/lite/shared/error/infrastructure/primary/AssertionErrorsHandlerIT.java b/src/test/java/tech/jhipster/lite/shared/error/infrastructure/primary/AssertionErrorsHandlerIT.java index ef995072fdb..a19b01da898 100644 --- a/src/test/java/tech/jhipster/lite/shared/error/infrastructure/primary/AssertionErrorsHandlerIT.java +++ b/src/test/java/tech/jhipster/lite/shared/error/infrastructure/primary/AssertionErrorsHandlerIT.java @@ -1,7 +1,8 @@ package tech.jhipster.lite.shared.error.infrastructure.primary; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.Locale; import org.junit.jupiter.api.Test; @@ -118,13 +119,13 @@ void shouldHandleStringWithWhitespaces() throws Exception { } @Test - void shouldHandleUrlSafeSingleWords() throws Exception { + void shouldHandleStringNotMatchingExpectedPattern() throws Exception { rest - .perform(get("/api/assertion-errors/url-safe-single-word")) + .perform(get("/api/assertion-errors/string-not-matching-pattern")) .andExpect(status().isBadRequest()) - .andExpect(jsonPath("title").value("String with special characters")) - .andExpect(jsonPath("detail").value("The string myField must only have lower case characters and numbers")) - .andExpect(jsonPath("key").value("URL_SAFE_SINGLE_WORD")); + .andExpect(jsonPath("title").value("String not matching the expected pattern")) + .andExpect(jsonPath("detail").value("The string myField must match the pattern ^[a-z0-9-]+$")) + .andExpect(jsonPath("key").value("STRING_NOT_MATCHING_PATTERN")); } @Test diff --git a/src/test/java/tech/jhipster/lite/shared/error_generator/infrastructure/primary/AssertionsErrorsResource.java b/src/test/java/tech/jhipster/lite/shared/error_generator/infrastructure/primary/AssertionsErrorsResource.java index 91c474acd3b..3fedf1723fc 100644 --- a/src/test/java/tech/jhipster/lite/shared/error_generator/infrastructure/primary/AssertionsErrorsResource.java +++ b/src/test/java/tech/jhipster/lite/shared/error_generator/infrastructure/primary/AssertionsErrorsResource.java @@ -3,9 +3,8 @@ import java.time.Instant; import java.util.Arrays; import java.util.List; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import java.util.regex.Pattern; +import org.springframework.web.bind.annotation.*; import tech.jhipster.lite.shared.error.domain.Assert; import tech.jhipster.lite.shared.error_generator.domain.MissingMandatoryValueFactory; @@ -58,9 +57,9 @@ void stringWithWhitespace() { Assert.field("myField", "with whitespace").noWhitespace(); } - @GetMapping("url-safe-single-word") - void urlSafeSingleWord() { - Assert.field("myField", "WithSpecialCharacters?").urlSafeSingleWord(); + @GetMapping("string-not-matching-pattern") + void stringNotMatchingPattern() { + Assert.field("myField", "WithSpecialCharacters?").matchesPattern(Pattern.compile("^[a-z0-9-]+$")); } @GetMapping("not-after-time")