Skip to content

Commit

Permalink
Add Locale validator (#106)
Browse files Browse the repository at this point in the history
Co-authored-by: Thomas Turrell-Croft <tom@berrycloud.co.uk>
  • Loading branch information
Selindek and Thomas Turrell-Croft authored Mar 30, 2023
1 parent eeeb714 commit 6af8e26
Show file tree
Hide file tree
Showing 13 changed files with 268 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import dev.learning.xapi.model.validation.constraints.HasScheme;
import dev.learning.xapi.model.validation.constraints.ValidLocale;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -33,11 +34,13 @@ public class ActivityDefinition {
/**
* The human readable/visual name of the Activity.
*/
@ValidLocale
private LanguageMap name;

/**
* A description of the Activity.
*/
@ValidLocale
private LanguageMap description;

/**
Expand Down Expand Up @@ -92,7 +95,8 @@ public class ActivityDefinition {
*/
private Map<@HasScheme URI, Object> extensions;

// **Warning** do not add fields that are not required by the xAPI specification.
// **Warning** do not add fields that are not required by the xAPI
// specification.

/**
* Builder for ActivityDefinition.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import dev.learning.xapi.model.validation.constraints.ValidLocale;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.valueextraction.Unwrapping;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
Expand Down Expand Up @@ -40,12 +42,14 @@ public class Attachment {
/**
* Display name of this Attachment.
*/
@NotNull
@NotNull(payload = Unwrapping.Skip.class)
@ValidLocale
private LanguageMap display;

/**
* A description of the Attachment.
*/
@ValidLocale
private LanguageMap description;

/**
Expand Down
2 changes: 2 additions & 0 deletions xapi-model/src/main/java/dev/learning/xapi/model/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import dev.learning.xapi.model.validation.constraints.HasScheme;
import dev.learning.xapi.model.validation.constraints.NotUndetermined;
import dev.learning.xapi.model.validation.constraints.ValidActor;
import dev.learning.xapi.model.validation.constraints.ValidLocale;
import dev.learning.xapi.model.validation.constraints.Variant;
import jakarta.validation.Valid;
import java.net.URI;
Expand Down Expand Up @@ -76,6 +77,7 @@ public class Context {
*/
@NotUndetermined
@JsonSerialize(using = LocaleSerializer.class)
@ValidLocale
private Locale language;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import dev.learning.xapi.model.validation.constraints.ValidLocale;
import jakarta.validation.constraints.NotNull;
import java.util.Locale;
import lombok.Builder;
Expand Down Expand Up @@ -36,6 +37,7 @@ public class InteractionComponent {
/**
* A description of the interaction component.
*/
@ValidLocale
private LanguageMap description;

// **Warning** do not add fields that are not required by the xAPI specification.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public class Statement implements CoreStatement {
/**
* Agent or Group who is asserting this Statement is true.
*/
@Valid
@ValidActor
@ValidAuthority
private Actor authority;
Expand All @@ -113,6 +114,7 @@ public class Statement implements CoreStatement {
/**
* Headers for Attachments to the Statement.
*/
@Valid
@JsonFormat(without = {JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY})
private List<Attachment> attachments;

Expand Down Expand Up @@ -349,7 +351,7 @@ public Builder addAttachment(Attachment attachment) {
*/
public Builder addAttachment(Consumer<Attachment.Builder> attachment) {

final Attachment.Builder builder = Attachment.builder();
final var builder = Attachment.builder();

attachment.accept(builder);

Expand Down
2 changes: 2 additions & 0 deletions xapi-model/src/main/java/dev/learning/xapi/model/Verb.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import dev.learning.xapi.model.validation.constraints.HasScheme;
import dev.learning.xapi.model.validation.constraints.ValidLocale;
import jakarta.validation.constraints.NotNull;
import java.net.URI;
import java.util.Locale;
Expand Down Expand Up @@ -351,6 +352,7 @@ public class Verb {
* impact on the meaning of the Statement, but serves to give a human-readable display of the
* meaning already determined by the chosen Verb.
*/
@ValidLocale
private LanguageMap display;

// **Warning** do not add fields that are not required by the xAPI specification.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2016-2023 Berry Cloud Ltd. All rights reserved.
*/

package dev.learning.xapi.model.validation.constraints;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
* The annotated Locale must have a ISO3 Language and Country.
*
* @author István Rátkai (Selindek)
*/
@Documented
@Constraint(validatedBy = {})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface ValidLocale {

/**
* Error Message.
*/
String message() default "all keys must have a ISO3 Language and Country";

/**
* Groups.
*/
Class<?>[] groups() default {};

/**
* Payload.
*/
Class<? extends Payload>[] payload() default {};

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2016-2023 Berry Cloud Ltd. All rights reserved.
*/

package dev.learning.xapi.model.validation.internal.validators;

import dev.learning.xapi.model.LanguageMap;
import jakarta.validation.valueextraction.ExtractedValue;
import jakarta.validation.valueextraction.UnwrapByDefault;
import jakarta.validation.valueextraction.ValueExtractor;
import java.util.Locale;

/**
* ValueExtractor for {@link LanguageMap}.
*
* @author István Rátkai (Selindek)
*/
@UnwrapByDefault
public class LanguageMapValueExtractor
implements ValueExtractor<@ExtractedValue(type = Locale.class) LanguageMap> {

@Override
public void extractValues(LanguageMap originalValue, ValueReceiver receiver) {
originalValue.keySet().forEach(k -> receiver.iterableValue(k.toLanguageTag(), k));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2016-2023 Berry Cloud Ltd. All rights reserved.
*/

package dev.learning.xapi.model.validation.internal.validators;

import dev.learning.xapi.model.validation.constraints.ValidLocale;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.util.Locale;
import java.util.MissingResourceException;

/**
* The Locale being validated must have a ISO3 Language and Country.
*
* @author István Rátkai (Selindek)
* @author Thomas Turrell-Croft
*/
public class LocaleValidator implements ConstraintValidator<ValidLocale, Locale> {

@Override
public boolean isValid(Locale locale, ConstraintValidatorContext context) {

if (locale == null) {
return true;
}

try {
locale.getISO3Language();
locale.getISO3Country();

return true;
} catch (final MissingResourceException e1) {

// Handle locale instantiated with Locale#Locale(String)
final var blar = Locale.forLanguageTag(locale.toString());

try {
blar.getISO3Language();
blar.getISO3Country();

return true;
} catch (final MissingResourceException e2) {

return false;
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ dev.learning.xapi.model.validation.internal.validators.StatementRevisionValidato
dev.learning.xapi.model.validation.internal.validators.StatementPlatformValidator
dev.learning.xapi.model.validation.internal.validators.StatementVerbValidator
dev.learning.xapi.model.validation.internal.validators.StatementsValidator
dev.learning.xapi.model.validation.internal.validators.LocaleValidator
dev.learning.xapi.model.validation.internal.validators.ScoreValidator
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dev.learning.xapi.model.validation.internal.validators.LanguageMapValueExtractor
18 changes: 16 additions & 2 deletions xapi-model/src/test/java/dev/learning/xapi/model/VerbTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ void whenValidatingVerbWithAllRequiredPropertiesThenConstraintViolationsSizeIsZe
final var verb = Verb.builder().id("http://adlnet.gov/expapi/verbs/answered")
.addDisplay(Locale.US, "answered").build();

// When Validating Interaction Component With All Required Properties
// When Validating Verb with All Required Properties
final Set<ConstraintViolation<Verb>> constraintViolations = validator.validate(verb);

// Then ConstraintViolations Size Is Zero
Expand All @@ -332,7 +332,21 @@ void whenValidatingVerbWithoutIdThenConstraintViolationsSizeIsOne() {

final var verb = Verb.builder().addDisplay(Locale.US, "answered").build();

// When Validating Interaction Component Without Id
// When Validating Verb Component Without Id
final Set<ConstraintViolation<Verb>> constraintViolations = validator.validate(verb);

// Then ConstraintViolations Size Is One
assertThat(constraintViolations, hasSize(1));

}

@Test
void whenValidatingVerbWithInvalidDisplayIdThenConstraintViolationsSizeIsOne() {

final var verb = Verb.builder().id("http://adlnet.gov/expapi/verbs/asked")
.addDisplay(new Locale("unknown"), "answered").build();

// When Validating Verb With invalid Display
final Set<ConstraintViolation<Verb>> constraintViolations = validator.validate(verb);

// Then ConstraintViolations Size Is One
Expand Down
Loading

0 comments on commit 6af8e26

Please sign in to comment.