refactor!(serde-extension): Fundamentally change error handling for data reading

This commit is contained in:
2026-04-04 22:33:15 +02:00
parent e6ef13ff7f
commit 615d3810a0
26 changed files with 706 additions and 403 deletions

View File

@@ -1,6 +1,7 @@
package de.siphalor.tweed5.coat.bridge.impl; package de.siphalor.tweed5.coat.bridge.impl;
import de.siphalor.coat.handler.ConfigEntryHandler; import de.siphalor.coat.handler.ConfigEntryHandler;
import de.siphalor.coat.handler.Message;
import de.siphalor.coat.input.CheckBoxConfigInput; import de.siphalor.coat.input.CheckBoxConfigInput;
import de.siphalor.coat.input.ConfigInput; import de.siphalor.coat.input.ConfigInput;
import de.siphalor.coat.input.CycleButtonConfigInput; import de.siphalor.coat.input.CycleButtonConfigInput;
@@ -20,22 +21,23 @@ import de.siphalor.tweed5.coat.bridge.api.mapping.handler.BasicTweedCoatEntryHan
import de.siphalor.tweed5.coat.bridge.api.mapping.handler.ConvertingTweedCoatEntryHandler; import de.siphalor.tweed5.coat.bridge.api.mapping.handler.ConvertingTweedCoatEntryHandler;
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry; import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension; import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReadException;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReader; import de.siphalor.tweed5.serde.extension.api.TweedEntryReader;
import de.siphalor.tweed5.serde.extension.api.TweedReadContext; import de.siphalor.tweed5.serde.extension.api.TweedReadContext;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde_api.api.TweedDataReadException; import de.siphalor.tweed5.serde_api.api.TweedDataReadException;
import de.siphalor.tweed5.serde_api.api.TweedDataReader; import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataToken; import de.siphalor.tweed5.serde_api.api.TweedDataToken;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor; import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import de.siphalor.tweed5.serde_api.api.decoration.TweedDataDecoration; import de.siphalor.tweed5.serde_api.api.decoration.TweedDataDecoration;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Value; import lombok.Value;
import lombok.extern.apachecommons.CommonsLog; import lombok.extern.apachecommons.CommonsLog;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.language.I18n; import net.minecraft.client.resources.language.I18n;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
@@ -44,8 +46,7 @@ import java.util.*;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static de.siphalor.tweed5.coat.bridge.api.TweedCoatMappingUtils.translatableComponent; import static de.siphalor.tweed5.coat.bridge.api.TweedCoatMappingUtils.*;
import static de.siphalor.tweed5.coat.bridge.api.TweedCoatMappingUtils.translatableComponentWithFallback;
@CommonsLog @CommonsLog
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -133,66 +134,71 @@ public class TweedCoatMappersImpl {
if (context.parentSaveHandler() == null) { if (context.parentSaveHandler() == null) {
throw new IllegalArgumentException("No parent save handler provided"); throw new IllegalArgumentException("No parent save handler provided");
} }
return new ConvertingTweedCoatEntryHandler<>( return new ConfigEntryHandler<String>() {
new BasicTweedCoatEntryHandler<>(entry, context.defaultValue(), context.parentSaveHandler()), @Override
this::convertToString, public String getDefault() {
input -> { return convertToString(context.defaultValue());
}
@Override
public Collection<Message> getMessages(String text) {
try { try {
TweedEntryReader<T, ConfigEntry<T>> reader = TweedReadResult<T> readResult = convertFromString(text);
readWriteExtension.getDefinedEntryReader(entry); if (readResult.hasValue() && !readResult.hasIssues()) {
Patchwork readExtData = readWriteExtension.createReadWriteContextExtensionsData(); return Collections.emptyList();
//noinspection DataFlowIssue } else if (readResult.hasIssues()) {
return reader.read( Message.Level messageLevel = readResult.hasValue()
new TweedDataReader() { ? Message.Level.WARNING
private boolean consumed = false; : Message.Level.ERROR;
return Arrays.stream(readResult.issues()).map(issue ->
@Override new Message(messageLevel, literalComponent(issue.exception().getMessage()))
public TweedDataToken peekToken() throws TweedDataReadException { ).collect(Collectors.toList());
if (consumed) { } else {
throw new IllegalStateException("Already consumed"); return Collections.emptyList();
}
} catch (Exception e) {
return Collections.singletonList(
new Message(Message.Level.ERROR, literalComponent(e.getMessage()))
);
} }
return new TweedDataToken() {
@Override
public boolean canReadAsString() {
return true;
} }
@Override @Override
public String readAsString() { public void save(String s) {
return input; try {
TweedReadResult<T> readResult = convertFromString(s);
if (readResult.hasValue()) {
if (readResult.hasIssues()) {
log.warn(
"There were issues understanding value \"" + s + "\":\n - "
+ Arrays.stream(readResult.issues())
.map(issue -> issue.exception().getMessage())
.collect(Collectors.joining("\n - "))
);
}
context.parentSaveHandler().accept(readResult.value());
} else {
log.error(
"Failed to understand value \"" + s + "\":\n - "
+ Arrays.stream(readResult.issues())
.map(issue -> issue.exception().getMessage())
.collect(Collectors.joining("\n - "))
);
context.parentSaveHandler().accept(context.defaultValue());
}
} catch (Exception e) {
log.error("Failed to save value \"" + s + "\"", e);
context.parentSaveHandler().accept(context.defaultValue());
}
}
@Override
public Component asText(String text) {
return literalComponent(text);
} }
}; };
} }
@Override
public TweedDataToken readToken() throws TweedDataReadException {
TweedDataToken token = peekToken();
consumed = true;
return token;
}
@Override
public void close() {
}
}, entry, new TweedReadContext() {
@Override
public ReadWriteExtension readWriteExtension() {
return readWriteExtension;
}
@Override
public Patchwork extensionsData() {
return readExtData;
}
}
);
} catch (TweedEntryReadException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
);
}
@Override @Override
public @Nullable ConfigContentWidget createContentWidget(TweedCoatEntryCreationContext<T> context) { public @Nullable ConfigContentWidget createContentWidget(TweedCoatEntryCreationContext<T> context) {
return null; return null;
@@ -270,6 +276,57 @@ public class TweedCoatMappersImpl {
} }
return wrapper[0]; return wrapper[0];
} }
private TweedReadResult<T> convertFromString(String text) {
TweedEntryReader<T, ConfigEntry<T>> reader =
readWriteExtension.getDefinedEntryReader(entry);
Patchwork readExtData = readWriteExtension.createReadWriteContextExtensionsData();
//noinspection DataFlowIssue
return reader.read(
new TweedDataReader() {
private boolean consumed = false;
@Override
public TweedDataToken peekToken() throws TweedDataReadException {
if (consumed) {
throw new IllegalStateException("Already consumed");
}
return new TweedDataToken() {
@Override
public boolean canReadAsString() {
return true;
}
@Override
public String readAsString() {
return text;
}
};
}
@Override
public TweedDataToken readToken() throws TweedDataReadException {
TweedDataToken token = peekToken();
consumed = true;
return token;
}
@Override
public void close() {
}
}, entry, new TweedReadContext() {
@Override
public ReadWriteExtension readWriteExtension() {
return readWriteExtension;
}
@Override
public Patchwork extensionsData() {
return readExtData;
}
}
);
}
}; };
} }
} }

View File

