From 35768550d3921fdc10921a55e204b2e3cf1e2995 Mon Sep 17 00:00:00 2001 From: Siphalor Date: Sun, 29 Jun 2025 00:08:06 +0200 Subject: [PATCH] [serde-api, serde-extension] Make handling of data read exceptions more explicit --- .../dataapi/api/TweedDataReadException.java | 2 +- .../impl/ReadWriteExtensionImpl.java | 7 +- .../impl/TweedEntryReaderWriterImpls.java | 155 +++++++++++------- 3 files changed, 101 insertions(+), 63 deletions(-) diff --git a/tweed5-serde-api/src/main/java/de/siphalor/tweed5/dataapi/api/TweedDataReadException.java b/tweed5-serde-api/src/main/java/de/siphalor/tweed5/dataapi/api/TweedDataReadException.java index 1c2fe6e..822630e 100644 --- a/tweed5-serde-api/src/main/java/de/siphalor/tweed5/dataapi/api/TweedDataReadException.java +++ b/tweed5-serde-api/src/main/java/de/siphalor/tweed5/dataapi/api/TweedDataReadException.java @@ -6,7 +6,7 @@ import lombok.Setter; import org.jspecify.annotations.Nullable; @Getter -public class TweedDataReadException extends RuntimeException { +public class TweedDataReadException extends Exception { @Nullable private final TweedDataReaderRecoverMode recoverMode; diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImpl.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImpl.java index b1e903a..71efe12 100644 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImpl.java +++ b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/ReadWriteExtensionImpl.java @@ -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.extension.ReadWriteExtensionSetupContext; 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.TweedDataVisitor; import de.siphalor.tweed5.dataapi.api.TweedDataWriteException; @@ -146,11 +145,7 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension { Patchwork contextExtensionsData ) throws TweedEntryReadException { TweedReadContext context = new TweedReadWriteContextImpl(this, contextExtensionsData); - try { - return getReaderChain(entry).read(reader, entry, context); - } catch (TweedDataReadException e) { - throw new TweedEntryReadException("Failed to read entry", e, context); - } + return getReaderChain(entry).read(reader, entry, context); } @Override diff --git a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/TweedEntryReaderWriterImpls.java b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/TweedEntryReaderWriterImpls.java index 284ea75..12ee67a 100644 --- a/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/TweedEntryReaderWriterImpls.java +++ b/tweed5-serde-extension/src/main/java/de/siphalor/tweed5/data/extension/impl/TweedEntryReaderWriterImpls.java @@ -9,12 +9,11 @@ import de.siphalor.tweed5.dataapi.api.*; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; +import lombok.var; import org.jetbrains.annotations.Contract; import org.jspecify.annotations.Nullable; import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Function; import java.util.function.Predicate; @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -39,9 +38,13 @@ public class TweedEntryReaderWriterImpls { @Override public T read(TweedDataReader reader, C entry, TweedReadContext context) throws TweedEntryReadException { - if (reader.peekToken().isNull()) { - reader.readToken(); - return null; + try { + if (reader.peekToken().isNull()) { + reader.readToken(); + return null; + } + } catch (TweedDataReadException e) { + throw new TweedEntryReadException(e, context); } return delegate.read(reader, entry, context); } @@ -62,19 +65,24 @@ public class TweedEntryReaderWriterImpls { } @RequiredArgsConstructor - private static class PrimitiveReaderWriter implements TweedEntryReaderWriter> { - private final Function readerCall; - private final BiConsumer writerCall; + private static class PrimitiveReaderWriter implements TweedEntryReaderWriter> { + private final PrimitiveReadFunction readerFunction; + private final PrimitiveWriteFunction writerFunction; @Override - public T read(TweedDataReader reader, ConfigEntry entry, TweedReadContext context) { - return readerCall.apply(reader.readToken()); + public T read(TweedDataReader reader, ConfigEntry entry, TweedReadContext context) + throws TweedEntryReadException { + try { + return readerFunction.read(reader.readToken()); + } catch (TweedDataReadException e) { + throw new TweedEntryReadException(e, context); + } } @Override public void write(TweedDataVisitor writer, @Nullable T value, ConfigEntry entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException { requireNonNullWriteValue(value, context); - writerCall.accept(writer, value); + writerFunction.write(writer, value); } } @@ -82,8 +90,13 @@ public class TweedEntryReaderWriterImpls { @Override public C read(TweedDataReader reader, CollectionConfigEntry entry, TweedReadContext context) throws TweedEntryReadException { - assertIsToken(reader.readToken(), TweedDataToken::isListStart, "Expected list start", context); - TweedDataToken token = reader.peekToken(); + TweedDataToken token; + 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()) { return entry.instantiateCollection(0); } @@ -93,15 +106,23 @@ public class TweedEntryReaderWriterImpls { List<@Nullable T> list = new ArrayList<>(20); while (true) { - token = reader.peekToken(); - if (token.isListEnd()) { - reader.readToken(); - break; - } else if (token.isListValue()) { - list.add(elementReader.read(reader, elementEntry, context)); - } else { + try { + token = reader.peekToken(); + if (token.isListEnd()) { + reader.readToken(); + break; + } else if (token.isListValue()) { + 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( - "Unexpected token " + token + ": expected next list value or list end", + "Failed reading element " + list.size() + " of collection", + e, context ); } @@ -113,7 +134,8 @@ public class TweedEntryReaderWriterImpls { } @Override - public void write(TweedDataVisitor writer, C value, CollectionConfigEntry entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException { + public void write(TweedDataVisitor writer, C value, CollectionConfigEntry entry, TweedWriteContext context) + throws TweedEntryWriteException, TweedDataWriteException { requireNonNullWriteValue(value, context); if (value.isEmpty()) { @@ -136,27 +158,35 @@ public class TweedEntryReaderWriterImpls { @Override public T read(TweedDataReader reader, CompoundConfigEntry entry, TweedReadContext context) throws 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> compoundEntries = entry.subEntries(); T compoundValue = entry.instantiateCompoundValue(); while (true) { - TweedDataToken token = reader.readToken(); - if (token.isMapEnd()) { - break; - } else if (token.isMapEntryKey()) { - String key = token.readAsString(); + try { + TweedDataToken token = reader.readToken(); + if (token.isMapEnd()) { + break; + } else if (token.isMapEntryKey()) { + String key = token.readAsString(); - //noinspection unchecked - ConfigEntry subEntry = (ConfigEntry) compoundEntries.get(key); - TweedEntryReader> subEntryReaderChain = context.readWriteExtension().getReaderChain(subEntry); - Object subEntryValue = subEntryReaderChain.read(reader, subEntry, context); - entry.set(compoundValue, key, subEntryValue); - } else { - throw new TweedEntryReadException( - "Unexpected token " + token + ": Expected map key or map end", - context - ); + //noinspection unchecked + ConfigEntry subEntry = (ConfigEntry) compoundEntries.get(key); + var subEntryReaderChain = context.readWriteExtension().getReaderChain(subEntry); + Object subEntryValue = subEntryReaderChain.read(reader, subEntry, context); + entry.set(compoundValue, key, subEntryValue); + } else { + throw new TweedEntryReadException( + "Unexpected token " + token + ": Expected map key or map end", + context + ); + } + } catch (TweedDataReadException e) { + throw new TweedEntryReadException("Failed reading compound entry", e, context); } } return compoundValue; @@ -186,31 +216,36 @@ public class TweedEntryReaderWriterImpls { public static class NoopReaderWriter implements TweedEntryReaderWriter<@Nullable Object, ConfigEntry> { @Override - public @Nullable Object read(TweedDataReader reader, ConfigEntry entry, TweedReadContext context) { - TweedDataToken token = reader.readToken(); - if (!token.isListStart() && !token.isMapStart()) { - return null; - } + public @Nullable Object read(TweedDataReader reader, ConfigEntry entry, TweedReadContext context) + throws TweedEntryReadException { + try { + TweedDataToken token = reader.readToken(); + if (!token.isListStart() && !token.isMapStart()) { + return null; + } - ArrayDeque stack = new ArrayDeque<>(20); - if (token.isListStart()) { - stack.push(Context.LIST); - } else if (token.isMapStart()) { - stack.push(Context.MAP); - } - - while (true) { - token = reader.readToken(); + ArrayDeque stack = new ArrayDeque<>(20); 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; + + 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 read(TweedDataToken token) throws TweedDataReadException; + } + + private interface PrimitiveWriteFunction { + void write(TweedDataVisitor writer, T value) throws TweedDataWriteException; + } + @Contract("null, _ -> fail") private static void requireNonNullWriteValue( @Nullable T value,