Skip to content

Commit

Permalink
Fixes hibernate-validator for quarkus.locales=all
Browse files Browse the repository at this point in the history
  • Loading branch information
Karm committed Nov 22, 2023
1 parent 3a76499 commit 94843be
Show file tree
Hide file tree
Showing 16 changed files with 177 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -348,8 +348,8 @@ public NativeImageRunnerBuildItem resolveNativeImageBuildRunner(NativeConfig nat
}

/**
* Creates a dummy runner for native-sources builds. This allows the creation of native-source jars without
* requiring podman/docker or a local native-image installation.
* Creates a dummy runner for native-sources builds. This allows the creation of native-source jars without requiring
* podman/docker or a local native-image installation.
*/
@BuildStep(onlyIf = NativeSourcesBuild.class)
public NativeImageRunnerBuildItem dummyNativeImageBuildRunner(NativeConfig nativeConfig) {
Expand Down Expand Up @@ -726,6 +726,9 @@ public NativeImageInvokerInfo build() {
final String includeLocales = LocaleProcessor.nativeImageIncludeLocales(nativeConfig, localesBuildTimeConfig);
if (!includeLocales.isEmpty()) {
if ("all".equals(includeLocales)) {
log.warn(
"Your application is setting the 'quarkus.locales' configuration key to include 'all'. " +
"All JDK locales, languages, currencies, etc. will be included, inflating the size of the executable.");
addExperimentalVMOption(nativeImageArgs, "-H:+IncludeAllLocales");
} else {
addExperimentalVMOption(nativeImageArgs, "-H:IncludeLocales=" + includeLocales);
Expand Down
3 changes: 2 additions & 1 deletion docs/src/main/asciidoc/validation.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,8 @@ provided the supported locales have been properly specified in the `application.
quarkus.locales=en-US,es-ES,fr-FR
----

Alternatively, you can use `all` to make native-image executable to include all available locales. It inflates the executable size though.
Alternatively, you can use `all` to make native-image executable to include all available locales. It inflate the size of the executable
substantially though. The difference between including just two or three locales and including all locales is easily at least 23 MB.

A similar mechanism exists for GraphQL services based on the `quarkus-smallrye-graphql` extension.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
package io.quarkus.locales.it;

import jakarta.validation.constraints.Pattern;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

import org.jboss.logging.Logger;

@Path("")
public class AllLocalesResource extends LocalesResource {
private static final Logger LOG = Logger.getLogger(AllLocalesResource.class);

// @Pattern validation does nothing when placed in LocalesResource.
@GET
@Path("/hibernate-validator-test-validation-message-locale/{id}/")
@Produces(MediaType.TEXT_PLAIN)
public Response validationMessageLocale(
@Pattern(regexp = "A.*", message = "{pattern.message}") @PathParam("id") String id) {
LOG.infof("Triggering test: id: %s", id);
return Response.ok(id).build();
}
}
Original file line number Diff line number Diff line change
@@ -1,44 +1,44 @@
package io.quarkus.locales.it;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;

import org.apache.http.HttpStatus;
import org.jboss.logging.Logger;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.params.provider.CsvSource;

import io.quarkus.test.junit.QuarkusIntegrationTest;
import io.restassured.RestAssured;

/**
* A special case where we want to include all locales in our app.
* It must not matter which arbitrary locale we use, it must work here.
* A special case where we want to include all locales in our app. It must not matter which arbitrary locale we use, it must
* work here.
*/
@QuarkusIntegrationTest
public class LocalesIT {

private static final Logger LOG = Logger.getLogger(LocalesIT.class);

@ParameterizedTest
@ValueSource(strings = {
@CsvSource(value = {
"en-US|en|United States",
"de-DE|de|Deutschland",
"de-AT|en|Austria",
"de-DE|en|Germany",
"zh-cmn-Hans-CN|cs|Čína",
"zh-Hant-TW|cs|Tchaj-wan",
"ja-JP-JP-#u-ca-japanese|sg|Zapöon"
})
public void testCorrectLocales(String countryLanguageTranslation) {
final String[] lct = countryLanguageTranslation.split("\\|");
LOG.infof("Triggering test: Country: %s, Language: %s, Translation: %s", lct[0], lct[1], lct[2]);
}, delimiter = '|')
public void testCorrectLocales(String country, String language, String translation) {
LOG.infof("Triggering test: Country: %s, Language: %s, Translation: %s", country, language, translation);
RestAssured.given().when()
.get(String.format("/locale/%s/%s", lct[0], lct[1]))
.get(String.format("/locale/%s/%s", country, language))
.then()
.statusCode(HttpStatus.SC_OK)
.body(is(lct[2]))
.body(is(translation))
.log().all();
}

Expand All @@ -53,23 +53,21 @@ public void testItalyIncluded() {
}

@ParameterizedTest

@ValueSource(strings = {
@CsvSource(value = {
"0,666|en-US|666.0",
"0,666|cs-CZ|0.666",
"0,666|fr-FR|0.666",
"0.666|fr-FR|0.0"
})
public void testNumbers(String zoneLanguageName) {
final String[] nlr = zoneLanguageName.split("\\|");
LOG.infof("Triggering test: Number: %s, Locale: %s, Expected result: %s", nlr[0], nlr[1], nlr[2]);
}, delimiter = '|')
public void testNumbers(String number, String locale, String expected) {
LOG.infof("Triggering test: Number: %s, Locale: %s, Expected result: %s", number, locale, expected);
RestAssured.given().when()
.param("number", nlr[0])
.param("locale", nlr[1])
.param("number", number)
.param("locale", locale)
.get("/numbers")
.then()
.statusCode(HttpStatus.SC_OK)
.body(equalTo(nlr[2]))
.body(equalTo(expected))
.log().all();
}

Expand All @@ -84,31 +82,47 @@ public void languageRanges() {
.log().all();
}

@Test
public void message() {
// Ukrainian language preference is higher than Czech.
RestAssured.given().when()
.header("Accept-Language", "cs;q=0.7,uk;q=0.9")
.get("/message")
.then()
.statusCode(HttpStatus.SC_OK)
.body(is("Привіт Світ!"))
.log().all();
// Czech language preference is higher than Ukrainian.
@ParameterizedTest
@CsvSource(value = {
// Ukrainian language preference is higher than Czech.
"cs;q=0.7,uk;q=0.9|Привіт Світ!",
// Czech language preference is higher than Ukrainian.
"cs;q=1.0,uk;q=0.9|Ahoj světe!",
// An unknown language preference, silent fallback to lingua franca.
"jp;q=1.0|Hello world!"
}, delimiter = '|')
public void message(String acceptLanguage, String expectedMessage) {
RestAssured.given().when()
.header("Accept-Language", "cs;q=1.0,uk;q=0.9")
.header("Accept-Language", acceptLanguage)
.get("/message")
.then()
.statusCode(HttpStatus.SC_OK)
.body(is("Ahoj světe!"))
.body(is(expectedMessage))
.log().all();
// An unknown language preference, silent fallback to lingua franca.
RestAssured.given().when()
.header("Accept-Language", "jp;q=1.0")
.get("/message")
}

/**
* @see integration-tests/hibernate-validator/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java
*/
@ParameterizedTest
@CsvSource(value = {
// Croatian language preference is higher than Ukrainian.
"en-US;q=0.25,hr-HR;q=0.9,fr-FR;q=0.5,uk-UA;q=0.1|Vrijednost ne zadovoljava uzorak",
// Ukrainian language preference is higher than Croatian.
"en-US;q=0.25,hr-HR;q=0.9,fr-FR;q=0.5,uk-UA;q=1.0|Значення не відповідає зразку",
// An unknown language preference, silent fallback to lingua franca.
"invalid string|Value is not in line with the pattern",
// Croatian language preference is the highest.
"en-US;q=0.25,hr-HR;q=1,fr-FR;q=0.5|Vrijednost ne zadovoljava uzorak",
// Chinese language preference is the highest.
"en-US;q=0.25,hr-HR;q=0.30,zh;q=0.9,fr-FR;q=0.50|數值不符合樣品",
}, delimiter = '|')
public void testValidationMessageLocale(String acceptLanguage, String expectedMessage) {
RestAssured.given()
.header("Accept-Language", acceptLanguage)
.when()
.get("/hibernate-validator-test-validation-message-locale/1")
.then()
.statusCode(HttpStatus.SC_OK)
.body(is("Hello world!"))
.log().all();
.body(containsString(expectedMessage));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pattern.message=Value is not in line with the pattern
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pattern.message=Vrijednost ne zadovoljava uzorak
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pattern.message=Значення не відповідає зразку
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pattern.message=數值不符合樣品
18 changes: 18 additions & 0 deletions integration-tests/locales/app/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>

<!-- Minimal test dependencies to *-deployment artifacts for consistent build order -->
<dependency>
Expand Down Expand Up @@ -48,5 +52,19 @@
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,27 @@ public class LocalesResource {
@Path("/locale/{country}/{language}")
@GET
public Response inLocale(@PathParam("country") String country, @NotNull @PathParam("language") String language) {
return Response.ok().entity(
Locale.forLanguageTag(country).getDisplayCountry(new Locale(language))).build();
return Response.ok().entity(Locale.forLanguageTag(country).getDisplayCountry(new Locale(language))).build();
}

@Path("/default/{country}")
@GET
public Response inDefaultLocale(@PathParam("country") String country) {
return Response.ok().entity(
Locale.forLanguageTag(country).getDisplayCountry()).build();
return Response.ok().entity(Locale.forLanguageTag(country).getDisplayCountry()).build();
}

@Path("/currency/{country}/{language}")
@GET
public Response currencyInLocale(@PathParam("country") String country, @NotNull @PathParam("language") String language) {
return Response.ok().entity(
Currency.getInstance(Locale.forLanguageTag(country)).getDisplayName(new Locale(language))).build();
return Response.ok().entity(Currency.getInstance(Locale.forLanguageTag(country)).getDisplayName(new Locale(language)))
.build();
}

@Path("/timeZone")
@GET
public Response timeZoneInLocale(@NotNull @QueryParam("zone") String zone,
@NotNull @QueryParam("language") String language) {
return Response.ok().entity(
TimeZone.getTimeZone(ZoneId.of(zone)).getDisplayName(new Locale(language))).build();
return Response.ok().entity(TimeZone.getTimeZone(ZoneId.of(zone)).getDisplayName(new Locale(language))).build();
}

@Path("/numbers")
Expand All @@ -60,8 +57,7 @@ public Response decimalDotCommaLocale(@NotNull @QueryParam("locale") String loca
@NotNull @QueryParam("number") String number) throws ParseException {
final Locale l = Locale.forLanguageTag(locale);
LOG.infof("Locale: %s, Locale tag: %s, Number: %s", l, locale, number);
return Response.ok().entity(String.valueOf(NumberFormat.getInstance(l).parse(number).doubleValue()))
.build();
return Response.ok().entity(String.valueOf(NumberFormat.getInstance(l).parse(number).doubleValue())).build();
}

@Path("/ranges")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
package io.quarkus.locales.it;

import jakarta.validation.constraints.Pattern;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

import org.jboss.logging.Logger;

@Path("")
public class SomeLocalesResource extends LocalesResource {
private static final Logger LOG = Logger.getLogger(SomeLocalesResource.class);

// @Pattern validation does nothing when placed in LocalesResource.
@GET
@Path("/hibernate-validator-test-validation-message-locale/{id}/")
@Produces(MediaType.TEXT_PLAIN)
public Response validationMessageLocale(
@Pattern(regexp = "A.*", message = "{pattern.message}") @PathParam("id") String id) {
LOG.infof("Triggering test: id: %s", id);
return Response.ok(id).build();
}
}
Loading

0 comments on commit 94843be

Please sign in to comment.