Skip to content

Commit

Permalink
Merge pull request #9909 from murdos/rework-assert-errors
Browse files Browse the repository at this point in the history
feat: add string not matching pattern field assertion
  • Loading branch information
pascalgrimaud authored May 26, 2024
2 parents d9201d0 + 6026b8b commit e5b6af8
Show file tree
Hide file tree
Showing 15 changed files with 150 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;"
);
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
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;
import tech.jhipster.lite.shared.generation.domain.ExcludeFromGeneratedCodeCoverage;

public abstract sealed class JHipsterSlug implements Comparable<JHipsterSlug> 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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down
83 changes: 35 additions & 48 deletions src/main/java/tech/jhipster/lite/shared/error/domain/Assert.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<RuntimeException> 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<RuntimeException> exceptionCreator) {
if (!NAME_PATTERN.matcher(value).matches()) {
throw exceptionCreator.get();
}
return this;
}

/**
* Ensure that the input value is at least of the given length
*
Expand Down Expand Up @@ -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 <X extends RuntimeException> StringAsserter matchesPatternOrThrow(Pattern pattern, Supplier<X> exceptionSupplier) {
if (value == null) {
return this;
}

if (!pattern.matcher(value).matches()) {
throw exceptionSupplier.get();
}

return this;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Original file line number Diff line number Diff line change
@@ -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<String, String> parameters() {
return Map.of("pattern", pattern);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit e5b6af8

Please sign in to comment.