[serde-api, serde-extension] Make handling of data read exceptions more explicit

This commit is contained in:
2025-06-29 00:08:06 +02:00
parent 25faea92d8
commit 35768550d3
3 changed files with 101 additions and 63 deletions

View File

@@ -6,7 +6,7 @@ import lombok.Setter;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
@Getter @Getter
public class TweedDataReadException extends RuntimeException { public class TweedDataReadException extends Exception {
@Nullable @Nullable
private final TweedDataReaderRecoverMode recoverMode; private final TweedDataReaderRecoverMode recoverMode;

View File

@@ -9,7 +9,6 @@ import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
import de.siphalor.tweed5.data.extension.api.*; import de.siphalor.tweed5.data.extension.api.*;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext; import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension; import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.dataapi.api.TweedDataReadException;
import de.siphalor.tweed5.dataapi.api.TweedDataReader; import de.siphalor.tweed5.dataapi.api.TweedDataReader;
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor; import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
import de.siphalor.tweed5.dataapi.api.TweedDataWriteException; import de.siphalor.tweed5.dataapi.api.TweedDataWriteException;
@@ -146,11 +145,7 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension {
Patchwork contextExtensionsData Patchwork contextExtensionsData
) throws TweedEntryReadException { ) throws TweedEntryReadException {
TweedReadContext context = new TweedReadWriteContextImpl(this, contextExtensionsData); TweedReadContext context = new TweedReadWriteContextImpl(this, contextExtensionsData);
try { return getReaderChain(entry).read(reader, entry, context);
return getReaderChain(entry).read(reader, entry, context);
} catch (TweedDataReadException e) {
throw new TweedEntryReadException("Failed to read entry", e, context);
}
} }
@Override @Override

View File

@@ -9,12 +9,11 @@ import de.siphalor.tweed5.dataapi.api.*;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.var;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
@NoArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PRIVATE)
@@ -39,9 +38,13 @@ public class TweedEntryReaderWriterImpls {
@Override @Override
public T read(TweedDataReader reader, C entry, TweedReadContext context) throws TweedEntryReadException { public T read(TweedDataReader reader, C entry, TweedReadContext context) throws TweedEntryReadException {
if (reader.peekToken().isNull()) { try {
reader.readToken(); if (reader.peekToken().isNull()) {
return null; reader.readToken();
return null;
}
} catch (TweedDataReadException e) {
throw new TweedEntryReadException(e, context);
} }
return delegate.read(reader, entry, context); return delegate.read(reader, entry, context);
} }
@@ -62,19 +65,24 @@ public class TweedEntryReaderWriterImpls {
} }
@RequiredArgsConstructor @RequiredArgsConstructor
private static class PrimitiveReaderWriter<T extends @Nullable Object> implements TweedEntryReaderWriter<T, ConfigEntry<T>> { private static class PrimitiveReaderWriter<T> implements TweedEntryReaderWriter<T, ConfigEntry<T>> {
private final Function<TweedDataToken, T> readerCall; private final PrimitiveReadFunction<T> readerFunction;
private final BiConsumer<TweedDataVisitor, T> writerCall; private final PrimitiveWriteFunction<T> writerFunction;
@Override @Override
public T read(TweedDataReader reader, ConfigEntry<T> entry, TweedReadContext context) { public T read(TweedDataReader reader, ConfigEntry<T> entry, TweedReadContext context)
return readerCall.apply(reader.readToken()); throws TweedEntryReadException {
try {
return readerFunction.read(reader.readToken());
} catch (TweedDataReadException e) {
throw new TweedEntryReadException(e, context);
}
} }
@Override @Override
public void write(TweedDataVisitor writer, @Nullable T value, ConfigEntry<T> entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException { public void write(TweedDataVisitor writer, @Nullable T value, ConfigEntry<T> entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException {
requireNonNullWriteValue(value, context); requireNonNullWriteValue(value, context);
writerCall.accept(writer, value); writerFunction.write(writer, value);
} }
} }
@@ -82,8 +90,13 @@ public class TweedEntryReaderWriterImpls {
@Override @Override
public C read(TweedDataReader reader, CollectionConfigEntry<T, C> entry, TweedReadContext context) throws public C read(TweedDataReader reader, CollectionConfigEntry<T, C> entry, TweedReadContext context) throws
TweedEntryReadException { TweedEntryReadException {
assertIsToken(reader.readToken(), TweedDataToken::isListStart, "Expected list start", context); TweedDataToken token;
TweedDataToken token = reader.peekToken(); try {
assertIsToken(reader.readToken(), TweedDataToken::isListStart, "Expected list start", context);
token = reader.peekToken();
} catch (TweedDataReadException e) {
throw new TweedEntryReadException("Failed reading collection start", e, context);
}
if (token.isListEnd()) { if (token.isListEnd()) {
return entry.instantiateCollection(0); return entry.instantiateCollection(0);
} }
@@ -93,15 +106,23 @@ public class TweedEntryReaderWriterImpls {
List<@Nullable T> list = new ArrayList<>(20); List<@Nullable T> list = new ArrayList<>(20);
while (true) { while (true) {
token = reader.peekToken(); try {
if (token.isListEnd()) { token = reader.peekToken();
reader.readToken(); if (token.isListEnd()) {
break; reader.readToken();
} else if (token.isListValue()) { break;
list.add(elementReader.read(reader, elementEntry, context)); } else if (token.isListValue()) {
} else { list.add(elementReader.read(reader, elementEntry, context));
} else {
throw new TweedEntryReadException(
"Unexpected token " + token + ": expected next list value or list end",
context
);
}
} catch (TweedDataReadException e) {
throw new TweedEntryReadException( throw new TweedEntryReadException(
"Unexpected token " + token + ": expected next list value or list end", "Failed reading element " + list.size() + " of collection",
e,
context context
); );
} }
@@ -113,7 +134,8 @@ public class TweedEntryReaderWriterImpls {
} }
@Override @Override
public void write(TweedDataVisitor writer, C value, CollectionConfigEntry<T, C> entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException { public void write(TweedDataVisitor writer, C value, CollectionConfigEntry<T, C> entry, TweedWriteContext context)
throws TweedEntryWriteException, TweedDataWriteException {
requireNonNullWriteValue(value, context); requireNonNullWriteValue(value, context);
if (value.isEmpty()) { if (value.isEmpty()) {
@@ -136,27 +158,35 @@ public class TweedEntryReaderWriterImpls {
@Override @Override
public T read(TweedDataReader reader, CompoundConfigEntry<T> entry, TweedReadContext context) throws public T read(TweedDataReader reader, CompoundConfigEntry<T> entry, TweedReadContext context) throws
TweedEntryReadException { TweedEntryReadException {
assertIsToken(reader.readToken(), TweedDataToken::isMapStart, "Expected map start", context); try {
assertIsToken(reader.readToken(), TweedDataToken::isMapStart, "Expected map start", context);
} catch (TweedDataReadException e) {
throw new TweedEntryReadException("Failed reading compound start", e, context);
}
Map<String, ConfigEntry<?>> compoundEntries = entry.subEntries(); Map<String, ConfigEntry<?>> compoundEntries = entry.subEntries();
T compoundValue = entry.instantiateCompoundValue(); T compoundValue = entry.instantiateCompoundValue();
while (true) { while (true) {
TweedDataToken token = reader.readToken(); try {
if (token.isMapEnd()) { TweedDataToken token = reader.readToken();
break; if (token.isMapEnd()) {
} else if (token.isMapEntryKey()) { break;
String key = token.readAsString(); } else if (token.isMapEntryKey()) {
String key = token.readAsString();
//noinspection unchecked //noinspection unchecked
ConfigEntry<Object> subEntry = (ConfigEntry<Object>) compoundEntries.get(key); ConfigEntry<Object> subEntry = (ConfigEntry<Object>) compoundEntries.get(key);
TweedEntryReader<Object, ConfigEntry<Object>> subEntryReaderChain = context.readWriteExtension().getReaderChain(subEntry); var subEntryReaderChain = context.readWriteExtension().getReaderChain(subEntry);
Object subEntryValue = subEntryReaderChain.read(reader, subEntry, context); Object subEntryValue = subEntryReaderChain.read(reader, subEntry, context);
entry.set(compoundValue, key, subEntryValue); entry.set(compoundValue, key, subEntryValue);
} else { } else {
throw new TweedEntryReadException( throw new TweedEntryReadException(
"Unexpected token " + token + ": Expected map key or map end", "Unexpected token " + token + ": Expected map key or map end",
context context
); );
}
} catch (TweedDataReadException e) {
throw new TweedEntryReadException("Failed reading compound entry", e, context);
} }
} }
return compoundValue; return compoundValue;
@@ -186,31 +216,36 @@ public class TweedEntryReaderWriterImpls {
public static class NoopReaderWriter implements TweedEntryReaderWriter<@Nullable Object, ConfigEntry<Object>> { public static class NoopReaderWriter implements TweedEntryReaderWriter<@Nullable Object, ConfigEntry<Object>> {
@Override @Override
public @Nullable Object read(TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext context) { public @Nullable Object read(TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext context)
TweedDataToken token = reader.readToken(); throws TweedEntryReadException {
if (!token.isListStart() && !token.isMapStart()) { try {
return null; TweedDataToken token = reader.readToken();
} if (!token.isListStart() && !token.isMapStart()) {
return null;
}
ArrayDeque<Context> stack = new ArrayDeque<>(20); ArrayDeque<Context> stack = new ArrayDeque<>(20);
if (token.isListStart()) {
stack.push(Context.LIST);
} else if (token.isMapStart()) {
stack.push(Context.MAP);
}
while (true) {
token = reader.readToken();
if (token.isListStart()) { if (token.isListStart()) {
stack.push(Context.LIST); stack.push(Context.LIST);
} else if (token.isMapStart()) { } else if (token.isMapStart()) {
stack.push(Context.MAP); stack.push(Context.MAP);
} else if (token.isListEnd() || token.isMapEnd()) {
stack.pop();
} }
if (stack.isEmpty()) {
return null; while (true) {
token = reader.readToken();
if (token.isListStart()) {
stack.push(Context.LIST);
} else if (token.isMapStart()) {
stack.push(Context.MAP);
} else if (token.isListEnd() || token.isMapEnd()) {
stack.pop();
}
if (stack.isEmpty()) {
return null;
}
} }
} catch (TweedDataReadException e) {
throw new TweedEntryReadException("Failed skipping through value", e, context);
} }
} }
@@ -224,6 +259,14 @@ public class TweedEntryReaderWriterImpls {
} }
} }
private interface PrimitiveReadFunction<T extends Object> {
T read(TweedDataToken token) throws TweedDataReadException;
}
private interface PrimitiveWriteFunction<T extends Object> {
void write(TweedDataVisitor writer, T value) throws TweedDataWriteException;
}
@Contract("null, _ -> fail") @Contract("null, _ -> fail")
private static <T> void requireNonNullWriteValue( private static <T> void requireNonNullWriteValue(
@Nullable T value, @Nullable T value,