diff --git a/src/main/java/com/livingworld/data/saved/SaveMetadata.java b/src/main/java/com/livingworld/data/saved/SaveMetadata.java new file mode 100644 index 0000000..28b6065 --- /dev/null +++ b/src/main/java/com/livingworld/data/saved/SaveMetadata.java @@ -0,0 +1,44 @@ +package com.livingworld.data.saved; + +/** + * Immutable metadata for saved game data. + * + * @param schemaVersion the schema version number (must be > 0) + * @param modVersion the mod version string (must not be blank) + * @param createdAt the timestamp when the save was created + * @param updatedAt the timestamp when the save was last updated + */ +public record SaveMetadata( + int schemaVersion, + String modVersion, + long createdAt, + long updatedAt +) { + + /** + * Constructs a {@link SaveMetadata} and validates all fields. + * + * @param schemaVersion the schema version number (must be > 0) + * @param modVersion the mod version string (must not be blank) + * @param createdAt the timestamp when the save was created + * @param updatedAt the timestamp when the save was last updated + * @throws IllegalArgumentException if {@code schemaVersion} is not greater than 0 + * @throws IllegalArgumentException if {@code modVersion} is blank + * @throws IllegalArgumentException if {@code updatedAt} is less than {@code createdAt} + */ + public SaveMetadata { + if (schemaVersion <= 0) { + throw new IllegalArgumentException( + "schemaVersion must be greater than 0, got: " + schemaVersion); + } + + if (modVersion == null || modVersion.isBlank()) { + throw new IllegalArgumentException("modVersion must not be blank"); + } + + if (updatedAt < createdAt) { + throw new IllegalArgumentException( + "updatedAt (" + updatedAt + ") must be >= createdAt (" + createdAt + ")"); + } + } +} diff --git a/src/main/java/com/livingworld/data/serialization/PersistenceWriter.java b/src/main/java/com/livingworld/data/serialization/PersistenceWriter.java index 6436b6c..83258b6 100644 --- a/src/main/java/com/livingworld/data/serialization/PersistenceWriter.java +++ b/src/main/java/com/livingworld/data/serialization/PersistenceWriter.java @@ -1,56 +1,47 @@ package com.livingworld.data.serialization; /** - * Abstraction for writing save data to a persistence target. - * - *

This interface hides the underlying storage format (JSON, NBT, etc.) from - * modules. Modules write their data through this abstraction without knowing - * how or where it is persisted.

+ * Interface for writing data values with associated keys. */ public interface PersistenceWriter { /** - * Writes a string value with the given key. + * Writes a string value. * - * @param key the identifier for this value (must not be null or blank) - * @param value the string to write - * @throws IllegalArgumentException if key is null or blank + * @param key the key identifier + * @param value the string value to write */ void writeString(String key, String value); /** - * Writes an integer value with the given key. + * Writes an integer value. * - * @param key the identifier for this value (must not be null or blank) - * @param value the integer to write - * @throws IllegalArgumentException if key is null or blank + * @param key the key identifier + * @param value the integer value to write */ void writeInt(String key, int value); /** - * Writes a long value with the given key. + * Writes a long value. * - * @param key the identifier for this value (must not be null or blank) - * @param value the long to write - * @throws IllegalArgumentException if key is null or blank + * @param key the key identifier + * @param value the long value to write */ void writeLong(String key, long value); /** - * Writes a double value with the given key. + * Writes a double value. * - * @param key the identifier for this value (must not be null or blank) - * @param value the double to write - * @throws IllegalArgumentException if key is null or blank + * @param key the key identifier + * @param value the double value to write */ void writeDouble(String key, double value); /** - * Writes a boolean value with the given key. + * Writes a boolean value. * - * @param key the identifier for this value (must not be null or blank) - * @param value the boolean to write - * @throws IllegalArgumentException if key is null or blank + * @param key the key identifier + * @param value the boolean value to write */ void writeBoolean(String key, boolean value); -} \ No newline at end of file +} diff --git a/src/test/java/com/livingworld/data/saved/SaveMetadataTest.java b/src/test/java/com/livingworld/data/saved/SaveMetadataTest.java new file mode 100644 index 0000000..c3fe80c --- /dev/null +++ b/src/test/java/com/livingworld/data/saved/SaveMetadataTest.java @@ -0,0 +1,40 @@ +package com.livingworld.data.saved; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class SaveMetadataTest { + + @Test + void acceptsVersionedChronologicalMetadata() { + SaveMetadata metadata = new SaveMetadata(1, "0.1.0", 100L, 200L); + + assertEquals(1, metadata.schemaVersion()); + assertEquals("0.1.0", metadata.modVersion()); + assertEquals(100L, metadata.createdAt()); + assertEquals(200L, metadata.updatedAt()); + } + + @Test + void rejectsInvalidSchemaVersion() { + assertThrows( + IllegalArgumentException.class, + () -> new SaveMetadata(0, "0.1.0", 100L, 100L)); + } + + @Test + void rejectsBlankModVersion() { + assertThrows( + IllegalArgumentException.class, + () -> new SaveMetadata(1, " ", 100L, 100L)); + } + + @Test + void rejectsUpdateBeforeCreation() { + assertThrows( + IllegalArgumentException.class, + () -> new SaveMetadata(1, "0.1.0", 200L, 100L)); + } +}