[patchwork, core, extensions] Hugely simplify Patchworks

This commit is contained in:
2025-06-13 22:04:16 +02:00
parent 694f993b8c
commit 6e5c9a23c2
72 changed files with 987 additions and 1977 deletions

View File

@@ -5,8 +5,20 @@ import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.defaultextensions.comment.impl.CommentExtensionImpl;
import org.jspecify.annotations.Nullable;
import java.util.function.Consumer;
public interface CommentExtension extends TweedExtension {
Class<? extends CommentExtension> DEFAULT = CommentExtensionImpl.class;
static <C extends ConfigEntry<?>> Consumer<C> baseComment(String baseComment) {
return entry -> {
CommentExtension extension = entry.container().extension(CommentExtension.class)
.orElseThrow(() -> new IllegalStateException("No comment extension registered"));
extension.setBaseComment(entry, baseComment);
};
}
void setBaseComment(ConfigEntry<?> configEntry, String baseComment);
@Nullable String getFullComment(ConfigEntry<?> configEntry);
}

View File

@@ -1,5 +0,0 @@
package de.siphalor.tweed5.defaultextensions.comment.api;
public interface EntryComment {
String comment();
}

View File

@@ -2,32 +2,32 @@ package de.siphalor.tweed5.defaultextensions.comment.impl;
import com.google.auto.service.AutoService;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.container.ConfigContainerSetupPhase;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.*;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import lombok.Data;
import lombok.Getter;
import lombok.Value;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
@AutoService(CommentExtension.class)
public class CommentExtensionImpl implements ReadWriteRelatedExtension, CommentExtension {
private final ConfigContainer<?> configContainer;
@Getter
private final RegisteredExtensionData<EntryExtensionsData, InternalCommentEntryData> internalEntryDataExtension;
private final PatchworkPartAccess<CustomEntryData> customEntryDataAccess;
private final DefaultMiddlewareContainer<CommentProducer> middlewareContainer;
public CommentExtensionImpl(ConfigContainer<?> configContainer, TweedExtensionSetupContext context) {
this.configContainer = configContainer;
this.internalEntryDataExtension = context.registerEntryExtensionData(InternalCommentEntryData.class);
context.registerEntryExtensionData(EntryComment.class);
this.customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class);
this.middlewareContainer = new DefaultMiddlewareContainer<>();
}
@@ -48,31 +48,49 @@ public class CommentExtensionImpl implements ReadWriteRelatedExtension, CommentE
@Override
public @Nullable Middleware<TweedEntryWriter<?, ?>> entryWriterMiddleware() {
return TweedEntryWriterCommentMiddleware.INSTANCE;
return new TweedEntryWriterCommentMiddleware(this);
}
@Override
public void setBaseComment(ConfigEntry<?> configEntry, String baseComment) {
if (configEntry.container() != configContainer) {
throw new IllegalArgumentException("config entry doesn't belong to config container of this extension");
} else if (configContainer.setupPhase().compareTo(ConfigContainerSetupPhase.INITIALIZED) >= 0) {
throw new IllegalStateException("config container must not be initialized");
}
getOrCreateCustomEntryData(configEntry).baseComment(baseComment);
}
@Override
public void initEntry(ConfigEntry<?> configEntry) {
EntryExtensionsData entryExtensionsData = configEntry.extensionsData();
String baseComment;
if (entryExtensionsData.isPatchworkPartSet(EntryComment.class)) {
baseComment = ((EntryComment) entryExtensionsData).comment();
} else {
baseComment = "";
}
CustomEntryData entryData = getOrCreateCustomEntryData(configEntry);
entryData.commentProducer(middlewareContainer.process(entry -> entryData.baseComment()));
}
CommentProducer middleware = middlewareContainer.process(entry -> baseComment);
internalEntryDataExtension.set(entryExtensionsData, new InternalCommentEntryDataImpl(middleware));
private CustomEntryData getOrCreateCustomEntryData(ConfigEntry<?> entry) {
CustomEntryData customEntryData = entry.extensionsData().get(customEntryDataAccess);
if (customEntryData == null) {
customEntryData = new CustomEntryData();
entry.extensionsData().set(customEntryDataAccess, customEntryData);
}
return customEntryData;
}
@Override
public @Nullable String getFullComment(@NonNull ConfigEntry<?> configEntry) {
String comment = ((InternalCommentEntryData) configEntry.extensionsData()).commentProducer().createComment(configEntry);
public @Nullable String getFullComment(ConfigEntry<?> configEntry) {
CustomEntryData customEntryData = configEntry.extensionsData().get(customEntryDataAccess);
if (customEntryData == null) {
return null;
}
String comment = customEntryData.commentProducer().createComment(configEntry);
return comment.isEmpty() ? null : comment;
}
@Value
private static class InternalCommentEntryDataImpl implements InternalCommentEntryData {
CommentProducer commentProducer;
@Data
private static class CustomEntryData {
private String baseComment = "";
private CommentProducer commentProducer;
}
}

View File

@@ -1,7 +0,0 @@
package de.siphalor.tweed5.defaultextensions.comment.impl;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer;
public interface InternalCommentEntryData {
CommentProducer commentProducer();
}

View File

@@ -5,12 +5,14 @@ import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
@RequiredArgsConstructor
class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?, ?>> {
public static final TweedEntryWriterCommentMiddleware INSTANCE = new TweedEntryWriterCommentMiddleware();
private final CommentExtension commentExtension;
@Override
public String id() {
@@ -49,7 +51,7 @@ class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?
}
@RequiredArgsConstructor
private static class CompoundDataVisitor implements TweedDataVisitor {
private class CompoundDataVisitor implements TweedDataVisitor {
private final TweedDataVisitor delegate;
private final CompoundConfigEntry<?> compoundConfigEntry;
@@ -134,11 +136,7 @@ class TweedEntryWriterCommentMiddleware implements Middleware<TweedEntryWriter<?
}
}
private static @Nullable String getEntryComment(ConfigEntry<?> entry) {
if (!entry.extensionsData().isPatchworkPartSet(InternalCommentEntryData.class)) {
return null;
}
String comment = ((InternalCommentEntryData) entry.extensionsData()).commentProducer().createComment(entry).trim();
return comment.isEmpty() ? null : comment;
private @Nullable String getEntryComment(ConfigEntry<?> entry) {
return commentExtension.getFullComment(entry);
}
}

View File

@@ -5,7 +5,7 @@ import org.jspecify.annotations.Nullable;
import java.util.ArrayDeque;
import java.util.Deque;
public class PathTracking implements PatherData {
public class PathTracking {
private final StringBuilder pathBuilder = new StringBuilder(256);
private final Deque<Context> contextStack = new ArrayDeque<>(50);
private final Deque<String> pathParts = new ArrayDeque<>(50);
@@ -52,8 +52,7 @@ public class PathTracking implements PatherData {
return index;
}
@Override
public String valuePath() {
public String currentPath() {
return pathBuilder.toString();
}

View File

@@ -3,6 +3,7 @@ package de.siphalor.tweed5.defaultextensions.pather.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.Nullable;
@RequiredArgsConstructor
public class PathTrackingConfigEntryValueVisitor implements ConfigEntryValueVisitor {
@@ -10,7 +11,7 @@ public class PathTrackingConfigEntryValueVisitor implements ConfigEntryValueVisi
private final PathTracking pathTracking;
@Override
public <T> void visitEntry(ConfigEntry<T> entry, T value) {
public <T extends @Nullable Object> void visitEntry(ConfigEntry<T> entry, T value) {
delegate.visitEntry(entry, value);
entryVisited();
}

View File

@@ -1,9 +0,0 @@
package de.siphalor.tweed5.defaultextensions.pather.api;
/**
* Extension data for {@link de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData}
* that provides the path to the value currently being read/written.
*/
public interface PatherData {
String valuePath();
}

View File

@@ -1,8 +1,13 @@
package de.siphalor.tweed5.defaultextensions.pather.api;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.data.extension.api.TweedReadContext;
import de.siphalor.tweed5.data.extension.api.TweedWriteContext;
import de.siphalor.tweed5.defaultextensions.pather.impl.PatherExtensionImpl;
public interface PatherExtension extends TweedExtension {
Class<? extends PatherExtension> DEFAULT = PatherExtensionImpl.class;
String getPath(TweedReadContext context);
String getPath(TweedWriteContext context);
}

View File

@@ -2,29 +2,33 @@ package de.siphalor.tweed5.defaultextensions.pather.impl;
import com.google.auto.service.AutoService;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.data.extension.api.*;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData;
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.data.extension.api.TweedReadContext;
import de.siphalor.tweed5.data.extension.api.TweedWriteContext;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
import de.siphalor.tweed5.defaultextensions.pather.api.*;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTracking;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingDataReader;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingDataVisitor;
import de.siphalor.tweed5.defaultextensions.pather.api.PatherExtension;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import lombok.val;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;
@AutoService(PatherExtension.class)
@NullUnmarked
public class PatherExtensionImpl implements PatherExtension, TweedExtension, ReadWriteRelatedExtension {
private static final String PATHER_ID = "pather";
private RegisteredExtensionData<ReadWriteContextExtensionsData, PatherData> rwContextPathTrackingData;
private Middleware<TweedEntryReader<?, ?>> entryReaderMiddleware;
private Middleware<TweedEntryWriter<?, ?>> entryWriterMiddleware;
private final Middleware<TweedEntryReader<?, ?>> entryReaderMiddleware = createEntryReaderMiddleware();
private final Middleware<TweedEntryWriter<?, ?>> entryWriterMiddleware = createEntryWriterMiddleware();
private @Nullable PatchworkPartAccess<PathTracking> rwContextPathTrackingAccess;
@Override
public String getId() {
@@ -33,13 +37,32 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea
@Override
public void setupReadWriteExtension(ReadWriteExtensionSetupContext context) {
rwContextPathTrackingData = context.registerReadWriteContextExtensionData(PatherData.class);
entryReaderMiddleware = createEntryReaderMiddleware();
entryWriterMiddleware = createEntryWriterMiddleware();
rwContextPathTrackingAccess = context.registerReadWriteContextExtensionData(PathTracking.class);
}
private @NonNull Middleware<TweedEntryReader<?, ?>> createEntryReaderMiddleware() {
@Override
public String getPath(TweedReadContext context) {
assert rwContextPathTrackingAccess != null;
PathTracking pathTracking = context.extensionsData().get(rwContextPathTrackingAccess);
if (pathTracking == null) {
throw new IllegalStateException("Path tracking is not active!");
}
return pathTracking.currentPath();
}
@Override
public String getPath(TweedWriteContext context) {
assert rwContextPathTrackingAccess != null;
PathTracking pathTracking = context.extensionsData().get(rwContextPathTrackingAccess);
if (pathTracking == null) {
throw new IllegalStateException("Path tracking is not active!");
}
return pathTracking.currentPath();
}
private Middleware<TweedEntryReader<?, ?>> createEntryReaderMiddleware() {
return new Middleware<TweedEntryReader<?, ?>>() {
@Override
public String id() {
@@ -48,16 +71,19 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea
@Override
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner) {
assert rwContextPathTrackingAccess != null;
//noinspection unchecked
val castedInner = (TweedEntryReader<Object, @NonNull ConfigEntry<Object>>) inner;
return (TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext context) -> {
if (context.extensionsData().isPatchworkPartSet(PatherData.class)) {
PathTracking pathTracking = context.extensionsData().get(rwContextPathTrackingAccess);
if (pathTracking != null) {
return castedInner.read(reader, entry, context);
}
PathTracking pathTracking = new PathTracking();
rwContextPathTrackingData.set(context.extensionsData(), pathTracking);
pathTracking = new PathTracking();
context.extensionsData().set(rwContextPathTrackingAccess, pathTracking);
return castedInner.read(new PathTrackingDataReader(reader, pathTracking), entry, context);
};
}
@@ -73,17 +99,20 @@ public class PatherExtensionImpl implements PatherExtension, TweedExtension, Rea
@Override
public TweedEntryWriter<?, ?> process(TweedEntryWriter<?, ?> inner) {
assert rwContextPathTrackingAccess != null;
//noinspection unchecked
val castedInner = (TweedEntryWriter<Object, @NonNull ConfigEntry<Object>>) inner;
return (TweedDataVisitor writer, Object value, ConfigEntry<Object> entry, TweedWriteContext context) -> {
if (context.extensionsData().isPatchworkPartSet(PatherData.class)) {
PathTracking pathTracking = context.extensionsData().get(rwContextPathTrackingAccess);
if (pathTracking != null) {
castedInner.write(writer, value, entry, context);
return;
}
PathTracking pathTracking = new PathTracking();
rwContextPathTrackingData.set(context.extensionsData(), pathTracking);
pathTracking = new PathTracking();
context.extensionsData().set(rwContextPathTrackingAccess, pathTracking);
castedInner.write(new PathTrackingDataVisitor(writer, pathTracking), value, entry, context);
};
}

View File

@@ -1,9 +0,0 @@
package de.siphalor.tweed5.defaultextensions.validation.api;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import java.util.Collection;
public interface EntrySpecificValidation {
Collection<Middleware<ConfigEntryValidator>> validators();
}

View File

@@ -2,12 +2,54 @@ package de.siphalor.tweed5.defaultextensions.validation.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssue;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssues;
import de.siphalor.tweed5.defaultextensions.validation.api.validators.SimpleValidatorMiddleware;
import de.siphalor.tweed5.defaultextensions.validation.impl.ValidationExtensionImpl;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.Nullable;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
public interface ValidationExtension extends TweedExtension {
Class<? extends ValidationExtension> DEFAULT = ValidationExtensionImpl.class;
static <C extends ConfigEntry<T>, T> Consumer<C> validators(ConfigEntryValidator... validators) {
return entry -> {
ValidationExtension extension = entry.container().extension(ValidationExtension.class)
.orElseThrow(() -> new IllegalStateException("No validation extension registered"));
extension.addValidators(entry, validators);
};
}
static <C extends ConfigEntry<T>, T> Function<C, ValidationIssues> validate(T value) {
return entry -> {
ValidationExtension extension = entry.container().extension(ValidationExtension.class)
.orElseThrow(() -> new IllegalStateException("No validation extension registered"));
return extension.validate(entry, value);
};
}
default <T> void addValidators(ConfigEntry<T> entry, ConfigEntryValidator... validators) {
String lastId = null;
for (ConfigEntryValidator validator : validators) {
String id = UUID.randomUUID().toString();
Set<String> mustComeAfter = lastId == null ? Collections.emptySet() : Collections.singleton(lastId);
addValidatorMiddleware(entry, new SimpleValidatorMiddleware(id, validator) {
@Override
public Set<String> mustComeAfter() {
return mustComeAfter;
}
});
lastId = id;
}
}
<T> void addValidatorMiddleware(ConfigEntry<T> entry, Middleware<ConfigEntryValidator> validator);
<T extends @Nullable Object> ValidationIssues validate(ConfigEntry<T> entry, T value);
}

View File

@@ -3,6 +3,7 @@ package de.siphalor.tweed5.defaultextensions.validation.api.result;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
@@ -11,16 +12,16 @@ import java.util.function.Function;
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class ValidationResult<T> {
public class ValidationResult<T extends @Nullable Object> {
private final T value;
private final Collection<ValidationIssue> issues;
private final boolean hasError;
public static <T> ValidationResult<T> ok(T value) {
public static <T extends @Nullable Object> ValidationResult<T> ok(T value) {
return new ValidationResult<>(value, Collections.emptyList(), false);
}
public static <T> ValidationResult<T> withIssues(T value, Collection<ValidationIssue> issues) {
public static <T extends @Nullable Object> ValidationResult<T> withIssues(T value, Collection<ValidationIssue> issues) {
return new ValidationResult<>(value, issues, issuesContainError(issues));
}

View File

@@ -1,7 +0,0 @@
package de.siphalor.tweed5.defaultextensions.validation.impl;
import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator;
public interface InternalValidationEntryData {
ConfigEntryValidator completeEntryValidator();
}

View File

@@ -4,8 +4,6 @@ import com.google.auto.service.AutoService;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext;
import de.siphalor.tweed5.core.api.middleware.DefaultMiddlewareContainer;
@@ -14,7 +12,6 @@ import de.siphalor.tweed5.core.api.middleware.MiddlewareContainer;
import de.siphalor.tweed5.data.extension.api.TweedEntryReadException;
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
import de.siphalor.tweed5.data.extension.api.TweedReadContext;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteContextExtensionsData;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteExtensionSetupContext;
import de.siphalor.tweed5.data.extension.api.extension.ReadWriteRelatedExtension;
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
@@ -22,20 +19,16 @@ import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtensio
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTracking;
import de.siphalor.tweed5.defaultextensions.pather.api.PathTrackingConfigEntryValueVisitor;
import de.siphalor.tweed5.defaultextensions.pather.api.PatherData;
import de.siphalor.tweed5.defaultextensions.pather.api.PatherExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator;
import de.siphalor.tweed5.defaultextensions.validation.api.EntrySpecificValidation;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationProvidingExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssue;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssueLevel;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssues;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import org.jetbrains.annotations.NotNull;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import lombok.*;
import org.jspecify.annotations.Nullable;
import java.util.*;
@@ -48,7 +41,7 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
);
private static final ConfigEntryValidator PRIMITIVE_VALIDATOR = new ConfigEntryValidator() {
@Override
public <T> ValidationResult<T> validate(@NotNull ConfigEntry<T> configEntry, @Nullable T value) {
public <T> ValidationResult<T> validate(ConfigEntry<T> configEntry, @Nullable T value) {
if (value == null) {
//noinspection unchecked
return (ValidationResult<T>) PRIMITIVE_IS_NULL_RESULT;
@@ -57,33 +50,32 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
}
@Override
public <T> String description(@NotNull ConfigEntry<T> configEntry) {
public <T> String description(ConfigEntry<T> configEntry) {
return "Value must not be null.";
}
};
private static final ConfigEntryValidator NOOP_VALIDATOR = new ConfigEntryValidator() {
@Override
public <T> ValidationResult<T> validate(@NotNull ConfigEntry<T> configEntry, @Nullable T value) {
public <T> ValidationResult<@Nullable T> validate(ConfigEntry<T> configEntry, @Nullable T value) {
return ValidationResult.ok(value);
}
@Override
public <T> String description(@NotNull ConfigEntry<T> configEntry) {
public <T> String description(ConfigEntry<T> configEntry) {
return "";
}
};
private final ConfigContainer<?> configContainer;
private final RegisteredExtensionData<EntryExtensionsData, InternalValidationEntryData> validationEntryDataExtension;
private final PatchworkPartAccess<CustomEntryData> customEntryDataAccess;
private final MiddlewareContainer<ConfigEntryValidator> entryValidatorMiddlewareContainer
= new DefaultMiddlewareContainer<>();
private @Nullable RegisteredExtensionData<ReadWriteContextExtensionsData, ValidationIssues>
readContextValidationIssuesExtensionData;
private @Nullable PatchworkPartAccess<ValidationIssues> readContextValidationIssuesAccess;
private @Nullable PatherExtension patherExtension;
public ValidationExtensionImpl(ConfigContainer<?> configContainer, TweedExtensionSetupContext context) {
this.configContainer = configContainer;
this.validationEntryDataExtension = context.registerEntryExtensionData(InternalValidationEntryData.class);
context.registerEntryExtensionData(EntrySpecificValidation.class);
this.customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class);
context.registerExtension(PatherExtension.class);
}
@@ -102,6 +94,9 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
}
}
entryValidatorMiddlewareContainer.seal();
patherExtension = configContainer.extension(PatherExtension.class)
.orElseThrow(() -> new IllegalStateException("Missing requested PatherExtension"));
}
@Override
@@ -116,16 +111,21 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
public CommentProducer process(CommentProducer inner) {
return entry -> {
String baseComment = inner.createComment(entry);
if (entry.extensionsData().isPatchworkPartSet(InternalValidationEntryData.class)) {
String validationDescription = ((InternalValidationEntryData) entry.extensionsData())
.completeEntryValidator()
.description(entry)
.trim();
if (!validationDescription.isEmpty()) {
baseComment += "\n\n" + validationDescription;
}
CustomEntryData entryData = entry.extensionsData().get(customEntryDataAccess);
if (entryData == null || entryData.completeValidator() == null) {
return baseComment;
}
String validationDescription = entryData.completeValidator()
.description(entry)
.trim();
if (validationDescription.isEmpty()) {
return baseComment;
}
if (baseComment.isEmpty()) {
return validationDescription;
} else {
return "\n\n" + validationDescription;
}
return baseComment;
};
}
};
@@ -133,7 +133,13 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
@Override
public void setupReadWriteExtension(ReadWriteExtensionSetupContext context) {
readContextValidationIssuesExtensionData = context.registerReadWriteContextExtensionData(ValidationIssues.class);
readContextValidationIssuesAccess = context.registerReadWriteContextExtensionData(ValidationIssues.class);
}
@Override
public <T> void addValidatorMiddleware(ConfigEntry<T> entry, Middleware<ConfigEntryValidator> validator) {
CustomEntryData entryData = getOrCreateCustomEntryData(entry);
entryData.addValidator(validator);
}
@Override
@@ -145,26 +151,26 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
baseValidator = NOOP_VALIDATOR;
}
ConfigEntryValidator entryValidator;
Collection<Middleware<ConfigEntryValidator>> entrySpecificValidators = getEntrySpecificValidators(configEntry);
if (entrySpecificValidators.isEmpty()) {
entryValidator = entryValidatorMiddlewareContainer.process(baseValidator);
CustomEntryData entryData = getOrCreateCustomEntryData(configEntry);
if (entryData.validators().isEmpty()) {
entryData.completeValidator(entryValidatorMiddlewareContainer.process(baseValidator));
} else {
DefaultMiddlewareContainer<ConfigEntryValidator> entrySpecificValidatorContainer = new DefaultMiddlewareContainer<>();
entrySpecificValidatorContainer.registerAll(entryValidatorMiddlewareContainer.middlewares());
entrySpecificValidatorContainer.registerAll(entrySpecificValidators);
entrySpecificValidatorContainer.registerAll(entryData.validators());
entrySpecificValidatorContainer.seal();
entryValidator = entrySpecificValidatorContainer.process(baseValidator);
entryData.completeValidator(entrySpecificValidatorContainer.process(baseValidator));
}
validationEntryDataExtension.set(configEntry.extensionsData(), new InternalValidationEntryDataImpl(entryValidator));
}
private Collection<Middleware<ConfigEntryValidator>> getEntrySpecificValidators(ConfigEntry<?> configEntry) {
if (!configEntry.extensionsData().isPatchworkPartSet(EntrySpecificValidation.class)) {
return Collections.emptyList();
private CustomEntryData getOrCreateCustomEntryData(ConfigEntry<?> entry) {
CustomEntryData entryData = entry.extensionsData().get(customEntryDataAccess);
if (entryData == null) {
entryData = new CustomEntryData();
entry.extensionsData().set(customEntryDataAccess, entryData);
}
return ((EntrySpecificValidation) configEntry.extensionsData()).validators();
return entryData;
}
@Override
@@ -173,7 +179,7 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
}
@Override
public <T> ValidationIssues validate(@NotNull ConfigEntry<T> entry, @Nullable T value) {
public <T> ValidationIssues validate(ConfigEntry<T> entry, @Nullable T value) {
PathTracking pathTracking = new PathTracking();
ValidatingConfigEntryVisitor validatingVisitor = new ValidatingConfigEntryVisitor(pathTracking);
@@ -182,9 +188,22 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
return validatingVisitor.validationIssues();
}
@Value
private static class InternalValidationEntryDataImpl implements InternalValidationEntryData {
ConfigEntryValidator completeEntryValidator;
@Data
private static class CustomEntryData {
@Setter(AccessLevel.NONE)
private @Nullable List<Middleware<ConfigEntryValidator>> validators;
private @Nullable ConfigEntryValidator completeValidator;
public List<Middleware<ConfigEntryValidator>> validators() {
return validators == null ? Collections.emptyList() : validators;
}
public void addValidator(Middleware<ConfigEntryValidator> validator) {
if (validators == null) {
validators = new ArrayList<>();
}
validators.add(validator);
}
}
private class EntryValidationReaderMiddleware implements Middleware<TweedEntryReader<?, ?>> {
@@ -200,23 +219,29 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
@Override
public TweedEntryReader<?, ?> process(TweedEntryReader<?, ?> inner) {
assert readContextValidationIssuesAccess != null && patherExtension != null;
//noinspection unchecked
TweedEntryReader<Object, ConfigEntry<Object>> castedInner = (TweedEntryReader<Object, ConfigEntry<Object>>) inner;
return (TweedDataReader reader, ConfigEntry<Object> entry, TweedReadContext context) -> {
ValidationIssues validationIssues;
if (!context.extensionsData().isPatchworkPartSet(ValidationIssues.class)) {
ValidationIssues validationIssues = context.extensionsData().get(readContextValidationIssuesAccess);
if (validationIssues == null) {
validationIssues = new ValidationIssuesImpl();
readContextValidationIssuesExtensionData.set(context.extensionsData(), validationIssues);
} else {
validationIssues = (ValidationIssues) context.extensionsData();
}
Object value = castedInner.read(reader, entry, context);
ValidationResult<Object> validationResult = ((InternalValidationEntryData) entry.extensionsData()).completeEntryValidator().validate(entry, value);
ConfigEntryValidator entryValidator = entry.extensionsData()
.get(customEntryDataAccess)
.completeValidator();
assert entryValidator != null;
if (!validationResult.issues().isEmpty() && context.extensionsData().isPatchworkPartSet(PatherData.class)) {
String path = ((PatherData) context.extensionsData()).valuePath();
ValidationResult<Object> validationResult = entryValidator.validate(entry, value);
if (!validationResult.issues().isEmpty()) {
String path = patherExtension.getPath(context);
validationIssues.issuesByPath().put(path, new ValidationIssues.EntryIssues(
entry,
validationResult.issues()
@@ -234,15 +259,19 @@ public class ValidationExtensionImpl implements ReadWriteRelatedExtension, Valid
@Getter
@RequiredArgsConstructor
private static class ValidatingConfigEntryVisitor implements ConfigEntryValueVisitor {
private class ValidatingConfigEntryVisitor implements ConfigEntryValueVisitor {
private final PathTracking pathTracking;
private final ValidationIssues validationIssues = new ValidationIssuesImpl();
@Override
public <T> void visitEntry(ConfigEntry<T> entry, T value) {
ValidationResult<T> result = ((InternalValidationEntryData) entry.extensionsData()).completeEntryValidator().validate(entry, value);
CustomEntryData entryData = entry.extensionsData().get(customEntryDataAccess);
assert entryData != null;
ConfigEntryValidator entryValidator = entryData.completeValidator();
assert entryValidator != null;
ValidationResult<T> result = entryValidator.validate(entry, value);
if (!result.issues().isEmpty()) {
validationIssues.issuesByPath().put(pathTracking.valuePath(), new ValidationIssues.EntryIssues(entry, result.issues()));
validationIssues.issuesByPath().put(pathTracking.currentPath(), new ValidationIssues.EntryIssues(entry, result.issues()));
}
}

View File

@@ -1,8 +1,21 @@
package de.siphalor.tweed5.defaultextensions.validationfallback.api;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.defaultextensions.validationfallback.impl.ValidationFallbackExtensionImpl;
import java.util.function.Consumer;
public interface ValidationFallbackExtension extends TweedExtension {
Class<? extends ValidationFallbackExtension> DEFAULT = ValidationFallbackExtensionImpl.class;
static <C extends ConfigEntry<T>, T> Consumer<C> validationFallbackValue(T value) {
return entry -> {
ValidationFallbackExtension extension = entry.container().extension(ValidationFallbackExtension.class)
.orElseThrow(() -> new IllegalStateException("ValidationFallbackExtension is not registered"));
extension.setFallbackValue(entry, value);
};
}
<T> void setFallbackValue(ConfigEntry<T> entry, T value);
}

View File

@@ -1,7 +0,0 @@
package de.siphalor.tweed5.defaultextensions.validationfallback.api;
import org.jspecify.annotations.Nullable;
public interface ValidationFallbackValue {
@Nullable Object validationFallbackValue();
}

View File

@@ -10,7 +10,8 @@ import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssu
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssueLevel;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult;
import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackExtension;
import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackValue;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import lombok.Data;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
@@ -20,8 +21,11 @@ import java.util.stream.Collectors;
@AutoService(ValidationFallbackExtension.class)
public class ValidationFallbackExtensionImpl implements ValidationFallbackExtension, ValidationProvidingExtension {
private final PatchworkPartAccess<CustomEntryData> customEntryDataAccess;
public ValidationFallbackExtensionImpl(TweedExtensionSetupContext context) {
context.registerEntryExtensionData(ValidationFallbackValue.class);
customEntryDataAccess = context.registerEntryExtensionData(CustomEntryData.class);
}
@Override
@@ -29,12 +33,31 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens
return "validation-fallback";
}
@Override
public <T> void setFallbackValue(ConfigEntry<T> entry, T value) {
getOrCreateCustomEntryData(entry).fallbackValue(value);
}
private CustomEntryData getOrCreateCustomEntryData(ConfigEntry<?> entry) {
CustomEntryData customEntryData = entry.extensionsData().get(customEntryDataAccess);
if (customEntryData == null) {
customEntryData = new CustomEntryData();
entry.extensionsData().set(customEntryDataAccess, customEntryData);
}
return customEntryData;
}
@Override
public Middleware<ConfigEntryValidator> validationMiddleware() {
return new ValidationFallbackMiddleware();
}
private static class ValidationFallbackMiddleware implements Middleware<ConfigEntryValidator> {
@Data
private static class CustomEntryData {
@Nullable Object fallbackValue;
}
private class ValidationFallbackMiddleware implements Middleware<ConfigEntryValidator> {
@Override
public String id() {
return "validation-fallback";
@@ -59,13 +82,14 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens
if (!result.hasError()) {
return result;
}
if (!configEntry.extensionsData().isPatchworkPartSet(ValidationFallbackValue.class)) {
CustomEntryData entryData = configEntry.extensionsData().get(customEntryDataAccess);
if (entryData == null) {
return result;
}
Object fallbackValue = ((ValidationFallbackValue) configEntry.extensionsData()).validationFallbackValue();
Object fallbackValue = entryData.fallbackValue();
if (fallbackValue != null) {
if (fallbackValue.getClass() == configEntry.valueClass()) {
if (configEntry.valueClass().isInstance(fallbackValue)) {
//noinspection unchecked
fallbackValue = configEntry.deepCopy((T) fallbackValue);
} else {
@@ -90,12 +114,11 @@ public class ValidationFallbackExtensionImpl implements ValidationFallbackExtens
@Override
public <T> String description(ConfigEntry<T> configEntry) {
if (!configEntry.extensionsData().isPatchworkPartSet(ValidationFallbackValue.class)) {
CustomEntryData entryData = configEntry.extensionsData().get(customEntryDataAccess);
if (entryData == null) {
return inner.description(configEntry);
}
return inner.description(configEntry) +
"\n\nDefault/Fallback value: " +
((ValidationFallbackValue) configEntry.extensionsData()).validationFallbackValue();
return inner.description(configEntry) + "\n\nDefault/Fallback value: " + entryData.fallbackValue();
}
};
}

View File

@@ -1,27 +1,20 @@
package de.siphalor.tweed5.defaultextensions.comment.impl;
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.core.api.middleware.Middleware;
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.data.extension.api.EntryReaderWriterDefinition;
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriter;
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters;
import de.siphalor.tweed5.data.hjson.HjsonCommentType;
import de.siphalor.tweed5.data.hjson.HjsonWriter;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentModifyingExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentProducer;
import de.siphalor.tweed5.defaultextensions.comment.api.EntryComment;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullUnmarked;
import org.junit.jupiter.api.Test;
@@ -31,6 +24,9 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.entryReaderWriter;
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
import static de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension.baseComment;
import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap;
import static java.util.Map.entry;
import static org.junit.jupiter.api.Assertions.*;
@@ -38,44 +34,44 @@ import static org.junit.jupiter.api.Assertions.*;
@NullUnmarked
class CommentExtensionImplTest {
private DefaultConfigContainer<Map<String, Object>> configContainer;
private StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry;
private SimpleConfigEntryImpl<Integer> intEntry;
private SimpleConfigEntryImpl<String> stringEntry;
private SimpleConfigEntryImpl<Long> noCommentEntry;
private DefaultConfigContainer<@NonNull Map<String, Object>> configContainer;
private CompoundConfigEntry<Map<String, Object>> rootEntry;
private SimpleConfigEntry<Integer> intEntry;
private SimpleConfigEntry<String> stringEntry;
private SimpleConfigEntry<Long> noCommentEntry;
@SafeVarargs
final void setupContainer(Class<? extends TweedExtension>... extraExtensions) {
configContainer = new DefaultConfigContainer<>();
configContainer.registerExtension(CommentExtension.DEFAULT);
configContainer.registerExtension(ReadWriteExtension.DEFAULT);
configContainer.registerExtensions(extraExtensions);
configContainer.finishExtensionSetup();
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class);
stringEntry = new SimpleConfigEntryImpl<>(configContainer, String.class);
noCommentEntry = new SimpleConfigEntryImpl<>(configContainer, Long.class);
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
.apply(entryReaderWriter(intReaderWriter()))
.apply(baseComment("It is an integer"));
stringEntry = new SimpleConfigEntryImpl<>(configContainer, String.class)
.apply(entryReaderWriter(stringReaderWriter()))
.apply(baseComment("It is a string"));
noCommentEntry = new SimpleConfigEntryImpl<>(configContainer, Long.class)
.apply(entryReaderWriter(longReaderWriter()));
//noinspection unchecked
rootEntry = new StaticMapCompoundConfigEntryImpl<>(
configContainer,
((Class<Map<String, Object>>)(Class<?>) Map.class),
((Class<Map<String, Object>>) (Class<?>) Map.class),
LinkedHashMap::new,
sequencedMap(List.of(
entry("int", intEntry),
entry("string", stringEntry),
entry("noComment", noCommentEntry)
))
);
)))
.apply(entryReaderWriter(compoundReaderWriter()))
.apply(baseComment("This is the root value.\nIt is the topmost value in the tree."));
configContainer.attachTree(rootEntry);
//noinspection unchecked
RegisteredExtensionData<EntryExtensionsData, EntryComment> commentData = (RegisteredExtensionData<EntryExtensionsData, EntryComment>) configContainer.entryDataExtensions().get(EntryComment.class);
commentData.set(rootEntry.extensionsData(), new CommentImpl("This is the root value.\nIt is the topmost value in the tree."));
commentData.set(intEntry.extensionsData(), new CommentImpl("It is an integer"));
commentData.set(stringEntry.extensionsData(), new CommentImpl("It is a string"));
}
@Test
@@ -102,8 +98,7 @@ class CommentExtensionImplTest {
@Test
void simpleCommentsInHjson() {
setupContainer(ReadWriteExtension.DEFAULT);
setupReadWriteTypes();
setupContainer();
configContainer.initialize();
Map<String, Object> value = new HashMap<>();
@@ -134,21 +129,6 @@ class CommentExtensionImplTest {
""", output.toString());
}
private void setupReadWriteTypes() {
//noinspection unchecked
var readerWriterData = (RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition>) configContainer.entryDataExtensions().get(EntryReaderWriterDefinition.class);
readerWriterData.set(rootEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.compoundReaderWriter()));
readerWriterData.set(intEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.intReaderWriter()));
readerWriterData.set(stringEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.stringReaderWriter()));
readerWriterData.set(noCommentEntry.extensionsData(), new TrivialEntryReaderWriterDefinition(TweedEntryReaderWriters.longReaderWriter()));
}
@Value
private static class CommentImpl implements EntryComment {
String comment;
}
@NoArgsConstructor
public static class TestCommentModifyingExtension implements TweedExtension, CommentModifyingExtension {
@Override
@@ -158,7 +138,7 @@ class CommentExtensionImplTest {
@Override
public Middleware<CommentProducer> commentMiddleware() {
return new Middleware<CommentProducer>() {
return new Middleware<>() {
@Override
public String id() {
return "test";
@@ -171,19 +151,4 @@ class CommentExtensionImplTest {
};
}
}
@RequiredArgsConstructor
private static class TrivialEntryReaderWriterDefinition implements EntryReaderWriterDefinition {
private final TweedEntryReaderWriter<?, ?> readerWriter;
@Override
public TweedEntryReader<?, ?> reader() {
return readerWriter;
}
@Override
public TweedEntryWriter<?, ?> writer() {
return readerWriter;
}
}
}

View File

@@ -1,37 +1,39 @@
package de.siphalor.tweed5.defaultextensions.validation.impl;
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.core.impl.entry.StaticMapCompoundConfigEntryImpl;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
import de.siphalor.tweed5.defaultextensions.comment.api.EntryComment;
import de.siphalor.tweed5.defaultextensions.validation.api.EntrySpecificValidation;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssue;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssueLevel;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssues;
import de.siphalor.tweed5.defaultextensions.validation.api.validators.NumberRangeValidator;
import de.siphalor.tweed5.defaultextensions.validation.api.validators.SimpleValidatorMiddleware;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import java.util.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension.baseComment;
import static de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension.validators;
import static de.siphalor.tweed5.testutils.MapTestUtils.sequencedMap;
import static java.util.Map.entry;
import static org.junit.jupiter.api.Assertions.*;
class ValidationExtensionImplTest {
private DefaultConfigContainer<Map<String, Object>> configContainer;
private StaticMapCompoundConfigEntryImpl<Map<String, Object>> rootEntry;
private SimpleConfigEntryImpl<Byte> byteEntry;
private SimpleConfigEntryImpl<Integer> intEntry;
private SimpleConfigEntryImpl<Double> doubleEntry;
private CompoundConfigEntry<Map<String, Object>> rootEntry;
private SimpleConfigEntry<Byte> byteEntry;
private SimpleConfigEntry<Integer> intEntry;
private SimpleConfigEntry<Double> doubleEntry;
@BeforeEach
void setUp() {
@@ -41,9 +43,13 @@ class ValidationExtensionImplTest {
configContainer.registerExtension(ValidationExtension.DEFAULT);
configContainer.finishExtensionSetup();
byteEntry = new SimpleConfigEntryImpl<>(configContainer, Byte.class);
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class);
doubleEntry = new SimpleConfigEntryImpl<>(configContainer, Double.class);
byteEntry = new SimpleConfigEntryImpl<>(configContainer, Byte.class)
.apply(validators(new NumberRangeValidator<>(Byte.class, (byte) 10, (byte) 100)));
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
.apply(validators(new NumberRangeValidator<>(Integer.class, null, 123)))
.apply(baseComment("This is the main comment!"));
doubleEntry = new SimpleConfigEntryImpl<>(configContainer, Double.class)
.apply(validators(new NumberRangeValidator<>(Double.class, 0.5, null)));
//noinspection unchecked
rootEntry = new StaticMapCompoundConfigEntryImpl<>(
@@ -59,25 +65,6 @@ class ValidationExtensionImplTest {
configContainer.attachTree(rootEntry);
//noinspection unchecked
RegisteredExtensionData<EntryExtensionsData, EntryComment> commentData = (RegisteredExtensionData<EntryExtensionsData, EntryComment>) configContainer.entryDataExtensions().get(EntryComment.class);
commentData.set(intEntry.extensionsData(), () -> "This is the main comment!");
//noinspection unchecked
RegisteredExtensionData<EntryExtensionsData, EntrySpecificValidation> entrySpecificValidation = (RegisteredExtensionData<EntryExtensionsData, EntrySpecificValidation>) configContainer.entryDataExtensions().get(EntrySpecificValidation.class);
entrySpecificValidation.set(
byteEntry.extensionsData(),
() -> Collections.singleton(new SimpleValidatorMiddleware("range", new NumberRangeValidator<>(Byte.class, (byte) 10, (byte) 100)))
);
entrySpecificValidation.set(
intEntry.extensionsData(),
() -> Collections.singleton(new SimpleValidatorMiddleware("range", new NumberRangeValidator<>(Integer.class, null, 123)))
);
entrySpecificValidation.set(
doubleEntry.extensionsData(),
() -> Collections.singleton(new SimpleValidatorMiddleware("range", new NumberRangeValidator<>(Double.class, 0.5, null)))
);
configContainer.initialize();
}

View File

@@ -1,30 +1,21 @@
package de.siphalor.tweed5.defaultextensions.validationfallback.impl;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.api.extension.EntryExtensionsData;
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.data.extension.api.EntryReaderWriterDefinition;
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters;
import de.siphalor.tweed5.data.hjson.HjsonCommentType;
import de.siphalor.tweed5.data.hjson.HjsonLexer;
import de.siphalor.tweed5.data.hjson.HjsonReader;
import de.siphalor.tweed5.data.hjson.HjsonWriter;
import de.siphalor.tweed5.defaultextensions.comment.api.CommentExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.ConfigEntryValidator;
import de.siphalor.tweed5.defaultextensions.validation.api.EntrySpecificValidation;
import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssue;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssueLevel;
import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult;
import de.siphalor.tweed5.defaultextensions.validation.api.validators.SimpleValidatorMiddleware;
import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackExtension;
import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackValue;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -33,18 +24,19 @@ import org.junit.jupiter.params.provider.ValueSource;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import static de.siphalor.tweed5.data.extension.api.ReadWriteExtension.*;
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
import static de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension.validators;
import static de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackExtension.validationFallbackValue;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ValidationFallbackExtensionImplTest {
private DefaultConfigContainer<Integer> configContainer;
private SimpleConfigEntryImpl<Integer> intEntry;
private SimpleConfigEntry<Integer> intEntry;
@SuppressWarnings("unchecked")
@BeforeEach
void setUp() {
configContainer = new DefaultConfigContainer<>();
@@ -55,92 +47,82 @@ class ValidationFallbackExtensionImplTest {
configContainer.finishExtensionSetup();
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class);
intEntry = new SimpleConfigEntryImpl<>(configContainer, Integer.class)
.apply(entryReaderWriter(nullableReader(intReaderWriter()), nullableWriter(intReaderWriter())))
.apply(validators(
new ConfigEntryValidator() {
@Override
public <T extends @Nullable Object> ValidationResult<T> validate(ConfigEntry<T> configEntry, @Nullable T value) {
if (value == null) {
return ValidationResult.withIssues(
null, Collections.singleton(
new ValidationIssue(
"Value must not be null",
ValidationIssueLevel.ERROR
)
)
);
}
return ValidationResult.ok(value);
}
@Override
public <T> String description(ConfigEntry<T> configEntry) {
return "Must not be null.";
}
},
new ConfigEntryValidator() {
@Override
public <T> ValidationResult<T> validate(ConfigEntry<T> configEntry, @Nullable T value) {
assert value != null;
int intValue = (int) value;
if (intValue < 1) {
return ValidationResult.withIssues(
value,
Collections.singleton(new ValidationIssue(
"Must be greater or equal to 1",
ValidationIssueLevel.ERROR
))
);
}
if (intValue > 6) {
return ValidationResult.withIssues(
value,
Collections.singleton(new ValidationIssue(
"Must be smaller or equal to 6",
ValidationIssueLevel.ERROR
))
);
}
return ValidationResult.ok(value);
}
@Override
public <T> String description(ConfigEntry<T> configEntry) {
return "Must be between 1 and 6";
}
}
))
.apply(validationFallbackValue(3));
configContainer.attachTree(intEntry);
RegisteredExtensionData<EntryExtensionsData, EntrySpecificValidation> entrySpecificValidation = (RegisteredExtensionData<EntryExtensionsData, EntrySpecificValidation>) configContainer.entryDataExtensions().get(EntrySpecificValidation.class);
entrySpecificValidation.set(intEntry.extensionsData(), () -> Arrays.asList(
new SimpleValidatorMiddleware("non-null", new ConfigEntryValidator() {
@Override
public <T> ValidationResult<T> validate(ConfigEntry<T> configEntry, @Nullable T value) {
if (value == null) {
return ValidationResult.withIssues(null, Collections.singleton(
new ValidationIssue("Value must not be null", ValidationIssueLevel.ERROR)
));
}
return ValidationResult.ok(value);
}
@Override
public @NotNull <T> String description(ConfigEntry<T> configEntry) {
return "Must not be null.";
}
}),
new SimpleValidatorMiddleware("range", new ConfigEntryValidator() {
@Override
public <T> ValidationResult<T> validate(ConfigEntry<T> configEntry, @Nullable T value) {
Integer intValue = (Integer) value;
if (intValue < 1) {
return ValidationResult.withIssues(value, Collections.singleton(new ValidationIssue("Must be greater or equal to 1", ValidationIssueLevel.ERROR)));
}
if (intValue > 6) {
return ValidationResult.withIssues(value, Collections.singleton(new ValidationIssue("Must be smaller or equal to 6", ValidationIssueLevel.ERROR)));
}
return ValidationResult.ok(value);
}
@Override
public @NotNull <T> String description(ConfigEntry<T> configEntry) {
return "Must be between 1 and 6";
}
}) {
@Override
public Set<String> mustComeAfter() {
return Collections.singleton("non-null");
}
}
));
RegisteredExtensionData<EntryExtensionsData, ValidationFallbackValue> validationFallbackValue = (RegisteredExtensionData<EntryExtensionsData, ValidationFallbackValue>) configContainer.entryDataExtensions().get(ValidationFallbackValue.class);
validationFallbackValue.set(intEntry.extensionsData(), () -> 3);
RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition> readerWriterData = (RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition>) configContainer.entryDataExtensions().get(EntryReaderWriterDefinition.class);
readerWriterData.set(intEntry.extensionsData(), new EntryReaderWriterDefinition() {
@Override
public TweedEntryReader<?, ?> reader() {
return TweedEntryReaderWriters.nullableReader(TweedEntryReaderWriters.intReaderWriter());
}
@Override
public TweedEntryWriter<?, ?> writer() {
return TweedEntryReaderWriters.nullableWriter(TweedEntryReaderWriters.intReaderWriter());
}
});
configContainer.initialize();
}
@ParameterizedTest
@ValueSource(strings = {"0", "7", "123", "null"})
void fallbackTriggers(String input) {
ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow();
Integer result = assertDoesNotThrow(() -> readWriteExtension.read(
new HjsonReader(new HjsonLexer(new StringReader(input))),
intEntry,
readWriteExtension.createReadWriteContextExtensionsData()
));
Integer result = intEntry.call(read(new HjsonReader(new HjsonLexer(new StringReader(input)))));
assertEquals(3, result);
}
@Test
void description() {
ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class).orElseThrow();
StringWriter stringWriter = new StringWriter();
assertDoesNotThrow(() -> readWriteExtension.write(
intEntry.apply(write(
new HjsonWriter(stringWriter, new HjsonWriter.Options().multilineCommentType(HjsonCommentType.SLASHES)),
5,
intEntry,
readWriteExtension.createReadWriteContextExtensionsData()
5
));
assertEquals(