@@ -58,9 +58,9 @@ public class TweedCoatBridgeTestMod implements ClientModInitializer {
MOD_ID + ".config", MOD_ID + ".config",
GLFW.GLFW_KEY_T, GLFW.GLFW_KEY_T,
//# if MC_VERSION_NUMBER >= 12109 //# if MC_VERSION_NUMBER >= 12109
//- KeyMapping.Category.MISC KeyMapping.Category.MISC
//# else //# else
"key.categories.misc" //- "key.categories.misc"
//# end //# end
)); ));
@@ -69,9 +69,9 @@ public class TweedCoatBridgeTestMod implements ClientModInitializer {
private class ScreenKeyBinding extends KeyMapping implements PriorityKeyBinding { private class ScreenKeyBinding extends KeyMapping implements PriorityKeyBinding {
//# if MC_VERSION_NUMBER >= 12109 //# if MC_VERSION_NUMBER >= 12109
//- public ScreenKeyBinding(String name, int key, Category category) { public ScreenKeyBinding(String name, int key, Category category) {
//# else //# else
public ScreenKeyBinding(String name, int key, String category) { //- public ScreenKeyBinding(String name, int key, String category) {
//# end //# end
super(name, key, category); super(name, key, category);
} }

View File

@@ -2,22 +2,22 @@ package de.siphalor.tweed5.fabric.helper.api;
import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.container.ConfigContainerSetupPhase; import de.siphalor.tweed5.core.api.container.ConfigContainerSetupPhase;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataWriter;
import de.siphalor.tweed5.serde_api.api.TweedSerde;
import de.siphalor.tweed5.defaultextensions.patch.api.PatchExtension; import de.siphalor.tweed5.defaultextensions.patch.api.PatchExtension;
import de.siphalor.tweed5.defaultextensions.patch.api.PatchInfo; import de.siphalor.tweed5.defaultextensions.patch.api.PatchInfo;
import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension; import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension;
import de.siphalor.tweed5.patchwork.api.Patchwork; import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadIssue;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataWriter;
import de.siphalor.tweed5.serde_api.api.TweedSerde;
import lombok.Getter; import lombok.Getter;
import lombok.extern.apachecommons.CommonsLog; import lombok.extern.apachecommons.CommonsLog;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -84,14 +84,24 @@ public class FabricConfigContainerHelper<T extends @Nullable Object> {
return; return;
} }
try (TweedDataReader reader = serde.createReader(new FileInputStream(configFile))) { try (TweedDataReader reader = serde.createReader(Files.newInputStream(configFile.toPath()))) {
Patchwork contextExtensionsData = readWriteExtension.createReadWriteContextExtensionsData(); Patchwork contextExtensionsData = readWriteExtension.createReadWriteContextExtensionsData();
readContextCustomizer.accept(contextExtensionsData); readContextCustomizer.accept(contextExtensionsData);
PatchInfo patchInfo = patchExtension.collectPatchInfo(contextExtensionsData); PatchInfo patchInfo = patchExtension.collectPatchInfo(contextExtensionsData);
T readValue = readWriteExtension.read(reader, configContainer().rootEntry(), contextExtensionsData); TweedReadResult<T> readResult = readWriteExtension.read(reader, configContainer().rootEntry(), contextExtensionsData);
if (readResult.hasValue()) {
patchExtension.patch(configContainer.rootEntry(), value, readResult.value(), patchInfo);
if (readResult.hasIssues()) {
log.warn(formatIssuesLogMessage(configFile, "issues", readResult.issues()));
}
} else if (readResult.hasIssues()) {
log.error(formatIssuesLogMessage(configFile, "errors", readResult.issues()));
} else {
log.debug("Reading config file " + configFile.getAbsolutePath() + " yielded empty result");
}
patchExtension.patch(configContainer.rootEntry(), value, readValue, patchInfo);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed loading config file " + configFile.getAbsolutePath(), e); log.error("Failed loading config file " + configFile.getAbsolutePath(), e);
} }
@@ -108,20 +118,54 @@ public class FabricConfigContainerHelper<T extends @Nullable Object> {
return defaultValueSupplier.get(); return defaultValueSupplier.get();
} }
try (TweedDataReader reader = serde.createReader(new FileInputStream(configFile))) { try (TweedDataReader reader = serde.createReader(Files.newInputStream(configFile.toPath()))) {
Patchwork contextExtensionsData = readWriteExtension.createReadWriteContextExtensionsData(); Patchwork contextExtensionsData = readWriteExtension.createReadWriteContextExtensionsData();
return readWriteExtension.read(reader, configContainer.rootEntry(), contextExtensionsData); TweedReadResult<T> readResult = readWriteExtension.read(
reader,
configContainer.rootEntry(),
contextExtensionsData
);
if (readResult.hasValue()) {
if (readResult.hasIssues()) {
log.warn(formatIssuesLogMessage(configFile, "issues", readResult.issues()));
}
return readResult.value();
} else if (readResult.hasIssues()) {
log.error(formatIssuesLogMessage(configFile, "errors", readResult.issues()));
} else {
log.info(
"Reading config file "
+ configFile.getAbsolutePath()
+ " yielded empty result, using default value"
);
}
return defaultValueSupplier.get();
} catch (Exception e) { } catch (Exception e) {
log.error("Failed loading config file " + configFile.getAbsolutePath(), e); log.error("Failed loading config file " + configFile.getAbsolutePath(), e);
return defaultValueSupplier.get(); return defaultValueSupplier.get();
} }
} }
private String formatIssuesLogMessage(File file, String type, TweedReadIssue[] issues) {
String filePath = file.getAbsolutePath();
StringBuilder stringBuilder = new StringBuilder(20 + filePath.length() + type.length() + issues.length * 50);
stringBuilder.append("Encountered ");
stringBuilder.append(type);
stringBuilder.append(" while reading ");
stringBuilder.append(filePath);
stringBuilder.append(": ");
for (TweedReadIssue issue : issues) {
stringBuilder.append(" - ").append(issue).append("\n");
}
return stringBuilder.toString();
}
public void writeConfigInConfigDirectory(T configValue) { public void writeConfigInConfigDirectory(T configValue) {
File configFile = getConfigFile(); File configFile = getConfigFile();
Path tempConfigDirectory = getOrCreateTempConfigDirectory(); Path tempConfigDirectory = getOrCreateTempConfigDirectory();
File tempConfigFile = tempConfigDirectory.resolve(getConfigFileName()).toFile(); File tempConfigFile = tempConfigDirectory.resolve(getConfigFileName()).toFile();
try (TweedDataWriter writer = serde.createWriter(new FileOutputStream(tempConfigFile))) { try (TweedDataWriter writer = serde.createWriter(Files.newOutputStream(tempConfigFile.toPath()))) {
Patchwork contextExtensionsData = readWriteExtension.createReadWriteContextExtensionsData(); Patchwork contextExtensionsData = readWriteExtension.createReadWriteContextExtensionsData();
readWriteExtension.write( readWriteExtension.write(

View File

@@ -1,71 +0,0 @@
package de.siphalor.tweed5.attributesextension.impl.serde.filter;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import java.util.Map;
@RequiredArgsConstructor
public class AttributesFilteredCompoundEntry<T extends @Nullable Object> implements CompoundConfigEntry<T> {
private final CompoundConfigEntry<T> delegate;
@Override
public void set(T compoundValue, String key, Object value) {
if (value == AttributesReadWriteFilterExtensionImpl.NOOP_MARKER) {
return;
}
delegate.set(compoundValue, key, value);
}
@Override
public Object get(T compoundValue, String key) {
return delegate.get(compoundValue, key);
}
@Override
public @NonNull T instantiateValue() {
return delegate.instantiateValue();
}
@Override
public Map<String, ConfigEntry<?>> subEntries() {
return delegate.subEntries();
}
@Override
public ConfigContainer<?> container() {
return delegate.container();
}
@Override
public Class<T> valueClass() {
return delegate.valueClass();
}
@Override
public Patchwork extensionsData() {
return delegate.extensionsData();
}
@Override
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
delegate.visitInOrder(visitor, value);
}
@Override
public void visitInOrder(ConfigEntryVisitor visitor) {
delegate.visitInOrder(visitor);
}
@Override
public T deepCopy(T value) {
return delegate.deepCopy(value);
}
}

View File

@@ -5,25 +5,22 @@ import de.siphalor.tweed5.attributesextension.api.AttributesRelatedExtension;
import de.siphalor.tweed5.attributesextension.api.serde.filter.AttributesReadWriteFilterExtension; import de.siphalor.tweed5.attributesextension.api.serde.filter.AttributesReadWriteFilterExtension;
import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.container.ConfigContainerSetupPhase; import de.siphalor.tweed5.core.api.container.ConfigContainerSetupPhase;
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor; import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext; import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
import de.siphalor.tweed5.core.api.middleware.Middleware; import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReadException; import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReader; import de.siphalor.tweed5.serde.extension.api.TweedEntryReader;
import de.siphalor.tweed5.serde.extension.api.TweedEntryWriter; import de.siphalor.tweed5.serde.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.serde.extension.api.TweedReadContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext; import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension; import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.extension.impl.TweedEntryReaderWriterImpls; import de.siphalor.tweed5.serde.extension.impl.TweedEntryReaderWriterImpls;
import de.siphalor.tweed5.serde_api.api.DelegatingTweedDataWriter; import de.siphalor.tweed5.serde_api.api.DelegatingTweedDataWriter;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataUnsupportedValueException; import de.siphalor.tweed5.serde_api.api.TweedDataUnsupportedValueException;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor; import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import de.siphalor.tweed5.serde_api.api.decoration.TweedDataDecoration; import de.siphalor.tweed5.serde_api.api.decoration.TweedDataDecoration;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import de.siphalor.tweed5.utils.api.UniqueSymbol; import de.siphalor.tweed5.utils.api.UniqueSymbol;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -184,13 +181,7 @@ public class AttributesReadWriteFilterExtensionImpl
TweedEntryReader<Object, ConfigEntry<Object>> innerCasted TweedEntryReader<Object, ConfigEntry<Object>> innerCasted
= (TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) inner; = (TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) inner;
return new TweedEntryReader<@Nullable Object, ConfigEntry<Object>>() { return (TweedEntryReader<Object, ConfigEntry<Object>>) (reader, entry, context) -> {
@Override
public @Nullable Object read(
TweedDataReader reader,
ConfigEntry<Object> entry,
TweedReadContext context
) throws TweedEntryReadException {
ReadWriteContextCustomData contextData = context.extensionsData().get(readWriteContextDataAccess); ReadWriteContextCustomData contextData = context.extensionsData().get(readWriteContextDataAccess);
if (contextData == null) { if (contextData == null) {
contextData = new ReadWriteContextCustomData(); contextData = new ReadWriteContextCustomData();
@@ -198,19 +189,9 @@ public class AttributesReadWriteFilterExtensionImpl
} }
if (!doFiltersMatch(entry, contextData)) { if (!doFiltersMatch(entry, contextData)) {
TweedEntryReaderWriterImpls.NOOP_READER_WRITER.read(reader, entry, context); TweedEntryReaderWriterImpls.NOOP_READER_WRITER.read(reader, entry, context);
return contextData.noopHandlerInstalled() ? NOOP_MARKER : null; return TweedReadResult.empty();
}
if (entry instanceof CompoundConfigEntry) {
CompoundConfigEntry<Object> delegated =
new AttributesFilteredCompoundEntry<>(((CompoundConfigEntry<Object>) entry));
boolean oldNoopHandlerInstalled = contextData.noopHandlerInstalled();
contextData.noopHandlerInstalled(true);
Object value = innerCasted.read(reader, delegated, context);
contextData.noopHandlerInstalled(oldNoopHandlerInstalled);
return value;
} }
return innerCasted.read(reader, entry, context); return innerCasted.read(reader, entry, context);
}
}; };
} }
} }

View File

@@ -9,6 +9,7 @@ import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension; import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.hjson.HjsonLexer; import de.siphalor.tweed5.serde.hjson.HjsonLexer;
import de.siphalor.tweed5.serde.hjson.HjsonReader; import de.siphalor.tweed5.serde.hjson.HjsonReader;
import de.siphalor.tweed5.serde.hjson.HjsonWriter; import de.siphalor.tweed5.serde.hjson.HjsonWriter;
@@ -164,12 +165,14 @@ class AttributesReadWriteFilterExtensionImplTest {
} }
} }
"""))); """)));
Map<String, Object> readValue = configContainer.rootEntry().call(read( TweedReadResult<Map<String, Object>> readResult = configContainer.rootEntry().call(read(
reader, reader,
patchwork -> attributesReadWriteFilterExtension.addFilter(patchwork, "type", "a") patchwork -> attributesReadWriteFilterExtension.addFilter(patchwork, "type", "a")
)); ));
assertThat(readValue) assertThat(readResult)
.extracting(TweedReadResult::value)
.asInstanceOf(map(String.class, Object.class))
.containsEntry("first", "1st") .containsEntry("first", "1st")
.doesNotContainKey("second") .doesNotContainKey("second")
.hasEntrySatisfying( .hasEntrySatisfying(

View File

@@ -7,6 +7,7 @@ import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension; import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.hjson.HjsonLexer; import de.siphalor.tweed5.serde.hjson.HjsonLexer;
import de.siphalor.tweed5.serde.hjson.HjsonReader; import de.siphalor.tweed5.serde.hjson.HjsonReader;
import de.siphalor.tweed5.defaultextensions.patch.api.PatchExtension; import de.siphalor.tweed5.defaultextensions.patch.api.PatchExtension;
@@ -25,6 +26,7 @@ import static de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderW
import static de.siphalor.tweed5.testutils.generic.MapTestUtils.sequencedMap; import static de.siphalor.tweed5.testutils.generic.MapTestUtils.sequencedMap;
import static java.util.Map.entry; import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.InstanceOfAssertFactories.map;
public class AttributesReadWriteFilterExtensionImplWithPatchInfoTest { public class AttributesReadWriteFilterExtensionImplWithPatchInfoTest {
@Test @Test
@@ -65,7 +67,7 @@ public class AttributesReadWriteFilterExtensionImplWithPatchInfoTest {
var patchInfo = patchExtension.collectPatchInfo(readExtData); var patchInfo = patchExtension.collectPatchInfo(readExtData);
filterExtension.addFilter(readExtData, "type", "a"); filterExtension.addFilter(readExtData, "type", "a");
var value = readWriteExtension.read( var readResult = readWriteExtension.read(
new HjsonReader(new HjsonLexer(new StringReader(""" new HjsonReader(new HjsonLexer(new StringReader("""
{ {
"first": "FIRST", "first": "FIRST",
@@ -76,8 +78,10 @@ public class AttributesReadWriteFilterExtensionImplWithPatchInfoTest {
readExtData readExtData
); );
assertThat(value).extracting(v -> v.get("first")).isEqualTo("FIRST"); assertThat(readResult)
assertThat(value).extracting(v -> v.get("second")).isNull(); .extracting(TweedReadResult::value)
.asInstanceOf(map(String.class, Object.class))
.isEqualTo(Map.of("first", "FIRST"));
assertThat(patchInfo.containsEntry(firstEntry)).isTrue(); assertThat(patchInfo.containsEntry(firstEntry)).isTrue();
assertThat(patchInfo.containsEntry(secondEntry)).isFalse(); assertThat(patchInfo.containsEntry(secondEntry)).isFalse();
} }

View File

@@ -3,12 +3,10 @@ package de.siphalor.tweed5.defaultextensions.patch.impl;
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry; import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.middleware.Middleware; import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReadException;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReader; import de.siphalor.tweed5.serde.extension.api.TweedEntryReader;
import de.siphalor.tweed5.serde.extension.api.TweedReadContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext; import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension; import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.serde_api.api.TweedDataReader; import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.defaultextensions.patch.api.PatchExtension; import de.siphalor.tweed5.defaultextensions.patch.api.PatchExtension;
import de.siphalor.tweed5.defaultextensions.patch.api.PatchInfo; import de.siphalor.tweed5.defaultextensions.patch.api.PatchInfo;
import de.siphalor.tweed5.patchwork.api.Patchwork; import de.siphalor.tweed5.patchwork.api.Patchwork;
@@ -96,20 +94,13 @@ public class PatchExtensionImpl implements PatchExtension, ReadWriteRelatedExten
//noinspection unchecked //noinspection unchecked
TweedEntryReader<Object, ConfigEntry<Object>> innerCasted = TweedEntryReader<Object, ConfigEntry<Object>> innerCasted =
(TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) inner; (TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) inner;
return new TweedEntryReader<@Nullable Object, ConfigEntry<Object>>() { return (TweedEntryReader<Object, ConfigEntry<Object>>) (reader, entry, context) -> {
@Override TweedReadResult<Object> readResult = innerCasted.read(reader, entry, context);
public @Nullable Object read(
TweedDataReader reader,
ConfigEntry<Object> entry,
TweedReadContext context
) throws TweedEntryReadException {
Object readValue = innerCasted.read(reader, entry, context);
ReadWriteContextCustomData customData = context.extensionsData().get(readWriteContextDataAccess); ReadWriteContextCustomData customData = context.extensionsData().get(readWriteContextDataAccess);
if (customData != null && customData.patchInfo() != null) { if (customData != null && customData.patchInfo() != null) {
customData.patchInfo().addEntry(entry); customData.patchInfo().addEntry(entry);
} }
return readValue; return readResult;
}
}; };
} }
} }

View File

@@ -70,21 +70,7 @@ public class PatherExtensionImpl implements PatherExtension, ReadWriteRelatedExt
pathTracking = PathTracking.create(); pathTracking = PathTracking.create();
context.extensionsData().set(rwContextPathTrackingAccess, pathTracking); context.extensionsData().set(rwContextPathTrackingAccess, pathTracking);
try {
return castedInner.read(new PathTrackingDataReader(reader, pathTracking), entry, context); return castedInner.read(new PathTrackingDataReader(reader, pathTracking), entry, context);
} catch (TweedEntryReadException e) {
val exceptionPathTracking = e.context().extensionsData().get(rwContextPathTrackingAccess);
if (exceptionPathTracking != null) {
throw new TweedEntryReadException(
"Exception while reading entry at "
+ exceptionPathTracking.currentPath()
+ ": " + e.getMessage(),
e
);
} else {
throw e;
}
}
}; };
} }
}; };
@@ -116,7 +102,8 @@ public class PatherExtensionImpl implements PatherExtension, ReadWriteRelatedExt
try { try {
castedInner.write(new PathTrackingDataVisitor(writer, pathTracking), value, entry, context); castedInner.write(new PathTrackingDataVisitor(writer, pathTracking), value, entry, context);
} catch (TweedEntryWriteException e) { } catch (TweedEntryWriteException e) {
val exceptionPathTracking = e.context().extensionsData().get(rwContextPathTrackingAccess); PathTracking exceptionPathTracking =
e.context().extensionsData().get(rwContextPathTrackingAccess);
if (exceptionPathTracking != null) { if (exceptionPathTracking != null) {
throw new TweedEntryWriteException( throw new TweedEntryWriteException(
"Exception while writing entry at " "Exception while writing entry at "

View File

@@ -3,7 +3,6 @@ package de.siphalor.tweed5.defaultextensions.readfallback.impl;
import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.middleware.Middleware; import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReadException;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReader; import de.siphalor.tweed5.serde.extension.api.TweedEntryReader;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext; import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension; import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension;
@@ -11,6 +10,7 @@ import de.siphalor.tweed5.defaultextensions.pather.api.PatherExtension;
import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension; import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension;
import de.siphalor.tweed5.defaultextensions.readfallback.api.ReadFallbackExtension; import de.siphalor.tweed5.defaultextensions.readfallback.api.ReadFallbackExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension; import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import lombok.extern.apachecommons.CommonsLog; import lombok.extern.apachecommons.CommonsLog;
import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NonNull;
@@ -30,8 +30,6 @@ public class ReadFallbackExtensionImpl implements ReadFallbackExtension, ReadWri
PresetsExtension presetsExtension = configContainer.extension(PresetsExtension.class) PresetsExtension presetsExtension = configContainer.extension(PresetsExtension.class)
.orElseThrow(() -> new IllegalStateException(getClass().getSimpleName() .orElseThrow(() -> new IllegalStateException(getClass().getSimpleName()
+ " requires " + ReadFallbackExtension.class.getSimpleName())); + " requires " + ReadFallbackExtension.class.getSimpleName()));
PatherExtension patherExtension = configContainer.extension(PatherExtension.class).orElse(null);
context.registerReaderMiddleware(new Middleware<TweedEntryReader<?, ?>>() { context.registerReaderMiddleware(new Middleware<TweedEntryReader<?, ?>>() {
@Override @Override
public String id() { public String id() {
@@ -53,23 +51,16 @@ public class ReadFallbackExtensionImpl implements ReadFallbackExtension, ReadWri
//noinspection unchecked //noinspection unchecked
TweedEntryReader<Object, ConfigEntry<Object>> castedInner = TweedEntryReader<Object, ConfigEntry<Object>> castedInner =
(TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) inner; (TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) inner;
return (TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) (reader, entry, context) -> { return (TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) (reader, entry, context) ->
try { castedInner.read(reader, entry, context).catchError(
return castedInner.read(reader, entry, context); issues -> {
} catch (TweedEntryReadException e) { Object fallback =
if (patherExtension == null) { presetsExtension.presetValue(entry, PresetsExtension.DEFAULT_PRESET_NAME);
log.error("Failed to read entry: " + e.getMessage(), e); return TweedReadResult.withIssues(fallback, issues);
} else { },
log.error( context
"Failed to read entry: " + e.getMessage()
+ " at " + patherExtension.getPath(context),
e
); );
} }
return presetsExtension.presetValue(entry, PresetsExtension.DEFAULT_PRESET_NAME);
}
};
}
}); });
} }
} }

View File

@@ -9,12 +9,6 @@ import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer; import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
import de.siphalor.tweed5.core.api.middleware.Middleware; import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.core.api.middleware.MiddlewareContainer; import de.siphalor.tweed5.core.api.middleware.MiddlewareContainer;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReadException;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReader;
import de.siphalor.tweed5.serde.extension.api.TweedReadContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtension; import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer; import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTracking; import de.siphalor.tweed5.defaultextensions.pather.api.PathTracking;
@@ -30,6 +24,13 @@ import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssu
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult; import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult;
import de.siphalor.tweed5.patchwork.api.Patchwork; import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess; import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReader;
import de.siphalor.tweed5.serde.extension.api.TweedReadContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadIssue;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import lombok.*; import lombok.*;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
@@ -233,8 +234,7 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
return (TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext context) -> { return (TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext context) -> {
ValidationIssues validationIssues = getOrCreateValidationIssues(context.extensionsData()); ValidationIssues validationIssues = getOrCreateValidationIssues(context.extensionsData());
Object value = castedInner.read(reader, entry, context); return castedInner.read(reader, entry, context).andThen(value -> {
ConfigEntryValidator entryValidator = entry.extensionsData() ConfigEntryValidator entryValidator = entry.extensionsData()
.get(customEntryDataAccess) .get(customEntryDataAccess)
.completeValidator(); .completeValidator();
@@ -242,26 +242,39 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
ValidationResult<Object> validationResult = entryValidator.validate(entry, value); ValidationResult<Object> validationResult = entryValidator.validate(entry, value);
if (!validationResult.issues().isEmpty()) { if (validationResult.issues().isEmpty()) {
return TweedReadResult.ok(validationResult.value());
}
String path = patherExtension.getPath(context); String path = patherExtension.getPath(context);
validationIssues.issuesByPath().put(path, new ValidationIssues.EntryIssues( validationIssues.issuesByPath().put(path, new ValidationIssues.EntryIssues(
entry, entry,
validationResult.issues() validationResult.issues()
)); ));
}
if (validationResult.hasError()) { if (validationResult.hasError()) {
throw new TweedEntryReadException( return TweedReadResult.failed(
"Failed to validate entry: " + validationResult.issues(), mapValidationIssuesToReadIssues(validationResult.issues(), context)
context );
} else {
return TweedReadResult.withIssues(
validationResult.value(),
mapValidationIssuesToReadIssues(validationResult.issues(), context)
); );
} }
}, context);
return validationResult.value();
}; };
} }
} }
private static TweedReadIssue[] mapValidationIssuesToReadIssues(
Collection<ValidationIssue> issues,
TweedReadContext context
) {
return issues.stream().map(
validationIssue -> TweedReadIssue.error(validationIssue.message(), context)
).toArray(TweedReadIssue[]::new);
}
private ValidationIssues getOrCreateValidationIssues(Patchwork readContextExtensionsData) { private ValidationIssues getOrCreateValidationIssues(Patchwork readContextExtensionsData) {
assert readContextValidationIssuesAccess != null; assert readContextValidationIssuesAccess != null;
ValidationIssues validationIssues = readContextExtensionsData.get(readContextValidationIssuesAccess); ValidationIssues validationIssues = readContextExtensionsData.get(readContextValidationIssuesAccess);

View File

@@ -9,6 +9,7 @@ import de.siphalor.tweed5.core.impl.entry.NullableConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension; import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.hjson.HjsonLexer; import de.siphalor.tweed5.serde.hjson.HjsonLexer;
import de.siphalor.tweed5.serde.hjson.HjsonReader; import de.siphalor.tweed5.serde.hjson.HjsonReader;
import de.siphalor.tweed5.defaultextensions.patch.api.PatchExtension; import de.siphalor.tweed5.defaultextensions.patch.api.PatchExtension;
@@ -150,7 +151,7 @@ class PatchExtensionImplTest {
PatchExtension patchExtension = configContainer.extension(PatchExtension.class).orElseThrow(); PatchExtension patchExtension = configContainer.extension(PatchExtension.class).orElseThrow();
var patchInfo = new AtomicReference<@Nullable PatchInfo>(); var patchInfo = new AtomicReference<@Nullable PatchInfo>();
Map<String, Object> patchValue = rootEntry.call(read( TweedReadResult<Map<String, Object>> patchResult = rootEntry.call(read(
reader, extensionsData -> reader, extensionsData ->
patchInfo.set(patchExtension.collectPatchInfo(extensionsData)) patchInfo.set(patchExtension.collectPatchInfo(extensionsData))
)); ));
@@ -158,7 +159,7 @@ class PatchExtensionImplTest {
Map<String, Object> resultValue = patchExtension.patch( Map<String, Object> resultValue = patchExtension.patch(
rootEntry, rootEntry,
baseValue, baseValue,
patchValue, patchResult.value(),
Objects.requireNonNull(patchInfo.get()) Objects.requireNonNull(patchInfo.get())
); );

View File

@@ -1,16 +1,19 @@
package de.siphalor.tweed5.defaultextensions.readfallback.impl; package de.siphalor.tweed5.defaultextensions.readfallback.impl;
import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry; import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.impl.DefaultConfigContainer; import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.defaultextensions.pather.api.PatherExtension;
import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension;
import de.siphalor.tweed5.defaultextensions.readfallback.api.ReadFallbackExtension;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension; import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.TweedEntryReadException;
import de.siphalor.tweed5.serde.extension.api.TweedReadContext; import de.siphalor.tweed5.serde.extension.api.TweedReadContext;
import de.siphalor.tweed5.serde.extension.api.TweedWriteContext; import de.siphalor.tweed5.serde.extension.api.TweedWriteContext;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadIssue;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriter; import de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriter;
import de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriters; import de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriters;
import de.siphalor.tweed5.serde.hjson.HjsonLexer; import de.siphalor.tweed5.serde.hjson.HjsonLexer;
@@ -19,9 +22,6 @@ import de.siphalor.tweed5.serde_api.api.TweedDataReadException;
import de.siphalor.tweed5.serde_api.api.TweedDataReader; import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor; import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import de.siphalor.tweed5.serde_api.api.TweedDataWriteException; import de.siphalor.tweed5.serde_api.api.TweedDataWriteException;
import de.siphalor.tweed5.defaultextensions.pather.api.PatherExtension;
import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension;
import de.siphalor.tweed5.defaultextensions.readfallback.api.ReadFallbackExtension;
import de.siphalor.tweed5.testutils.generic.log.LogCaptureMockitoExtension; import de.siphalor.tweed5.testutils.generic.log.LogCaptureMockitoExtension;
import de.siphalor.tweed5.testutils.generic.log.LogsCaptor; import de.siphalor.tweed5.testutils.generic.log.LogsCaptor;
import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.NullMarked;
@@ -34,12 +34,11 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension.presetValue;
import static de.siphalor.tweed5.serde.extension.api.ReadWriteExtension.entryReaderWriter; import static de.siphalor.tweed5.serde.extension.api.ReadWriteExtension.entryReaderWriter;
import static de.siphalor.tweed5.serde.extension.api.ReadWriteExtension.read; import static de.siphalor.tweed5.serde.extension.api.ReadWriteExtension.read;
import static de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriters.compoundReaderWriter; import static de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriters.compoundReaderWriter;
import static de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension.presetValue;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.InstanceOfAssertFactories.STRING;
@ExtendWith(LogCaptureMockitoExtension.class) @ExtendWith(LogCaptureMockitoExtension.class)
@NullMarked @NullMarked
@@ -59,12 +58,18 @@ class ReadFallbackExtensionImplTest {
configContainer.attachTree(entry); configContainer.attachTree(entry);
configContainer.initialize(); configContainer.initialize();
assertThat(entry.call(read(new HjsonReader(new HjsonLexer(new StringReader("12")))))).isEqualTo(12); assertThat(entry.call(read(new HjsonReader(new HjsonLexer(new StringReader("12"))))))
.isEqualTo(TweedReadResult.ok(12));
assertThat(logsCaptor.getLogsForLevel(Level.ERROR)).isEmpty(); assertThat(logsCaptor.getLogsForLevel(Level.ERROR)).isEmpty();
logsCaptor.clear(); logsCaptor.clear();
assertThat(entry.call(read(new HjsonReader(new HjsonLexer(new StringReader("13")))))).isEqualTo(-1); assertThat(entry.call(read(new HjsonReader(new HjsonLexer(new StringReader("13")))))).satisfies(
assertThat(logsCaptor.getLogsForLevel(Level.ERROR)).hasSize(1); r -> assertThat(r).extracting(TweedReadResult::value).isEqualTo(-1),
r -> assertThat(r.issues()).singleElement()
.extracting(TweedReadIssue::exception)
.extracting(Throwable::getMessage)
.isEqualTo("Value is not even")
);
} }
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes"})
@@ -105,6 +110,7 @@ class ReadFallbackExtensionImplTest {
assertThat(root.call(read(new HjsonReader(new HjsonLexer(new StringReader( assertThat(root.call(read(new HjsonReader(new HjsonLexer(new StringReader(
"{first: {second: 12}}" "{first: {second: 12}}"
)))))) ))))))
.extracting(TweedReadResult::value)
.extracting(map -> (Map<String, Object>) map.get("first")) .extracting(map -> (Map<String, Object>) map.get("first"))
.extracting(map -> (Integer) map.get("second")) .extracting(map -> (Integer) map.get("second"))
.isEqualTo(12); .isEqualTo(12);
@@ -114,22 +120,20 @@ class ReadFallbackExtensionImplTest {
assertThat(root.call(read(new HjsonReader(new HjsonLexer(new StringReader( assertThat(root.call(read(new HjsonReader(new HjsonLexer(new StringReader(
"{first: {second: 13}}" "{first: {second: 13}}"
)))))) ))))))
.extracting(TweedReadResult::value)
.extracting(map -> (Map<String, Object>) map.get("first")) .extracting(map -> (Map<String, Object>) map.get("first"))
.extracting(map -> (Integer) map.get("second")) .extracting(map -> (Integer) map.get("second"))
.isEqualTo(-1); .isEqualTo(-1);
assertThat(logsCaptor.getLogsForLevel(Level.ERROR)).singleElement() assertThat(logsCaptor.getLogsForLevel(Level.ERROR)).isEmpty();
.extracting(ILoggingEvent::getMessage)
.asInstanceOf(STRING)
.contains("first.second");
} }
private static class EvenIntReader implements TweedEntryReaderWriter<Integer, ConfigEntry<Integer>> { private static class EvenIntReader implements TweedEntryReaderWriter<Integer, ConfigEntry<Integer>> {
@Override @Override
public Integer read( public TweedReadResult<Integer> read(
TweedDataReader reader, TweedDataReader reader,
ConfigEntry<Integer> entry, ConfigEntry<Integer> entry,
TweedReadContext context TweedReadContext context
) throws TweedEntryReadException { ) {
int value; int value;
try { try {
value = reader.readToken().readAsInt(); value = reader.readToken().readAsInt();
@@ -137,9 +141,9 @@ class ReadFallbackExtensionImplTest {
throw new IllegalStateException("Should not be called", e); throw new IllegalStateException("Should not be called", e);
} }
if (value % 2 == 0) { if (value % 2 == 0) {
return value; return TweedReadResult.ok(value);
} else { } else {
throw new TweedEntryReadException("Value is not even", context); return TweedReadResult.error(TweedReadIssue.error("Value is not even", context));
} }
} }

View File

@@ -7,6 +7,7 @@ import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension; import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.hjson.HjsonCommentType; import de.siphalor.tweed5.serde.hjson.HjsonCommentType;
import de.siphalor.tweed5.serde.hjson.HjsonLexer; import de.siphalor.tweed5.serde.hjson.HjsonLexer;
import de.siphalor.tweed5.serde.hjson.HjsonReader; import de.siphalor.tweed5.serde.hjson.HjsonReader;
@@ -156,12 +157,14 @@ class ValidationExtensionImplTest {
} }
"""))); """)));
var validationIssues = new AtomicReference<@Nullable ValidationIssues>(); var validationIssues = new AtomicReference<@Nullable ValidationIssues>();
Map<String, Object> value = configContainer.rootEntry().call(read( TweedReadResult<Map<String, Object>> value = configContainer.rootEntry().call(read(
reader, reader,
extensionsData -> validationIssues.set(validationExtension.captureValidationIssues(extensionsData)) extensionsData -> validationIssues.set(validationExtension.captureValidationIssues(extensionsData))
)); ));
assertThat(value).isEqualTo(Map.of("byte", (byte) 11, "int", 123, "double", 0.5)); assertThat(value)
.extracting(TweedReadResult::value)
.isEqualTo(Map.of("byte", (byte) 11, "int", 123, "double", 0.5));
//noinspection DataFlowIssue //noinspection DataFlowIssue
assertThat(validationIssues.get()).isNotNull().satisfies( assertThat(validationIssues.get()).isNotNull().satisfies(
vi -> assertValidationIssue( vi -> assertValidationIssue(

View File

@@ -5,6 +5,7 @@ import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
import de.siphalor.tweed5.core.impl.DefaultConfigContainer; import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension; import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.hjson.HjsonCommentType; import de.siphalor.tweed5.serde.hjson.HjsonCommentType;
import de.siphalor.tweed5.serde.hjson.HjsonLexer; import de.siphalor.tweed5.serde.hjson.HjsonLexer;
import de.siphalor.tweed5.serde.hjson.HjsonReader; import de.siphalor.tweed5.serde.hjson.HjsonReader;
@@ -30,6 +31,7 @@ import static de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension.
import static de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension.validators; import static de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension.validators;
import static de.siphalor.tweed5.serde.extension.api.ReadWriteExtension.*; import static de.siphalor.tweed5.serde.extension.api.ReadWriteExtension.*;
import static de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriters.*; import static de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriters.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
class ValidationFallbackExtensionImplTest { class ValidationFallbackExtensionImplTest {
@@ -114,8 +116,9 @@ class ValidationFallbackExtensionImplTest {
@ParameterizedTest @ParameterizedTest
@ValueSource(strings = {"0", "7", "123", "null"}) @ValueSource(strings = {"0", "7", "123", "null"})
void fallbackTriggers(String input) { void fallbackTriggers(String input) {
Integer result = intEntry.call(read(new HjsonReader(new HjsonLexer(new StringReader(input))))); TweedReadResult<Integer> result = intEntry.call(read(new HjsonReader(new HjsonLexer(new StringReader(input)))));
assertEquals(3, result); assertThat(result).extracting(TweedReadResult::value).isEqualTo(3);
assertThat(result.issues()).hasSize(1);
} }
@Test @Test

View File

@@ -2,6 +2,7 @@ package de.siphalor.tweed5.serde.extension.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriter; import de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriter;
import de.siphalor.tweed5.serde.extension.impl.ReadWriteExtensionImpl; import de.siphalor.tweed5.serde.extension.impl.ReadWriteExtensionImpl;
import de.siphalor.tweed5.serde_api.api.TweedDataReader; import de.siphalor.tweed5.serde_api.api.TweedDataReader;
@@ -55,11 +56,11 @@ public interface ReadWriteExtension extends TweedExtension {
}; };
} }
static <T extends @Nullable Object> Function<ConfigEntry<T>, T> read(TweedDataReader reader) { static <T extends @Nullable Object> Function<ConfigEntry<T>, TweedReadResult<T>> read(TweedDataReader reader) {
return read(reader, null); return read(reader, null);
} }
static <T extends @Nullable Object> Function<ConfigEntry<T>, T> read( static <T extends @Nullable Object> Function<ConfigEntry<T>, TweedReadResult<T>> read(
TweedDataReader reader, TweedDataReader reader,
@Nullable Consumer<Patchwork> contextExtensionsDataCustomizer @Nullable Consumer<Patchwork> contextExtensionsDataCustomizer
) { ) {
@@ -115,7 +116,7 @@ public interface ReadWriteExtension extends TweedExtension {
Patchwork createReadWriteContextExtensionsData(); Patchwork createReadWriteContextExtensionsData();
<T extends @Nullable Object> T read(TweedDataReader reader, ConfigEntry<T> entry, Patchwork contextExtensionsData) <T extends @Nullable Object> TweedReadResult<T> read(TweedDataReader reader, ConfigEntry<T> entry, Patchwork contextExtensionsData)
throws TweedEntryReadException; throws TweedEntryReadException;
<T extends @Nullable Object> void write( <T extends @Nullable Object> void write(

View File

@@ -1,10 +1,11 @@
package de.siphalor.tweed5.serde.extension.api; package de.siphalor.tweed5.serde.extension.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde_api.api.TweedDataReader; import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
@FunctionalInterface @FunctionalInterface
public interface TweedEntryReader<T extends @Nullable Object, C extends ConfigEntry<T>> { public interface TweedEntryReader<T extends @Nullable Object, C extends ConfigEntry<T>> {
T read(TweedDataReader reader, C entry, TweedReadContext context) throws TweedEntryReadException; TweedReadResult<T> read(TweedDataReader reader, C entry, TweedReadContext context);
} }

View File

@@ -0,0 +1,7 @@
package de.siphalor.tweed5.serde.extension.api.read.result;
import org.jspecify.annotations.Nullable;
public interface ThrowingFunction<T extends @Nullable Object, R extends @Nullable Object> {
R apply(T value) throws Exception;
}

View File

@@ -0,0 +1,71 @@
package de.siphalor.tweed5.serde.extension.api.read.result;
import de.siphalor.tweed5.serde.extension.api.TweedReadContext;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@FunctionalInterface
public interface ThrowingReadFunction<T extends @Nullable Object, R extends @Nullable Object> {
static <T extends @Nullable Object, R extends @Nullable Object> ThrowingReadFunction<T, R> any(
TweedReadContext readContext,
ThrowingFunction<T, R>... functions
) {
//noinspection unchecked
ThrowingReadFunction<T, R>[] wrappedFunctions = new ThrowingReadFunction[functions.length];
for (int i = 0; i < functions.length; i++) {
wrappedFunctions[i] = wrap(readContext, functions[i]);
}
return any(wrappedFunctions);
}
static <T extends @Nullable Object, R extends @Nullable Object> ThrowingReadFunction<T, R> any(
ThrowingReadFunction<T, R>... functions
) {
if (functions.length == 0) {
throw new IllegalArgumentException("At least one function is required");
}
return value -> {
List<TweedReadIssue> issues = null;
boolean foundEmpty = false;
for (ThrowingReadFunction<T, R> function : functions) {
TweedReadResult<R> result = function.apply(value);
if (result.hasValue()) {
return result;
} else if (!result.isFailed()) {
foundEmpty = true;
}
if (result.hasIssues()) {
if (issues == null) {
issues = new ArrayList<>(functions.length);
issues.addAll(Arrays.asList(result.issues()));
}
}
}
if (foundEmpty) {
return TweedReadResult.empty();
}
if (issues != null) {
return TweedReadResult.failed(issues.toArray(new TweedReadIssue[0]));
}
throw new IllegalStateException("Unreachable");
};
}
static <T extends @Nullable Object, R extends @Nullable Object> ThrowingReadFunction<T, R> wrap(
TweedReadContext readContext,
ThrowingFunction<T, R> function
) {
return value -> {
try {
return TweedReadResult.ok(function.apply(value));
} catch (Exception e) {
return TweedReadResult.failed(TweedReadIssue.error(e, readContext));
}
};
}
TweedReadResult<R> apply(T value) throws Exception;
}

View File

@@ -0,0 +1,26 @@
package de.siphalor.tweed5.serde.extension.api.read.result;
import de.siphalor.tweed5.serde.extension.api.TweedReadContext;
import lombok.*;
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode
public class TweedReadIssue {
@Getter
private final Exception exception;
private final TweedReadContext readContext;
public static TweedReadIssue error(String message, TweedReadContext readContext) {
return new TweedReadIssue(new Exception(message), readContext);
}
public static TweedReadIssue error(Exception exception, TweedReadContext readContext) {
return new TweedReadIssue(exception, readContext);
}
@Override
public String toString() {
return "TweedReadIssue(" + exception + ")";
}
}

View File

@@ -0,0 +1,168 @@
package de.siphalor.tweed5.serde.extension.api.read.result;
import de.siphalor.tweed5.serde.extension.api.TweedReadContext;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.Nullable;
import java.util.Arrays;
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode(doNotUseGetters = true)
public class TweedReadResult<T extends @Nullable Object> {
private static final TweedReadResult<?> EMPTY = new TweedReadResult<>(null, false, null, Severity.OK);
private final T value;
private final boolean hasValue;
private final TweedReadIssue @Nullable [] issues;
private final Severity severity;
public static <T extends @Nullable Object> TweedReadResult<T> ok(T value) {
return new TweedReadResult<>(value, true, null, Severity.OK);
}
public static <T extends @Nullable Object> TweedReadResult<T> empty() {
//noinspection unchecked
return (TweedReadResult<T>) EMPTY;
}
public static <T extends @Nullable Object> TweedReadResult<T> withIssues(T value, TweedReadIssue... issues) {
return new TweedReadResult<>(value, true, issues, Severity.OK);
}
public static <T extends @Nullable Object> TweedReadResult<T> error(TweedReadIssue... issues) {
return new TweedReadResult<>(null, false, issues, Severity.ERROR);
}
public static <T extends @Nullable Object> TweedReadResult<T> failed(TweedReadIssue... issues) {
if (issues.length == 0) {
throw new IllegalArgumentException("At least one issue is required for a failed read result");
}
return new TweedReadResult<>(null, false, issues, Severity.FAILURE);
}
public T value() {
if (hasValue) {
return value;
}
throw new IllegalStateException("No value present");
}
public boolean hasValue() {
return hasValue;
}
public boolean hasIssues() {
return issues != null;
}
public TweedReadIssue [] issues() {
if (issues == null) {
return new TweedReadIssue[0];
}
return issues;
}
public boolean isError() {
return severity == Severity.ERROR;
}
public boolean isFailed() {
return severity == Severity.FAILURE;
}
public <R extends @Nullable Object> TweedReadResult<R> map(
ThrowingFunction<T, R> function,
TweedReadContext readContext
) {
return andThen(value -> ok(function.apply(value)), readContext);
}
public <R extends @Nullable Object> TweedReadResult<R> andThen(
ThrowingReadFunction<T, R> function,
TweedReadContext readContext
) {
if (severity != Severity.OK || !hasValue()) {
//noinspection unchecked
return (TweedReadResult<R>) this;
}
try {
TweedReadResult<R> innerResult = function.apply(value());
TweedReadIssue[] issues = combineIssues(issues(), innerResult.issues());
if (issues.length == 0) {
return innerResult;
}
if (innerResult.isFailed()) {
return failed(issues);
} else if (innerResult.isError()) {
return error(issues);
} else if (innerResult.hasValue()) {
return withIssues(innerResult.value(), issues);
} else {
return empty();
}
} catch (Exception exception) {
if (hasIssues()) {
TweedReadIssue[] issues = new TweedReadIssue[issues().length + 1];
System.arraycopy(issues(), 0, issues, 0, issues().length);
issues[issues().length] = TweedReadIssue.error(exception, readContext);
return failed(issues);
}
return failed(TweedReadIssue.error(exception, readContext));
}
}
public TweedReadResult<T> catchError(
ThrowingFunction<TweedReadIssue[], TweedReadResult<T>> errorHandler,
TweedReadContext readContext
) {
if (severity != Severity.ERROR) {
return this;
}
try {
return errorHandler.apply(issues());
} catch (Exception exception) {
TweedReadIssue[] issues = new TweedReadIssue[issues().length + 1];
System.arraycopy(issues(), 0, issues, 0, issues().length);
issues[issues().length] = TweedReadIssue.error(exception, readContext);
return error(issues);
}
}
private static TweedReadIssue[] combineIssues(TweedReadIssue[] a, TweedReadIssue[] b) {
if (a.length == 0) {
return b;
}
if (b.length == 0) {
return a;
}
TweedReadIssue[] result = new TweedReadIssue[a.length + b.length];
System.arraycopy(a, 0, result, 0, a.length);
System.arraycopy(b, 0, result, a.length, b.length);
return result;
}
@Override
public String toString() {
if (severity == Severity.FAILURE) {
return "TweedReadResult#failed(issues=" + Arrays.toString(issues) + ')';
} else if (severity == Severity.ERROR) {
return "TweedReadResult#error(issues=" + Arrays.toString(issues) + ')';
} else if (hasValue) {
if (hasIssues()) {
return "TweedReadResult#withIssues(value=" + value + ", issues=" + Arrays.toString(issues) + ')';
} else {
return "TweedReadResult#ok(value=" + value + ')';
}
} else {
return "TweedReadResult#empty";
}
}
private enum Severity {
OK,
ERROR,
FAILURE,
}
}

View File

@@ -0,0 +1,4 @@
@NullMarked
package de.siphalor.tweed5.serde.extension.api.read.result;
import org.jspecify.annotations.NullMarked;

View File

@@ -9,6 +9,7 @@ import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.serde.extension.api.*; import de.siphalor.tweed5.serde.extension.api.*;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext; import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension; import de.siphalor.tweed5.serde.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde_api.api.TweedDataReader; import de.siphalor.tweed5.serde_api.api.TweedDataReader;
import de.siphalor.tweed5.serde_api.api.TweedDataVisitor; import de.siphalor.tweed5.serde_api.api.TweedDataVisitor;
import de.siphalor.tweed5.serde_api.api.TweedDataWriteException; import de.siphalor.tweed5.serde_api.api.TweedDataWriteException;
@@ -136,7 +137,7 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension {
return readWriteContextPatchworkFactory.create(); return readWriteContextPatchworkFactory.create();
} }
public <T extends @Nullable Object> T read( public <T extends @Nullable Object> TweedReadResult<T> read(
TweedDataReader reader, TweedDataReader reader,
ConfigEntry<T> entry, ConfigEntry<T> entry,
Patchwork contextExtensionsData Patchwork contextExtensionsData

View File

@@ -5,6 +5,9 @@ import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.NullableConfigEntry; import de.siphalor.tweed5.core.api.entry.NullableConfigEntry;
import de.siphalor.tweed5.serde.extension.api.*; import de.siphalor.tweed5.serde.extension.api.*;
import de.siphalor.tweed5.serde.extension.api.read.result.ThrowingReadFunction;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadIssue;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriter; import de.siphalor.tweed5.serde.extension.api.readwrite.TweedEntryReaderWriter;
import de.siphalor.tweed5.serde_api.api.*; import de.siphalor.tweed5.serde_api.api.*;
import lombok.AccessLevel; import lombok.AccessLevel;
@@ -38,15 +41,14 @@ public class TweedEntryReaderWriterImpls {
public static class NullableReaderWriter<T extends @Nullable Object> implements TweedEntryReaderWriter<T, NullableConfigEntry<T>> { public static class NullableReaderWriter<T extends @Nullable Object> implements TweedEntryReaderWriter<T, NullableConfigEntry<T>> {
@Override @Override
public T read(TweedDataReader reader, NullableConfigEntry<T> entry, TweedReadContext context) public TweedReadResult<T> read(TweedDataReader reader, NullableConfigEntry<T> entry, TweedReadContext context) {
throws TweedEntryReadException {
try { try {
if (reader.peekToken().isNull()) { if (reader.peekToken().isNull()) {
reader.readToken(); reader.readToken();
return null; return TweedReadResult.ok(null);
} }
} catch (TweedDataReadException e) { } catch (TweedDataReadException e) {
throw new TweedEntryReadException(e, context); return TweedReadResult.failed(TweedReadIssue.error(e, context));
} }
TweedEntryReader<T, ConfigEntry<T>> nonNullReader = context.readWriteExtension().getReaderChain(entry.nonNullEntry()); TweedEntryReader<T, ConfigEntry<T>> nonNullReader = context.readWriteExtension().getReaderChain(entry.nonNullEntry());
@@ -75,14 +77,14 @@ public class TweedEntryReaderWriterImpls {
private final TweedEntryReader<T, C> delegate; private final TweedEntryReader<T, C> delegate;
@Override @Override
public T read(TweedDataReader reader, C entry, TweedReadContext context) throws TweedEntryReadException { public TweedReadResult<T> read(TweedDataReader reader, C entry, TweedReadContext context) {
try { try {
if (reader.peekToken().isNull()) { if (reader.peekToken().isNull()) {
reader.readToken(); reader.readToken();
return null; return TweedReadResult.ok(null);
} }
} catch (TweedDataReadException e) { } catch (TweedDataReadException e) {
throw new TweedEntryReadException(e, context); return TweedReadResult.failed(TweedReadIssue.error(e, context));
} }
return delegate.read(reader, entry, context); return delegate.read(reader, entry, context);
} }
@@ -109,12 +111,11 @@ public class TweedEntryReaderWriterImpls {
private final PrimitiveWriteFunction<T> writerFunction; private final PrimitiveWriteFunction<T> writerFunction;
@Override @Override
public T read(TweedDataReader reader, ConfigEntry<T> entry, TweedReadContext context) public TweedReadResult<T> read(TweedDataReader reader, ConfigEntry<T> entry, TweedReadContext context) {
throws TweedEntryReadException {
try { try {
return readerFunction.read(reader.readToken()); return TweedReadResult.ok(readerFunction.read(reader.readToken()));
} catch (TweedDataReadException e) { } catch (TweedDataReadException e) {
throw new TweedEntryReadException(e, context); return TweedReadResult.failed(TweedReadIssue.error(e, context));
} }
} }
@@ -128,20 +129,15 @@ public class TweedEntryReaderWriterImpls {
@RequiredArgsConstructor @RequiredArgsConstructor
public static class EnumReaderWriter<T extends Enum<?>> implements TweedEntryReaderWriter<T, ConfigEntry<T>> { public static class EnumReaderWriter<T extends Enum<?>> implements TweedEntryReaderWriter<T, ConfigEntry<T>> {
@Override @Override
public T read(TweedDataReader reader, ConfigEntry<T> entry, TweedReadContext context) throws public TweedReadResult<T> read(TweedDataReader reader, ConfigEntry<T> entry, TweedReadContext context) {
TweedEntryReadException {
try {
TweedDataToken token = reader.readToken();
assertIsToken(token, TweedDataToken::canReadAsString, "Expected string", context);
//noinspection unchecked,rawtypes //noinspection unchecked,rawtypes
return (T) Enum.valueOf(((Class) entry.valueClass()), token.readAsString()); return requireToken(reader, TweedDataToken::canReadAsString, "Expected string", context)
} catch (TweedDataReadException | IllegalArgumentException e) { .map(TweedDataToken::readAsString, context)
throw new TweedEntryReadException( .andThen(ThrowingReadFunction.any(
"Failed reading enum value for " + entry.valueClass().getName(), context,
e, value -> (T) Enum.valueOf(((Class) entry.valueClass()), value),
context value -> (T) Enum.valueOf(((Class) entry.valueClass()), value.toUpperCase(Locale.ROOT))
); ), context);
}
} }
@Override @Override
@@ -158,49 +154,46 @@ public class TweedEntryReaderWriterImpls {
public static class CollectionReaderWriter<T extends @Nullable Object, C extends Collection<T>> implements TweedEntryReaderWriter<C, CollectionConfigEntry<T, C>> { public static class CollectionReaderWriter<T extends @Nullable Object, C extends Collection<T>> implements TweedEntryReaderWriter<C, CollectionConfigEntry<T, C>> {
@Override @Override
public C read(TweedDataReader reader, CollectionConfigEntry<T, C> entry, TweedReadContext context) throws public TweedReadResult<C> read(TweedDataReader reader, CollectionConfigEntry<T, C> entry, TweedReadContext context) {
TweedEntryReadException { return requireToken(reader, TweedDataToken::isListStart, "Expected list start", context).andThen(_token -> {
TweedDataToken token; if (reader.peekToken().isListEnd()) {
try { return TweedReadResult.ok(entry.instantiateCollection(0));
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);
} }
ConfigEntry<T> elementEntry = entry.elementEntry(); ConfigEntry<T> elementEntry = entry.elementEntry();
TweedEntryReader<T, ConfigEntry<T>> elementReader = context.readWriteExtension().getReaderChain(elementEntry); TweedEntryReader<T, ConfigEntry<T>> elementReader = context.readWriteExtension().getReaderChain(elementEntry);
List<@Nullable T> list = new ArrayList<>(20); List<T> list = new ArrayList<>(20);
List<TweedReadIssue> issues = new ArrayList<>();
while (true) { while (true) {
try { try {
token = reader.peekToken(); TweedDataToken token = reader.peekToken();
if (token.isListEnd()) { if (token.isListEnd()) {
reader.readToken(); reader.readToken();
break; break;
} else if (token.isListValue()) { } else if (token.isListValue()) {
list.add(elementReader.read(reader, elementEntry, context)); TweedReadResult<T> elementResult = elementReader.read(reader, elementEntry, context);
issues.addAll(Arrays.asList(elementResult.issues()));
if (elementResult.isFailed() || elementResult.isError()) {
return TweedReadResult.failed(issues.toArray(new TweedReadIssue[0]));
} else if (elementResult.hasValue()) {
list.add(elementResult.value());
}
} else { } else {
throw new TweedEntryReadException( issues.add(TweedReadIssue.error(
"Unexpected token " + token + ": expected next list value or list end", "Unexpected token " + token + ": expected next list value or list end",
context context
); ));
return TweedReadResult.failed(issues.toArray(new TweedReadIssue[0]));
} }
} catch (TweedDataReadException e) { } catch (TweedDataReadException e) {
throw new TweedEntryReadException( issues.add(TweedReadIssue.error(e, context));
"Failed reading element " + list.size() + " of collection",
e,
context
);
} }
} }
C result = entry.instantiateCollection(list.size()); C result = entry.instantiateCollection(list.size());
result.addAll(list); result.addAll(list);
return result; return TweedReadResult.ok(result);
}, context);
} }
@Override @Override
@@ -226,16 +219,11 @@ public class TweedEntryReaderWriterImpls {
public static class CompoundReaderWriter<T> implements TweedEntryReaderWriter<T, CompoundConfigEntry<T>> { public static class CompoundReaderWriter<T> implements TweedEntryReaderWriter<T, CompoundConfigEntry<T>> {
@Override @Override
public T read(TweedDataReader reader, CompoundConfigEntry<T> entry, TweedReadContext context) throws public TweedReadResult<T> read(TweedDataReader reader, CompoundConfigEntry<T> entry, TweedReadContext context) {
TweedEntryReadException { return requireToken(reader, TweedDataToken::isMapStart, "Expected map start", context).andThen(_token -> {
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.instantiateValue(); T compoundValue = entry.instantiateValue();
List<TweedReadIssue> issues = new ArrayList<>();
while (true) { while (true) {
try { try {
TweedDataToken token = reader.readToken(); TweedDataToken token = reader.readToken();
@@ -247,24 +235,38 @@ public class TweedEntryReaderWriterImpls {
//noinspection unchecked //noinspection unchecked
ConfigEntry<Object> subEntry = (ConfigEntry<Object>) compoundEntries.get(key); ConfigEntry<Object> subEntry = (ConfigEntry<Object>) compoundEntries.get(key);
if (subEntry == null) { if (subEntry == null) {
//noinspection DataFlowIssue TweedReadResult<Object> noopResult = NOOP_READER_WRITER.read(reader, null, context);
NOOP_READER_WRITER.read(reader, null, context); issues.addAll(Arrays.asList(noopResult.issues()));
if (noopResult.isFailed()) {
return TweedReadResult.failed(issues.toArray(new TweedReadIssue[0]));
}
continue; continue;
} }
val subEntryReaderChain = context.readWriteExtension().getReaderChain(subEntry); val subEntryReaderChain = context.readWriteExtension().getReaderChain(subEntry);
Object subEntryValue = subEntryReaderChain.read(reader, subEntry, context); TweedReadResult<Object> subEntryResult = subEntryReaderChain.read(reader, subEntry, context);
entry.set(compoundValue, key, subEntryValue); issues.addAll(Arrays.asList(subEntryResult.issues()));
if (subEntryResult.isFailed() || subEntryResult.isError()) {
return TweedReadResult.failed(issues.toArray(new TweedReadIssue[0]));
} else if (subEntryResult.hasValue()) {
entry.set(compoundValue, key, subEntryResult.value());
}
} else { } else {
throw new TweedEntryReadException( issues.add(TweedReadIssue.error(
"Unexpected token " + token + ": Expected map key or map end", "Unexpected token " + token + ": Expected map key or map end",
context context
); ));
return TweedReadResult.failed(issues.toArray(new TweedReadIssue[0]));
} }
} catch (TweedDataReadException e) { } catch (TweedDataReadException e) {
throw new TweedEntryReadException("Failed reading compound entry", e, context); throw new TweedEntryReadException("Failed reading compound entry", e, context);
} }
} }
return compoundValue; if (issues.isEmpty()) {
return TweedReadResult.ok(compoundValue);
} else {
return TweedReadResult.withIssues(compoundValue, issues.toArray(new TweedReadIssue[0]));
}
}, context);
} }
@Override @Override
@@ -291,12 +293,11 @@ 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 TweedReadResult<@Nullable Object> read(TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext context) {
throws TweedEntryReadException {
try { try {
TweedDataToken token = reader.readToken(); TweedDataToken token = reader.readToken();
if (!token.isListStart() && !token.isMapStart()) { if (!token.isListStart() && !token.isMapStart()) {
return null; return TweedReadResult.empty();
} }
ArrayDeque<Context> stack = new ArrayDeque<>(20); ArrayDeque<Context> stack = new ArrayDeque<>(20);
@@ -316,11 +317,11 @@ public class TweedEntryReaderWriterImpls {
stack.pop(); stack.pop();
} }
if (stack.isEmpty()) { if (stack.isEmpty()) {
return null; return TweedReadResult.empty();
} }
} }
} catch (TweedDataReadException e) { } catch (TweedDataReadException e) {
throw new TweedEntryReadException("Failed skipping through value", e, context); return TweedReadResult.failed(TweedReadIssue.error(e, context));
} }
} }
@@ -352,14 +353,23 @@ public class TweedEntryReaderWriterImpls {
} }
} }
private static void assertIsToken( private static TweedReadResult<TweedDataToken> requireToken(
TweedDataToken token, TweedDataReader reader,
Predicate<TweedDataToken> isToken, Predicate<TweedDataToken> isToken,
String description, String description,
TweedReadContext context TweedReadContext context
) throws TweedEntryReadException { ) {
if (!isToken.test(token)) { try {
throw new TweedEntryReadException("Unexpected token " + token + ": " + description, context); TweedDataToken token = reader.readToken();
if (isToken.test(token)) {
return TweedReadResult.ok(token);
}
return TweedReadResult.failed(TweedReadIssue.error(
"Unexpected token " + token + ": " + description,
context
));
} catch (TweedDataReadException e) {
return TweedReadResult.failed(TweedReadIssue.error(e, context));
} }
} }
} }

View File

@@ -28,7 +28,6 @@ import static de.siphalor.tweed5.testutils.generic.MapTestUtils.sequencedMap;
import static java.util.Map.entry; import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ReadWriteExtensionImplTest { class ReadWriteExtensionImplTest {
private final StringWriter stringWriter = new StringWriter(); private final StringWriter stringWriter = new StringWriter();
@@ -97,7 +96,7 @@ class ReadWriteExtensionImplTest {
@Test @Test
void read() { void read() {
ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow(); ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow();
Map<String, Object> result = assertDoesNotThrow(() -> readWriteExtension.read( var result = assertDoesNotThrow(() -> readWriteExtension.read(
new HjsonReader(new HjsonLexer(new StringReader(""" new HjsonReader(new HjsonLexer(new StringReader("""
{ {
\tint: 123 \tint: 123
@@ -112,9 +111,12 @@ class ReadWriteExtensionImplTest {
readWriteExtension.createReadWriteContextExtensionsData() readWriteExtension.createReadWriteContextExtensionsData()
)); ));
assertEquals(2, result.size()); assertThat(result.isFailed()).isFalse();
assertEquals(123, result.get("int")); assertThat(result.hasValue()).isTrue();
assertEquals(Arrays.asList(true, false, true), result.get("list")); assertThat(result.value()).isEqualTo(Map.of(
"int", 123,
"list", Arrays.asList(true, false, true)
));
} }
private TweedDataVisitor setupWriter(Function<Writer, TweedDataVisitor> writerFactory) { private TweedDataVisitor setupWriter(Function<Writer, TweedDataVisitor> writerFactory) {

View File

@@ -7,6 +7,7 @@ import de.siphalor.tweed5.serde.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.serde.extension.api.TweedEntryWriter; import de.siphalor.tweed5.serde.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.serde.extension.api.TweedReaderWriterProvider; import de.siphalor.tweed5.serde.extension.api.TweedReaderWriterProvider;
import de.siphalor.tweed5.serde.extension.api.TweedWriteContext; import de.siphalor.tweed5.serde.extension.api.TweedWriteContext;
import de.siphalor.tweed5.serde.extension.api.read.result.TweedReadResult;
import de.siphalor.tweed5.serde.hjson.HjsonLexer; import de.siphalor.tweed5.serde.hjson.HjsonLexer;
import de.siphalor.tweed5.serde.hjson.HjsonReader; import de.siphalor.tweed5.serde.hjson.HjsonReader;
import de.siphalor.tweed5.serde.hjson.HjsonWriter; import de.siphalor.tweed5.serde.hjson.HjsonWriter;
@@ -55,7 +56,7 @@ class ReadWritePojoWeavingProcessorTest {
}""" }"""
))); )));
assertThat(configContainer.rootEntry().call(read(reader))) assertThat(configContainer.rootEntry().call(read(reader)))
.isEqualTo(new AnnotatedConfig(987, "abdef", new TestClass(29))); .isEqualTo(TweedReadResult.ok(new AnnotatedConfig(987, "abdef", new TestClass(29))));
} }
@AutoService(TweedReaderWriterProvider.class) @AutoService(TweedReaderWriterProvider.class)