Complete persistence metadata foundation

This commit is contained in:
George
2026-06-07 14:02:50 +01:00
parent e23f362b12
commit 4f5a067300
3 changed files with 101 additions and 26 deletions
@@ -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 + ")");
}
}
}
@@ -1,56 +1,47 @@
package com.livingworld.data.serialization;
/**
* Abstraction for writing save data to a persistence target.
*
* <p>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.</p>
* 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);
}
}
@@ -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));
}
}