feat: implemented JSON validation

This commit is contained in:
Yannik Bretschneider 2021-06-05 15:57:33 +02:00
parent 1f3eb89532
commit e42bce7917
19 changed files with 220 additions and 6 deletions

View File

@ -21,9 +21,13 @@ dependencies {
implementation "com.fasterxml.jackson.core:jackson-core:2.12.3"
implementation "com.fasterxml.jackson.core:jackson-annotations:2.12.3"
implementation "com.fasterxml.jackson.core:jackson-databind:2.12.3"
// implementation "javax.validation:validation-api:2.0.1.Final"
implementation "org.hibernate.validator:hibernate-validator:7.0.1.Final"
implementation "org.glassfish:jakarta.el:4.0.1"
implementation "org.tinylog:tinylog-api:2.4.0-M1"
implementation "org.tinylog:tinylog-impl:2.4.0-M1"
implementation "javax.validation:validation-api:2.0.1.Final"
testImplementation "net.jqwik:jqwik:1.5.1"
testImplementation "org.junit.jupiter:junit-jupiter-api:5.8.0-M1"

View File

@ -1,6 +1,7 @@
package uulm.teamname.marvelous.gamelibrary.config;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.validation.constraints.NotEmpty;
import java.util.*;
@ -8,6 +9,8 @@ import java.util.*;
* POJO describing the CharacterConfig as defined by the standard document.
*/
public class CharacterConfig {
@NotEmpty
public CharacterProperties[] characters;
@JsonIgnore private Map<String, CharacterProperties> propertyMap;

View File

@ -2,20 +2,45 @@ package uulm.teamname.marvelous.gamelibrary.config;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.util.Objects;
/** Represents properties of a character in the {@link CharacterConfig} */
@JsonPropertyOrder({"characterID", "name", "HP", "MP", "AP", "meleeDamage", "rangeCombatDamage", "rangeCombatReach"})
public class CharacterProperties {
@NotNull
@Positive
public int characterID;
@NotEmpty
public String name;
@NotNull
@Positive
public int HP;
@NotNull
@Positive
public int MP;
@NotNull
@Positive
public int AP;
@NotNull
@Positive
public int meleeDamage;
@NotNull
@Positive
@JsonProperty("rangeCombatDamage")
public int rangedDamage;
@NotNull
@Positive
@JsonProperty("rangeCombatReach")
public int attackRange;

View File

@ -1,5 +1,7 @@
package uulm.teamname.marvelous.gamelibrary.config;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.util.Objects;
/**
@ -7,55 +9,80 @@ import java.util.Objects;
*/
public class PartyConfig {
/** Max round amount in a match */
@NotNull
@Positive
public int maxRounds;
/** Max round time in a match in seconds */
@NotNull
@Positive
public int maxRoundTime;
/** Max overall time in a match in seconds */
@NotNull
@Positive
public int maxGameTime;
/** Max time a single animation might take up in seconds */
@NotNull
@Positive
public int maxAnimationTime;
/** Cooldown of the space stone in rounds */
@NotNull
@Positive
public int spaceStoneCD;
/** Cooldown of the mind stone in rounds */
@NotNull
@Positive
public int mindStoneCD;
/** Cooldown of the reality stone in rounds */
@NotNull
@Positive
public int realityStoneCD;
/** Cooldown of the power stone in rounds */
@NotNull
@Positive
public int powerStoneCD;
/** Cooldown of the time stone in rounds */
@NotNull
@Positive
public int timeStoneCD;
/** Cooldown of the soul stone in rounds */
@NotNull
@Positive
public int soulStoneCD;
/** Damage the mind stone does when used */
@NotNull
@Positive
public int mindStoneDMG;
/** Max pause time. Optional */
@NotNull
@Positive
public int maxPauseTime;
/** Duration that the server waits for the client to respond (send a message), based on the usage of Keep-Alives */
public int maxResponseTime;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PartyConfig that = (PartyConfig) o;
return maxRounds == that.maxRounds && maxRoundTime == that.maxRoundTime && maxGameTime == that.maxGameTime && maxAnimationTime == that.maxAnimationTime && spaceStoneCD == that.spaceStoneCD && mindStoneCD == that.mindStoneCD && realityStoneCD == that.realityStoneCD && powerStoneCD == that.powerStoneCD && timeStoneCD == that.timeStoneCD && soulStoneCD == that.soulStoneCD && mindStoneDMG == that.mindStoneDMG && maxPauseTime == that.maxPauseTime;
return maxRounds == that.maxRounds && maxRoundTime == that.maxRoundTime && maxGameTime == that.maxGameTime && maxAnimationTime == that.maxAnimationTime && spaceStoneCD == that.spaceStoneCD && mindStoneCD == that.mindStoneCD && realityStoneCD == that.realityStoneCD && powerStoneCD == that.powerStoneCD && timeStoneCD == that.timeStoneCD && soulStoneCD == that.soulStoneCD && mindStoneDMG == that.mindStoneDMG && maxPauseTime == that.maxPauseTime && maxResponseTime == that.maxResponseTime;
}
@Override
public int hashCode() {
return Objects.hash(maxRounds, maxRoundTime, maxGameTime, maxAnimationTime, spaceStoneCD, mindStoneCD, realityStoneCD, powerStoneCD, timeStoneCD, soulStoneCD, mindStoneDMG, maxPauseTime);
return Objects.hash(maxRounds, maxRoundTime, maxGameTime, maxAnimationTime, spaceStoneCD, mindStoneCD, realityStoneCD, powerStoneCD, timeStoneCD, soulStoneCD, mindStoneDMG, maxPauseTime, maxResponseTime);
}
@Override
@ -73,6 +100,7 @@ public class PartyConfig {
", soulStoneCD=" + soulStoneCD +
", mindStoneDMG=" + mindStoneDMG +
", maxPauseTime=" + maxPauseTime +
", maxResponseTime=" + maxResponseTime +
'}';
}
}

View File

@ -0,0 +1,41 @@
package uulm.teamname.marvelous.gamelibrary.json;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validator;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
public class Validation {
private Validation() {}
private static final Validator validator;
static {
var factory = jakarta.validation.Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
public static Set<ConstraintViolation<Object>> validate(Object object) {
return validator.validate(object);
}
/**
* Validates the given object by using a {@link Validator}, and generates
* a {@link String} from the violations. If there aren't any violations, and the object perfectly
* conforms to the shape it should be in, an empty optional is returned.
* @param object is the object to validate
* @return an {@link Optional}<{@link String}> describing the violations the object commits,
* or an empty {@link Optional} if there aren't any
*/
public static Optional<String> validateAndGetString(Object object) {
var violations = validator.validate(object);
if (violations.isEmpty()) return Optional.empty();
else {
return Optional.of(violations.stream()
.map(violation -> violation.getPropertyPath() + ": " + violation.getMessage())
.collect(Collectors.joining(", ")));
}
}
}

View File

@ -1,13 +1,17 @@
package uulm.teamname.marvelous.gamelibrary.messages;
import jakarta.validation.constraints.NotNull;
public class ErrorMessage extends BasicMessage{
public final MessageType messageType = MessageType.ERROR;
/** Some message telling the client what went wrong */
public String message;
@NotNull
public String message = "No message given";
/** The type of the error that happened */
public int type;
@NotNull
public int type = 0;
}

View File

@ -3,6 +3,8 @@ package uulm.teamname.marvelous.gamelibrary.messages.client;
import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
import uulm.teamname.marvelous.gamelibrary.messages.MessageType;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.util.Arrays;
public class CharacterSelectionMessage extends BasicMessage {
@ -13,6 +15,8 @@ public class CharacterSelectionMessage extends BasicMessage {
* Boolean array that conveys information about what characters (6)
* of the given characters (12) have been selected
*/
@NotNull
@Size(min = 12, max = 12, message = "doesn't have twelve elements")
public Boolean[] characters;
@Override

View File

@ -3,13 +3,17 @@ package uulm.teamname.marvelous.gamelibrary.messages.client;
import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
import uulm.teamname.marvelous.gamelibrary.messages.MessageType;
import jakarta.validation.constraints.NotNull;
public class HelloServerMessage extends BasicMessage {
public final MessageType messageType = MessageType.HELLO_SERVER;
/** User-chosen name, basically a PlayerName */
@NotNull
public String name;
/** Device ID sent by the Client, might be anything, but used together with name to uniquely identify the client */
@NotNull
public String deviceID;
}

View File

@ -4,13 +4,17 @@ import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
import uulm.teamname.marvelous.gamelibrary.messages.MessageType;
import uulm.teamname.marvelous.gamelibrary.messages.RoleEnum;
import jakarta.validation.constraints.NotNull;
public class PlayerReadyMessage extends BasicMessage {
public final MessageType messageType = MessageType.PLAYER_READY;
/** Whether the client wants to start the game. If this is false, the client gets disconnected. */
@NotNull
public Boolean startGame;
/** The {@link RoleEnum Role} the client wants to take */
@NotNull
public RoleEnum role;
}

View File

@ -3,10 +3,13 @@ package uulm.teamname.marvelous.gamelibrary.messages.client;
import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
import uulm.teamname.marvelous.gamelibrary.messages.MessageType;
import jakarta.validation.constraints.NotNull;
public class ReconnectMessage extends BasicMessage {
public final MessageType messageType = MessageType.RECONNECT;
/** Whether the client wants to reconnect to the previously running game */
@NotNull
public Boolean reconnect;
}

View File

@ -6,6 +6,8 @@ import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
import uulm.teamname.marvelous.gamelibrary.messages.MessageType;
import uulm.teamname.marvelous.gamelibrary.requests.Request;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;
@ -16,6 +18,8 @@ public class RequestMessage extends BasicMessage {
public final MessageType messageType = MessageType.REQUESTS;
/** The list of {@link Event Events} sent inside the message. */
@NotNull(message = "No Requests found")
@NotEmpty(message = "No Requests found")
public Request[] messages;
/** The type of the custom content sent with the message. */

View File

@ -3,6 +3,7 @@ package uulm.teamname.marvelous.gamelibrary.messages.server;
import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
import uulm.teamname.marvelous.gamelibrary.messages.MessageType;
import jakarta.validation.constraints.NotNull;
import java.util.Objects;
public class ConfirmSelectionMessage extends BasicMessage {
@ -10,6 +11,7 @@ public class ConfirmSelectionMessage extends BasicMessage {
public final MessageType messageType = MessageType.CONFIRM_SELECTION;
/** Whether the other player is also done with the selection already */
@NotNull
public Boolean selectionComplete;
@Override

View File

@ -5,6 +5,8 @@ import uulm.teamname.marvelous.gamelibrary.events.Event;
import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
import uulm.teamname.marvelous.gamelibrary.messages.MessageType;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;
@ -16,6 +18,8 @@ public class EventMessage extends BasicMessage {
public final MessageType messageType = MessageType.EVENTS;
/** The list of {@link Event Events} sent inside the message. */
@NotNull(message = "No events found")
@NotEmpty(message = "No events found")
public Event[] messages;
/** The type of the custom content sent with the message. */

View File

@ -4,6 +4,9 @@ import uulm.teamname.marvelous.gamelibrary.config.CharacterProperties;
import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
import uulm.teamname.marvelous.gamelibrary.messages.MessageType;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.util.Arrays;
import java.util.Objects;
@ -13,9 +16,12 @@ public class GameAssignmentMessage extends BasicMessage {
public final MessageType messageType = MessageType.GAME_ASSIGNMENT;
/** The ID of the game that the client is connected to. What this is used for is kind of unknown. */
@NotEmpty
public String gameID;
/** The characters the player can choose from */
@NotNull
@Size(min = 12, max = 12, message = "Character Selection doesn't have 12 booleans")
public CharacterProperties[] characterSelection;
@Override

View File

@ -10,6 +10,9 @@ import uulm.teamname.marvelous.gamelibrary.messages.ParticipantType;
import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
import uulm.teamname.marvelous.gamelibrary.messages.MessageType;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Stream;
@ -28,25 +31,36 @@ public class GameStructureMessage extends BasicMessage {
public final MessageType messageType = MessageType.GAME_STRUCTURE;
/** The role the client has been assigned to */
@NotNull
public ParticipantType assignment;
/** The name of the first player */
@NotNull
public String playerOneName;
/** The name of the second player */
@NotNull
public String playerTwoName;
/** The characters that the first player has chosen (and is therefore playing with in this match) */
@NotNull
@Size(min = 6, max = 6)
public CharacterProperties[] playerOneCharacters;
/** The characters that the second player has chosen (and is therefore playing with in this match) */
@NotNull
@Size(min = 6, max = 6)
public CharacterProperties[] playerTwoCharacters;
/** The {@link PartyConfig Party Configuration} of the current match */
@NotNull
@Valid
public PartyConfig matchconfig;
/** The {@link ScenarioConfig Scenario Configuration} of the current scenario */
@NotNull
@Valid
public ScenarioConfig scenarioconfig;
@JsonIgnore public CharacterConfig getCharacterConfig() {

View File

@ -3,6 +3,8 @@ package uulm.teamname.marvelous.gamelibrary.messages.server;
import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
import uulm.teamname.marvelous.gamelibrary.messages.MessageType;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.Objects;
public class GeneralAssignmentMessage extends BasicMessage {
@ -10,6 +12,8 @@ public class GeneralAssignmentMessage extends BasicMessage {
public final MessageType messageType = MessageType.GENERAL_ASSIGNMENT;
/** The ID of the game that the client is connected to. What this is used for is kind of unknown. */
@NotNull
@NotEmpty
public String gameID;
@Override

View File

@ -3,10 +3,13 @@ package uulm.teamname.marvelous.gamelibrary.messages.server;
import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
import uulm.teamname.marvelous.gamelibrary.messages.MessageType;
import jakarta.validation.constraints.NotNull;
public class GoodbyeClientMessage extends BasicMessage {
public final MessageType messageType = MessageType.GOODBYE_CLIENT;
/** A message sent to the client on disconnect */
public String message;
@NotNull
public String message = "You got disconnected.";
}

View File

@ -3,10 +3,13 @@ package uulm.teamname.marvelous.gamelibrary.messages.server;
import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
import uulm.teamname.marvelous.gamelibrary.messages.MessageType;
import jakarta.validation.constraints.NotNull;
public class HelloClientMessage extends BasicMessage {
public final MessageType messageType = MessageType.HELLO_CLIENT;
/** Whether there is a running game that the player disconnected from */
@NotNull
public Boolean runningGame;
}

View File

@ -0,0 +1,54 @@
package uulm.teamname.marvelous.gamelibrary.messages;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ValidatorFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import uulm.teamname.marvelous.gamelibrary.messages.client.CharacterSelectionMessage;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import java.util.stream.Collectors;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.Assertions.*;
class MessageValidationTest {
ValidatorFactory factory;
Validator validator;
@BeforeEach
void beforeEach() {
factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
// TODO: more tests here
@Test
void CharacterSelectionMessageTest() {
var message = new CharacterSelectionMessage();
var violations = validator.validate(message);
assertThat(violations).size().isOne();
message.characters = new Boolean[] {true, false, true, false, true, false}; // too little
violations = validator.validate(message);
var violationsString = violations.stream()
.map(violation -> violation.getPropertyPath() + " " + violation.getMessage())
.collect(Collectors.joining(", "));
// System.out.println(violationsString);
assertThat(violations).isNotEmpty();
assertThat(violationsString)
.isEqualTo("characters doesn't have twelve elements");
}
}