feat: implemented JSON validation
This commit is contained in:
parent
1f3eb89532
commit
e42bce7917
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -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(", ")));
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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. */
|
||||
|
@ -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
|
||||
|
@ -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. */
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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.";
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user