Rework exchanges, rework stores, add dialog API

This commit is contained in:
Christopher Schnick
2022-06-17 05:24:09 +02:00
parent b8164339d0
commit 9440037f03
45 changed files with 711 additions and 428 deletions
@@ -1,4 +0,0 @@
package io.xpipe.core.connection;
public class Connection {
}
@@ -9,13 +9,21 @@ import lombok.Getter;
public class BaseQueryElement extends DialogElement {
private final String description;
private final boolean newLine;
private final boolean required;
private final boolean hidden;
protected String value;
@JsonCreator
public BaseQueryElement(String description, boolean required, String value) {
public BaseQueryElement(String description, boolean newLine, boolean required, boolean hidden, String value) {
this.description = description;
this.newLine = newLine;
this.required = required;
this.hidden = hidden;
this.value = value;
}
public boolean isNewLine() {
return newLine;
}
}
@@ -0,0 +1,12 @@
package io.xpipe.core.dialog;
import com.fasterxml.jackson.annotation.JsonTypeName;
@JsonTypeName("busy")
public class BusyElement extends DialogElement {
@Override
public boolean apply(String value) {
return true;
}
}
@@ -0,0 +1,15 @@
package io.xpipe.core.dialog;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
@Value
@Builder
@Jacksonized
@AllArgsConstructor
public class Choice {
Character character;
String description;
}
@@ -2,17 +2,14 @@ package io.xpipe.core.dialog;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.List;
@JsonTypeName("choice")
public class ChoiceElement extends DialogElement {
private final List<Element> elements;
private final String description;
private final List<Choice> elements;
private int selected;
@@ -42,26 +39,22 @@ public class ChoiceElement extends DialogElement {
return false;
}
@Value
@Builder
@Jacksonized
@AllArgsConstructor
public static class Element {
Character character;
String description;
}
@JsonCreator
public ChoiceElement(List<Element> elements, int selected) {
public ChoiceElement(String description, List<Choice> elements, int selected) {
this.description = description;
this.elements = elements;
this.selected = selected;
}
public List<Element> getElements() {
public List<Choice> getElements() {
return elements;
}
public int getSelected() {
return selected;
}
public String getDescription() {
return description;
}
}
@@ -1,42 +1,112 @@
package io.xpipe.core.dialog;
import io.xpipe.core.util.Secret;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public abstract class Dialog {
private static class Sequence extends Dialog {
private int index = 0;
private final DialogElement[] es;
public Sequence(DialogElement... es) {
this.es = es;
}
@Override
public DialogElement start() {
index = 0;
return es[0];
}
@Override
public DialogElement receive(String answer) {
if (es[index].apply(answer)) {
if (index == es.length - 1) {
complete();
return null;
} else {
return es[++index];
}
public static Dialog empty() {
return new Dialog() {
@Override
public DialogElement start() throws Exception {
return null;
}
return es[index];
@Override
protected DialogElement next(String answer) throws Exception {
return null;
}
};
}
public static class Choice extends Dialog {
private final ChoiceElement element;
private Choice(String description, List<io.xpipe.core.dialog.Choice> elements, int selected) {
this.element = new ChoiceElement(description, elements, selected);
}
@Override
public DialogElement start() throws Exception {
return element;
}
@Override
protected DialogElement next(String answer) throws Exception {
if (element.apply(answer)) {
return null;
}
return element;
}
private int getSelected() {
return element.getSelected();
}
}
public static Dialog chain(DialogElement... es) {
return new Dialog.Sequence(es);
public static Dialog.Choice choice(String description, List<io.xpipe.core.dialog.Choice> elements, int selected) {
Dialog.Choice c = new Dialog.Choice(description, elements, selected);
c.evaluateTo(c::getSelected);
return c;
}
@SafeVarargs
public static <T> Dialog.Choice choice(String description, Function<T, String> toString, T def, T... vals) {
var elements = Arrays.stream(vals).map(v -> new io.xpipe.core.dialog.Choice(null, toString.apply(v))).toList();
var index = Arrays.asList(vals).indexOf(def);
var c = choice(description, elements, index);
c.evaluateTo(() -> vals[c.getSelected()]);
return c;
}
public static class Query extends Dialog {
private final QueryElement element;
private Query(String description, boolean newLine, boolean required, Object value, QueryConverter<?> converter, boolean hidden) {
this.element = new QueryElement(description, newLine, required,value, converter, hidden);
}
@Override
public Optional<Map.Entry<String, String>> toValue() {
return Optional.of(new AbstractMap.SimpleEntry<>(element.getDescription(), element.getValue()));
}
@Override
public DialogElement start() throws Exception {
return element;
}
@Override
protected DialogElement next(String answer) throws Exception {
if (element.apply(answer)) {
return null;
}
return element;
}
private <T> T getConvertedValue() {
return element.getConvertedValue();
}
}
public static Dialog.Query query(String description, boolean newLine, boolean required, Object value, QueryConverter<?> converter) {
var q = new Dialog.Query(description, newLine, required, value, converter, false);
q.evaluateTo(q::getConvertedValue);
return q;
}
public static Dialog.Query querySecret(String description, boolean newLine, boolean required, Secret value) {
var q = new Dialog.Query(description, newLine, required, value, QueryConverter.SECRET, true);
q.evaluateTo(q::getConvertedValue);
return q;
}
public static Dialog chain(Dialog... ds) {
@@ -45,50 +115,100 @@ public abstract class Dialog {
private int current = 0;
@Override
public DialogElement start() {
public DialogElement start() throws Exception {
current = 0;
eval = null;
return ds[0].start();
}
@Override
public DialogElement receive(String answer) {
protected DialogElement next(String answer) throws Exception {
DialogElement currentElement = ds[current].receive(answer);
if (currentElement == null) {
ds[current].complete();
if (current == ds.length - 1) {
complete();
return null;
} else {
return ds[++current].start();
}
DialogElement next = null;
while (current < ds.length - 1 && (next = ds[++current].start()) == null) {
};
return next;
}
return currentElement;
}
};
}.evaluateTo(ds[ds.length - 1]);
}
public static Dialog repeatIf(Dialog d, Supplier<Boolean> shouldRepeat) {
public static <T> Dialog repeatIf(Dialog d, Predicate<T> shouldRepeat) {
return new Dialog() {
@Override
public DialogElement start() {
public DialogElement start() throws Exception {
eval = null;
return d.start();
}
@Override
public DialogElement receive(String answer) {
protected DialogElement next(String answer) throws Exception {
var next = d.receive(answer);
if (next == null) {
if (shouldRepeat.get()) {
if (shouldRepeat.test(d.getResult())) {
return d.start();
}
}
return next;
}
}.evaluateTo(d.onCompletion);
}.evaluateTo(d).onCompletion(d.completion);
}
public static Dialog header(String msg) {
return of(new HeaderElement(msg)).evaluateTo(() -> msg);
}
public static Dialog header(Supplier<String> msg) {
final String[] msgEval = {null};
return new Dialog() {
@Override
public DialogElement start() throws Exception {
msgEval[0] = msg.get();
return new HeaderElement(msgEval[0]);
}
@Override
protected DialogElement next(String answer) throws Exception {
return null;
}
}.evaluateTo(() -> msgEval[0]);
}
public static Dialog busy() {
return of(new BusyElement());
}
public static interface FailableSupplier<T> {
T get() throws Exception;
}
public static Dialog lazy(FailableSupplier<Dialog> d) {
return new Dialog() {
Dialog dialog;
@Override
public DialogElement start() throws Exception {
eval = null;
dialog = d.get();
evaluateTo(dialog);
return dialog.start();
}
@Override
protected DialogElement next(String answer) throws Exception {
return dialog.receive(answer);
}
};
}
public static Dialog of(DialogElement e) {
@@ -96,14 +216,14 @@ public abstract class Dialog {
@Override
public DialogElement start() {
public DialogElement start() throws Exception {
eval = null;
return e;
}
@Override
public DialogElement receive(String answer) {
protected DialogElement next(String answer) throws Exception {
if (e.apply(answer)) {
complete();
return null;
}
@@ -112,18 +232,38 @@ public abstract class Dialog {
};
}
public static Dialog retryIf(Dialog d, Supplier<String> msg) {
public static Dialog skipIf(Dialog d, Supplier<Boolean> check) {
return new Dialog() {
private Dialog active;
@Override
public DialogElement start() throws Exception {
active = check.get() ? null : d;
return active != null ? active.start() : null;
}
@Override
protected DialogElement next(String answer) throws Exception {
return active != null ? active.receive(answer) : null;
}
}.evaluateTo(d).onCompletion(d.completion);
}
public static <T> Dialog retryIf(Dialog d, Function<T, String> msg) {
return new Dialog() {
private boolean retry;
@Override
public DialogElement start() {
public DialogElement start() throws Exception {
eval = null;
return d.start();
}
@Override
public DialogElement receive(String answer) {
protected DialogElement next(String answer) throws Exception {
if (retry) {
retry = false;
return d.start();
@@ -131,7 +271,7 @@ public abstract class Dialog {
var next = d.receive(answer);
if (next == null) {
var s = msg.get();
var s = msg.apply(d.getResult());
if (s != null) {
retry = true;
return new HeaderElement(s);
@@ -140,47 +280,78 @@ public abstract class Dialog {
return next;
}
}.evaluateTo(d.onCompletion);
}.evaluateTo(d.evaluation).onCompletion(d.completion);
}
public static Dialog choice(ChoiceElement choice, Function<Integer, Dialog> c) {
public static Dialog fork(String description, List<io.xpipe.core.dialog.Choice> elements, int selected, Function<Integer, Dialog> c) {
var choice = new ChoiceElement(description, elements, selected);
return new Dialog() {
private Dialog choiceMade;
@Override
public DialogElement start() {
public DialogElement start() throws Exception {
choiceMade = null;
eval = null;
return choice;
}
@Override
public DialogElement receive(String answer) {
protected DialogElement next(String answer) throws Exception {
if (choiceMade != null) {
var r = choiceMade.receive(answer);
if (r == null) {
complete();
}
return r;
}
if (choice.apply(answer)) {
choiceMade = c.apply(choice.getSelected());
return choiceMade.start();
return choiceMade != null ? choiceMade.start() : null;
}
return choice;
}
};
}.evaluateTo(() -> choice.getSelected());
}
private Object eval;
private Supplier<?> onCompletion;
protected Object eval;
private Supplier<?> evaluation;
private final List<Consumer<?>> completion = new ArrayList<>();
public abstract DialogElement start();
public abstract DialogElement start() throws Exception;
public Optional<Map.Entry<String, String>> toValue() {
return Optional.empty();
}
public Dialog evaluateTo(Dialog d) {
evaluation = d.evaluation;
return this;
}
public Dialog evaluateTo(Supplier<?> s) {
onCompletion = s;
evaluation = s;
return this;
}
@SuppressWarnings("unchecked")
public <T> Dialog map(Function<T, ?> s) {
var oldEval = evaluation;
evaluation = () -> s.apply((T) oldEval.get());
return this;
}
public Dialog onCompletion(Consumer<?> s) {
completion.add(s);
return this;
}
public Dialog onCompletion(Runnable r) {
completion.add(v -> r.run());
return this;
}
public Dialog onCompletion(List<Consumer<?>> s) {
completion.addAll(s);
return this;
}
@@ -189,11 +360,24 @@ public abstract class Dialog {
return (T) eval;
}
public void complete() {
if (onCompletion != null) {
eval = onCompletion.get();
@SuppressWarnings("unchecked")
public <T> void complete() {
if (evaluation != null) {
eval = evaluation.get();
completion.forEach(c -> {
Consumer<T> ct = (Consumer<T>) c;
ct.accept((T) eval);
});
}
}
public abstract DialogElement receive(String answer);
public final DialogElement receive(String answer) throws Exception {
var next = next(answer);
if (next == null) {
complete();
}
return next;
}
protected abstract DialogElement next(String answer) throws Exception;
}
@@ -0,0 +1,18 @@
package io.xpipe.core.dialog;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import java.util.UUID;
@Value
@Builder
@Jacksonized
@AllArgsConstructor
public class DialogReference {
UUID dialogId;
DialogElement start;
}
@@ -1,8 +1,12 @@
package io.xpipe.core.dialog;
import java.net.MalformedURLException;
import java.net.URL;
import io.xpipe.core.util.Secret;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.AbstractMap;
import java.util.Map;
public abstract class QueryConverter<T> {
@@ -30,18 +34,51 @@ public abstract class QueryConverter<T> {
}
};
public static final QueryConverter<URL> URL = new QueryConverter<URL>() {
public static final QueryConverter<Secret> SECRET = new QueryConverter<Secret>() {
@Override
protected URL fromString(String s) {
protected Secret fromString(String s) {
return Secret.parse(s);
}
@Override
protected String toString(Secret value) {
return value.getValue();
}
};
public static final QueryConverter<Map.Entry<String, String>> HTTP_HEADER = new QueryConverter<Map.Entry<String, String>>() {
@Override
protected Map.Entry<String, String> fromString(String s) {
if (!s.contains(":")) {
throw new IllegalArgumentException("Missing colon");
}
var split = s.split(":");
if (split.length != 2) {
throw new IllegalArgumentException("Too many colons");
}
return new AbstractMap.SimpleEntry<>(split[0].trim(), split[1].trim());
}
@Override
protected String toString(Map.Entry<String, String> value) {
return value.getKey() + ": " + value.getValue();
}
};
public static final QueryConverter<URI> URI = new QueryConverter<URI>() {
@Override
protected URI fromString(String s) {
try {
return new URL(s);
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
return new URI(s);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
@Override
protected String toString(URL value) {
protected String toString(URI value) {
return value.toString();
}
};
@@ -1,21 +1,14 @@
package io.xpipe.core.dialog;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@JsonSerialize(as = BaseQueryElement.class)
public class QueryElement extends BaseQueryElement {
@JsonIgnore
private final QueryConverter<?> converter;
public QueryElement(String description, boolean required, String value, QueryConverter<?> converter) {
super(description, required, value);
this.converter = converter;
}
public QueryElement(String description, boolean required, Object value, QueryConverter<?> converter) {
super(description, required, value != null ? value.toString() : null);
public QueryElement(String description, boolean newLine, boolean required, Object value, QueryConverter<?> converter, boolean hidden) {
super(description, newLine, required, hidden, value != null ? value.toString() : null);
this.converter = converter;
}
@@ -1,6 +1,6 @@
package io.xpipe.core.store;
public abstract class CollectionEntryDataStore implements FileDataStore {
public abstract class CollectionEntryDataStore implements StreamDataStore, FilenameStore {
private final boolean directory;
private final String name;
@@ -1,19 +0,0 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Value;
import java.io.InputStream;
@Value
@JsonTypeName("commandInput")
public class CommandInputStore implements StreamDataStore {
String cmd;
@Override
public InputStream openInput() throws Exception {
var proc = Runtime.getRuntime().exec(cmd);
return proc.getInputStream();
}
}
@@ -16,6 +16,17 @@ import java.util.Optional;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public interface DataStore {
default void validate() throws Exception {
}
default boolean delete() throws Exception {
return false;
}
default String toDisplay() {
return null;
}
/**
* Casts this instance to the required type without checking whether a cast is possible.
*/
@@ -0,0 +1,44 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Value;
import java.nio.file.Path;
@Value
@JsonTypeName("file")
public class FileStore implements StreamDataStore, FilenameStore {
public static FileStore local(Path p) {
return new FileStore(MachineStore.local(), p.toString());
}
public static FileStore local(String p) {
return new FileStore(MachineStore.local(), p);
}
MachineStore machine;
String file;
@JsonCreator
public FileStore(MachineStore machine, String file) {
this.machine = machine;
this.file = file;
}
@Override
public String toDisplay() {
return file + "@" + machine.toDisplay();
}
@Override
public boolean persistent() {
return true;
}
@Override
public String getFileName() {
return file;
}
}
@@ -2,7 +2,7 @@ package io.xpipe.core.store;
import java.util.Optional;
public interface FileDataStore extends StreamDataStore {
public interface FilenameStore extends DataStore {
@Override
default Optional<String> determineDefaultName() {
@@ -11,10 +11,5 @@ public interface FileDataStore extends StreamDataStore {
return Optional.of(i != -1 ? n.substring(0, i) : n);
}
@Override
default boolean persistent() {
return true;
}
String getFileName();
}
@@ -1,52 +0,0 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Value;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Map;
import java.util.Optional;
@Value
@JsonTypeName("httpRequest")
public class HttpRequestStore implements StreamDataStore {
public static boolean isHttpRequest(String s) {
return s.startsWith("http:") || s.startsWith("https:");
}
public static Optional<HttpRequestStore> fromString(String s) {
try {
var uri = new URI(s);
return Optional.of(new HttpRequestStore(uri, Map.of()));
} catch (URISyntaxException e) {
return Optional.empty();
}
}
URI uri;
Map<String, String> headers;
@Override
public InputStream openInput() throws Exception {
var b = HttpRequest.newBuilder().uri(uri);
headers.forEach(b::setHeader);
var req = b.GET().build();
var client = HttpClient.newHttpClient();
var res = client.send(req, HttpResponse.BodyHandlers.ofByteArray());
return new ByteArrayInputStream(res.body());
}
@Override
public boolean exists() {
return false;
}
}
@@ -1,70 +0,0 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.EqualsAndHashCode;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.Instant;
import java.util.Optional;
@JsonTypeName("local")
@EqualsAndHashCode
public class LocalFileDataStore implements FileDataStore {
private final Path file;
@JsonCreator
public LocalFileDataStore(Path file) {
this.file = file;
}
public String toString() {
return getFileName();
}
@Override
public Optional<Instant> determineLastModified() {
try {
var l = Files.getLastModifiedTime(file);
return Optional.of(l.toInstant());
} catch (IOException e) {
return Optional.empty();
}
}
public Path getFile() {
return file;
}
@Override
public InputStream openInput() throws Exception {
return new BufferedInputStream(Files.newInputStream(file));
}
@Override
public OutputStream openOutput() throws Exception {
return Files.newOutputStream(file);
}
@Override
public OutputStream openAppendingOutput() throws Exception {
return Files.newOutputStream(file, StandardOpenOption.APPEND);
}
@Override
public boolean exists() {
return Files.exists(file);
}
@Override
public String getFileName() {
return file.getFileName().toString();
}
}
@@ -0,0 +1,29 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
@JsonTypeName("local")
public class LocalMachineStore implements MachineStore {
@Override
public String toDisplay() {
return "local";
}
@Override
public InputStream openInput(String file) throws Exception {
var p = Path.of(file);
return Files.newInputStream(p);
}
@Override
public OutputStream openOutput(String file) throws Exception {
var p = Path.of(file);
return Files.newOutputStream(p);
}
}
@@ -0,0 +1,16 @@
package io.xpipe.core.store;
import java.io.InputStream;
import java.io.OutputStream;
public interface MachineStore extends DataStore {
static MachineStore local() {
return new LocalMachineStore();
}
InputStream openInput(String file) throws Exception;
OutputStream openOutput(String file) throws Exception;
}
@@ -1,34 +0,0 @@
package io.xpipe.core.store;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.Instant;
import java.util.Optional;
public class RemoteFileDataStore implements StreamDataStore {
@Override
public Optional<String> determineDefaultName() {
return Optional.empty();
}
@Override
public Optional<Instant> determineLastModified() {
return Optional.empty();
}
@Override
public InputStream openInput() throws Exception {
return null;
}
@Override
public OutputStream openOutput() throws Exception {
return null;
}
@Override
public boolean exists() {
return false;
}
}
@@ -1,14 +1,7 @@
package io.xpipe.core.store;
import lombok.NonNull;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.Optional;
/**
* A data store that can be accessed using InputStreams and/or OutputStreams.
@@ -16,21 +9,6 @@ import java.util.Optional;
*/
public interface StreamDataStore extends DataStore {
static Optional<StreamDataStore> fromString(@NonNull String s) {
try {
var path = Path.of(s);
return Optional.of(new LocalFileDataStore(path));
} catch (InvalidPathException ignored) {
}
try {
var path = new URL(s);
} catch (MalformedURLException ignored) {
}
return Optional.empty();
}
/**
* Opens an input stream. This input stream does not necessarily have to be a new instance.
*/
@@ -11,19 +11,20 @@ import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.module.SimpleModule;
import io.xpipe.core.dialog.BaseQueryElement;
import io.xpipe.core.dialog.ChoiceElement;
import io.xpipe.core.dialog.HeaderElement;
import io.xpipe.core.data.type.ArrayType;
import io.xpipe.core.data.type.TupleType;
import io.xpipe.core.data.type.ValueType;
import io.xpipe.core.data.type.WildcardType;
import io.xpipe.core.dialog.BaseQueryElement;
import io.xpipe.core.dialog.BusyElement;
import io.xpipe.core.dialog.ChoiceElement;
import io.xpipe.core.dialog.HeaderElement;
import io.xpipe.core.source.DataSourceInfo;
import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.store.CollectionEntryDataStore;
import io.xpipe.core.store.HttpRequestStore;
import io.xpipe.core.store.FileStore;
import io.xpipe.core.store.LocalDirectoryDataStore;
import io.xpipe.core.store.LocalFileDataStore;
import io.xpipe.core.store.LocalMachineStore;
import java.io.IOException;
import java.nio.charset.Charset;
@@ -34,10 +35,9 @@ public class CoreJacksonModule extends SimpleModule {
@Override
public void setupModule(SetupContext context) {
context.registerSubtypes(
new NamedType(LocalFileDataStore.class),
new NamedType(FileStore.class),
new NamedType(LocalDirectoryDataStore.class),
new NamedType(CollectionEntryDataStore.class),
new NamedType(HttpRequestStore.class),
new NamedType(ValueType.class),
new NamedType(TupleType.class),
new NamedType(ArrayType.class),
@@ -49,6 +49,8 @@ public class CoreJacksonModule extends SimpleModule {
new NamedType(DataSourceInfo.Raw.class),
new NamedType(BaseQueryElement.class),
new NamedType(ChoiceElement.class),
new NamedType(BusyElement.class),
new NamedType(LocalMachineStore.class),
new NamedType(HeaderElement.class)
);
@@ -58,6 +60,9 @@ public class CoreJacksonModule extends SimpleModule {
addSerializer(Path.class, new LocalPathSerializer());
addDeserializer(Path.class, new LocalPathDeserializer());
addSerializer(Secret.class, new SecretSerializer());
addDeserializer(Secret.class, new SecretDeserializer());
addSerializer(DataSourceReference.class, new DataSourceReferenceSerializer());
addDeserializer(DataSourceReference.class, new DataSourceReferenceDeserializer());
@@ -119,6 +124,23 @@ public class CoreJacksonModule extends SimpleModule {
}
}
public static class SecretSerializer extends JsonSerializer<Secret> {
@Override
public void serialize(Secret value, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeString(value.getValue());
}
}
public static class SecretDeserializer extends JsonDeserializer<Secret> {
@Override
public Secret deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return Secret.parse(p.getValueAsString());
}
}
@JsonSerialize(as = Throwable.class)
public abstract static class ThrowableTypeMixIn {
@@ -0,0 +1,30 @@
package io.xpipe.core.util;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
@AllArgsConstructor
@EqualsAndHashCode
public class Secret {
public static Secret parse(String s) {
return new Secret(Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_8)));
}
String value;
public String getDisplay() {
return "*".repeat(value.length());
}
public String getValue() {
return value;
}
public String getSecret() {
return new String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8);
}
}