From 742a354000241d25406ffbe9a38f9eb2e6d0e128 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Thu, 14 Nov 2024 21:10:13 +0100 Subject: Add dispatch codecs --- genrecord.ts | 10 +- .../test/java/moe/nea/jcp/gson/test/TestBasic.java | 38 +++++- src/main/java/moe/nea/pcj/Result.java | 5 +- src/main/java/moe/nea/pcj/json/MapCodec.java | 39 ++++++- src/main/java/moe/nea/pcj/json/RecordCodec.java | 13 +-- src/main/java/moe/nea/pcj/json/RecordJoiners.java | 127 +++++++++------------ 6 files changed, 137 insertions(+), 95 deletions(-) diff --git a/genrecord.ts b/genrecord.ts index a700dc9..ba3d0e2 100644 --- a/genrecord.ts +++ b/genrecord.ts @@ -35,19 +35,18 @@ function argFor(va: string): string { function genRecordJoiner(elements: number) { if (!elements) return const vars = [...new Array(elements)].map((_, idx) => "T" + idx) - console.log(`\tpublic static ${typArgs([...vars, 'O', 'Format'])} JsonCodec join(`) + console.log(`\tpublic static ${typArgs([...vars, 'O', 'Format'])} MapCodec join(`) for (let var1 of vars) { console.log(`\t\tRecordCodec ${argFor(var1)},`) } console.log(`\t\tTuple.Func${elements}${typArgs(['O', ...vars])} joiner`) console.log("\t) {") - console.log("\t\treturn new RecordCompleteCodec<>() {") + console.log("\t\treturn new MapCodec<>() {") console.log("\t\t\t@Override") - console.log("\t\t\tpublic Result encode(O data, JsonLikeOperations ops) {") + console.log("\t\t\tpublic Result, JsonLikeError> encode(O data, JsonLikeOperations ops) {") console.log(`\t\t\t\treturn Stream.of(${vars.map(it => argFor(it) + ".enc(data, ops)").join(", ")})`) - console.log(`\t\t\t\t\t.reduce(Result.ok(ops.createObject()), RecordCodec::merge)`) - console.log(`\t\t\t\t\t.map(RecordBuilder::complete);`) + console.log(`\t\t\t\t\t.reduce(Result.ok(ops.createObject()), RecordCodec::merge);`) console.log("\t\t\t}") console.log("\t\t\t@Override") @@ -65,7 +64,6 @@ function genRecords(maxI: number) { console.log("package moe.nea.pcj.json;") console.log(` import moe.nea.pcj.*; -import moe.nea.pcj.json.RecordCodec.*; import java.util.stream.*;`) console.log() console.log("@SuppressWarnings(\"unused\")") diff --git a/gson/src/test/java/moe/nea/jcp/gson/test/TestBasic.java b/gson/src/test/java/moe/nea/jcp/gson/test/TestBasic.java index 70c4345..628518c 100644 --- a/gson/src/test/java/moe/nea/jcp/gson/test/TestBasic.java +++ b/gson/src/test/java/moe/nea/jcp/gson/test/TestBasic.java @@ -91,10 +91,17 @@ public class TestBasic { new UnexpectedJsonElement("number", mkPrim("1"))); } + sealed interface Parent permits OtherTestObject, TestObject { + } + + record OtherTestObject( + String test + ) implements Parent {} + record TestObject( String foo, int bar - ) {} + ) implements Parent {} @Test @@ -109,7 +116,7 @@ public class TestBasic { codecs.STRING.fieldOf("foo").withGetter(TestObject::foo), codecs.INTEGER.fieldOf("bar").withGetter(TestObject::bar), TestObject::new - ); + ).codec(); assertSuccess(decode(codec, mkJsonObject("foo", "fooValue", "bar", -10)), new TestObject("fooValue", -10)); assertFail(decode(codec, mkJsonObject("foo", "fooValue")), @@ -124,7 +131,7 @@ public class TestBasic { codecs.STRING.fieldOf("foo").withGetter(TestObject::foo), codecs.INTEGER.fieldOf("foo").withGetter(TestObject::bar), TestObject::new - ); + ).codec(); // TODO: add test for decoding with duplicate keys warning (esp. optional fields) assertFail(codec.encode(new TestObject("", 0), GsonOperations.INSTANCE), new DuplicateJsonKey("foo")); @@ -138,5 +145,30 @@ public class TestBasic { assertFail(decode(codec, mkJsonArray("foo", mkJsonObject("hello", "bar"))), new AtIndex(0, new UnexpectedJsonElement("object", mkPrim("foo")))); } + + @Test + void testDispatched() { + var testObjectCodec = RecordJoiners.join( + codecs.STRING.fieldOf("foo").withGetter(TestObject::foo), + codecs.INTEGER.fieldOf("bar").withGetter(TestObject::bar), + TestObject::new + ); + var otherObjectCodec = RecordJoiners.join( + codecs.STRING.fieldOf("test").withGetter(OtherTestObject::test), + OtherTestObject::new + ); + codecs.STRING.fieldOf("type") + .dispatch( + obj -> switch (obj) { + case OtherTestObject ignored -> "other"; + case TestObject ignored -> "normal"; + }, + key -> switch (key) { + case "other" -> otherObjectCodec; + case "normal" -> testObjectCodec; + default -> throw new AssertionError("Unknown thing"); + } + ); + } } diff --git a/src/main/java/moe/nea/pcj/Result.java b/src/main/java/moe/nea/pcj/Result.java index af5398e..a9c2494 100644 --- a/src/main/java/moe/nea/pcj/Result.java +++ b/src/main/java/moe/nea/pcj/Result.java @@ -7,7 +7,6 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.function.Consumer; import java.util.function.Function; public sealed interface Result permits Result.Ok, Result.Fail { @@ -37,6 +36,10 @@ public sealed interface Result permits Result.Ok, Result.Fail { Result mapErrors(Function, List> mapper); + default Result appendError(Bad error) { + return appendErrors(List.of(error)); + } + Result appendErrors(List error); record Ok(Good okValue) implements Result { diff --git a/src/main/java/moe/nea/pcj/json/MapCodec.java b/src/main/java/moe/nea/pcj/json/MapCodec.java index 4b76150..b18a639 100644 --- a/src/main/java/moe/nea/pcj/json/MapCodec.java +++ b/src/main/java/moe/nea/pcj/json/MapCodec.java @@ -5,14 +5,49 @@ import moe.nea.pcj.Result; import java.util.function.Function; public interface MapCodec { - Result decode( + Result decode( RecordView record, JsonLikeOperations ops); Result, JsonLikeError> encode(T value, JsonLikeOperations ops); + default MapCodec dispatch( + Function keyExtractor, + Function> codecGenerator + ) { + // TODO: the codecGenerator function is not exactly typesafe. there should be some limit on keyExtractor and codecGenerator working in tandem + return new MapCodec<>() { + @Override + public Result decode(RecordView record, JsonLikeOperations ops) { + return MapCodec.this.decode(record, ops) + .map(codecGenerator::apply) + .flatMap(codec -> codec.decode(record, ops)); + } + + @Override + public Result, JsonLikeError> encode(O value, JsonLikeOperations ops) { + var key = keyExtractor.apply(value); + var codec = codecGenerator.apply(key); + return MapCodec.this + .encode(key, ops) + .flatMap(keyEncoded -> ((MapCodec) codec).encode(value, ops).flatMap(keyEncoded::mergeWith)); + } + }; + } + default JsonCodec codec() { - return RecordJoiners.join(withGetter(it -> it), it -> it); + return new JsonCodec<>() { + @Override + public Result decode(Format format, JsonLikeOperations ops) { + return Result., JsonLikeError>cast(ops.getObject(format)) + .flatMap(record -> MapCodec.this.decode(record, ops)); + } + + @Override + public Result encode(T data, JsonLikeOperations ops) { + return Result.cast(MapCodec.this.encode(data, ops)).map(RecordBuilder::complete); + } + }; } default RecordCodec withGetter(Function getter) { diff --git a/src/main/java/moe/nea/pcj/json/RecordCodec.java b/src/main/java/moe/nea/pcj/json/RecordCodec.java index cb6d696..fa7aac7 100644 --- a/src/main/java/moe/nea/pcj/json/RecordCodec.java +++ b/src/main/java/moe/nea/pcj/json/RecordCodec.java @@ -14,21 +14,10 @@ public record RecordCodec( } Result dec(RecordView data, JsonLikeOperations ops) { - return codec().decode(data, ops); + return Result.cast(codec().decode(data, ops)); } static Result, JsonLikeError> merge(Result, JsonLikeError> left, Result, JsonLikeError> right) { return left.flatMap(l -> right.flatMap(l::mergeWith)); } - - abstract static class RecordCompleteCodec implements JsonCodec { - @Override - public Result decode(Format format, JsonLikeOperations ops) { - return Result., JsonLikeError>cast(ops.getObject(format)) - .flatMap(record -> (decode(record, ops))); - } - - protected abstract Result decode(RecordView record, JsonLikeOperations ops); - } - } diff --git a/src/main/java/moe/nea/pcj/json/RecordJoiners.java b/src/main/java/moe/nea/pcj/json/RecordJoiners.java index 3af622d..57bdd63 100644 --- a/src/main/java/moe/nea/pcj/json/RecordJoiners.java +++ b/src/main/java/moe/nea/pcj/json/RecordJoiners.java @@ -2,21 +2,19 @@ package moe.nea.pcj.json; import moe.nea.pcj.*; -import moe.nea.pcj.json.RecordCodec.*; import java.util.stream.*; @SuppressWarnings("unused") public class RecordJoiners { - public static JsonCodec join( + public static MapCodec join( RecordCodec arg0, Tuple.Func1 joiner ) { - return new RecordCompleteCodec<>() { + return new MapCodec<>() { @Override - public Result encode(O data, JsonLikeOperations ops) { + public Result, JsonLikeError> encode(O data, JsonLikeOperations ops) { return Stream.of(arg0.enc(data, ops)) - .reduce(Result.ok(ops.createObject()), RecordCodec::merge) - .map(RecordBuilder::complete); + .reduce(Result.ok(ops.createObject()), RecordCodec::merge); } @Override public Result decode(RecordView format, JsonLikeOperations ops) { @@ -25,17 +23,16 @@ public class RecordJoiners { } }; } - public static JsonCodec join( + public static MapCodec join( RecordCodec arg0, RecordCodec arg1, Tuple.Func2 joiner ) { - return new RecordCompleteCodec<>() { + return new MapCodec<>() { @Override - public Result encode(O data, JsonLikeOperations ops) { + public Result, JsonLikeError> encode(O data, JsonLikeOperations ops) { return Stream.of(arg0.enc(data, ops), arg1.enc(data, ops)) - .reduce(Result.ok(ops.createObject()), RecordCodec::merge) - .map(RecordBuilder::complete); + .reduce(Result.ok(ops.createObject()), RecordCodec::merge); } @Override public Result decode(RecordView format, JsonLikeOperations ops) { @@ -44,18 +41,17 @@ public class RecordJoiners { } }; } - public static JsonCodec join( + public static MapCodec join( RecordCodec arg0, RecordCodec arg1, RecordCodec arg2, Tuple.Func3 joiner ) { - return new RecordCompleteCodec<>() { + return new MapCodec<>() { @Override - public Result encode(O data, JsonLikeOperations ops) { + public Result, JsonLikeError> encode(O data, JsonLikeOperations ops) { return Stream.of(arg0.enc(data, ops), arg1.enc(data, ops), arg2.enc(data, ops)) - .reduce(Result.ok(ops.createObject()), RecordCodec::merge) - .map(RecordBuilder::complete); + .reduce(Result.ok(ops.createObject()), RecordCodec::merge); } @Override public Result decode(RecordView format, JsonLikeOperations ops) { @@ -64,19 +60,18 @@ public class RecordJoiners { } }; } - public static JsonCodec join( + public static MapCodec join( RecordCodec arg0, RecordCodec arg1, RecordCodec arg2, RecordCodec arg3, Tuple.Func4 joiner ) { - return new RecordCompleteCodec<>() { + return new MapCodec<>() { @Override - public Result encode(O data, JsonLikeOperations ops) { + public Result, JsonLikeError> encode(O data, JsonLikeOperations ops) { return Stream.of(arg0.enc(data, ops), arg1.enc(data, ops), arg2.enc(data, ops), arg3.enc(data, ops)) - .reduce(Result.ok(ops.createObject()), RecordCodec::merge) - .map(RecordBuilder::complete); + .reduce(Result.ok(ops.createObject()), RecordCodec::merge); } @Override public Result decode(RecordView format, JsonLikeOperations ops) { @@ -85,7 +80,7 @@ public class RecordJoiners { } }; } - public static JsonCodec join( + public static MapCodec join( RecordCodec arg0, RecordCodec arg1, RecordCodec arg2, @@ -93,12 +88,11 @@ public class RecordJoiners { RecordCodec arg4, Tuple.Func5 joiner ) { - return new RecordCompleteCodec<>() { + return new MapCodec<>() { @Override - public Result encode(O data, JsonLikeOperations ops) { + public Result, JsonLikeError> encode(O data, JsonLikeOperations ops) { return Stream.of(arg0.enc(data, ops), arg1.enc(data, ops), arg2.enc(data, ops), arg3.enc(data, ops), arg4.enc(data, ops)) - .reduce(Result.ok(ops.createObject()), RecordCodec::merge) - .map(RecordBuilder::complete); + .reduce(Result.ok(ops.createObject()), RecordCodec::merge); } @Override public Result decode(RecordView format, JsonLikeOperations ops) { @@ -107,7 +101,7 @@ public class RecordJoiners { } }; } - public static JsonCodec join( + public static MapCodec join( RecordCodec arg0, RecordCodec arg1, RecordCodec arg2, @@ -116,12 +110,11 @@ public class RecordJoiners { RecordCodec arg5, Tuple.Func6 joiner ) { - return new RecordCompleteCodec<>() { + return new MapCodec<>() { @Override - public Result encode(O data, JsonLikeOperations ops) { + public Result, JsonLikeError> encode(O data, JsonLikeOperations ops) { return Stream.of(arg0.enc(data, ops), arg1.enc(data, ops), arg2.enc(data, ops), arg3.enc(data, ops), arg4.enc(data, ops), arg5.enc(data, ops)) - .reduce(Result.ok(ops.createObject()), RecordCodec::merge) - .map(RecordBuilder::complete); + .reduce(Result.ok(ops.createObject()), RecordCodec::merge); } @Override public Result decode(RecordView format, JsonLikeOperations ops) { @@ -130,7 +123,7 @@ public class RecordJoiners { } }; } - public static JsonCodec join( + public static MapCodec join( RecordCodec arg0, RecordCodec arg1, RecordCodec arg2, @@ -140,12 +133,11 @@ public class RecordJoiners { RecordCodec arg6, Tuple.Func7 joiner ) { - return new RecordCompleteCodec<>() { + return new MapCodec<>() { @Override - public Result encode(O data, JsonLikeOperations ops) { + public Result, JsonLikeError> encode(O data, JsonLikeOperations ops) { return Stream.of(arg0.enc(data, ops), arg1.enc(data, ops), arg2.enc(data, ops), arg3.enc(data, ops), arg4.enc(data, ops), arg5.enc(data, ops), arg6.enc(data, ops)) - .reduce(Result.ok(ops.createObject()), RecordCodec::merge) - .map(RecordBuilder::complete); + .reduce(Result.ok(ops.createObject()), RecordCodec::merge); } @Override public Result decode(RecordView format, JsonLikeOperations ops) { @@ -154,7 +146,7 @@ public class RecordJoiners { } }; } - public static JsonCodec join( + public static MapCodec join( RecordCodec arg0, RecordCodec arg1, RecordCodec arg2, @@ -165,12 +157,11 @@ public class RecordJoiners { RecordCodec arg7, Tuple.Func8 joiner ) { - return new RecordCompleteCodec<>() { + return new MapCodec<>() { @Override - public Result encode(O data, JsonLikeOperations ops) { + public Result, JsonLikeError> encode(O data, JsonLikeOperations ops) { return Stream.of(arg0.enc(data, ops), arg1.enc(data, ops), arg2.enc(data, ops), arg3.enc(data, ops), arg4.enc(data, ops), arg5.enc(data, ops), arg6.enc(data, ops), arg7.enc(data, ops)) - .reduce(Result.ok(ops.createObject()), RecordCodec::merge) - .map(RecordBuilder::complete); + .reduce(Result.ok(ops.createObject()), RecordCodec::merge); } @Override public Result decode(RecordView format, JsonLikeOperations ops) { @@ -179,7 +170,7 @@ public class RecordJoiners { } }; } - public static JsonCodec join( + public static MapCodec join( RecordCodec arg0, RecordCodec arg1, RecordCodec arg2, @@ -191,12 +182,11 @@ public class RecordJoiners { RecordCodec arg8, Tuple.Func9 joiner ) { - return new RecordCompleteCodec<>() { + return new MapCodec<>() { @Override - public Result encode(O data, JsonLikeOperations ops) { + public Result, JsonLikeError> encode(O data, JsonLikeOperations ops) { return Stream.of(arg0.enc(data, ops), arg1.enc(data, ops), arg2.enc(data, ops), arg3.enc(data, ops), arg4.enc(data, ops), arg5.enc(data, ops), arg6.enc(data, ops), arg7.enc(data, ops), arg8.enc(data, ops)) - .reduce(Result.ok(ops.createObject()), RecordCodec::merge) - .map(RecordBuilder::complete); + .reduce(Result.ok(ops.createObject()), RecordCodec::merge); } @Override public Result decode(RecordView format, JsonLikeOperations ops) { @@ -205,7 +195,7 @@ public class RecordJoiners { } }; } - public static JsonCodec join( + public static MapCodec join( RecordCodec arg0, RecordCodec arg1, RecordCodec arg2, @@ -218,12 +208,11 @@ public class RecordJoiners { RecordCodec arg9, Tuple.Func10 joiner ) { - return new RecordCompleteCodec<>() { + return new MapCodec<>() { @Override - public Result encode(O data, JsonLikeOperations ops) { + public Result, JsonLikeError> encode(O data, JsonLikeOperations ops) { return Stream.of(arg0.enc(data, ops), arg1.enc(data, ops), arg2.enc(data, ops), arg3.enc(data, ops), arg4.enc(data, ops), arg5.enc(data, ops), arg6.enc(data, ops), arg7.enc(data, ops), arg8.enc(data, ops), arg9.enc(data, ops)) - .reduce(Result.ok(ops.createObject()), RecordCodec::merge) - .map(RecordBuilder::complete); + .reduce(Result.ok(ops.createObject()), RecordCodec::merge); } @Override public Result decode(RecordView format, JsonLikeOperations ops) { @@ -232,7 +221,7 @@ public class RecordJoiners { } }; } - public static JsonCodec join( + public static MapCodec join( RecordCodec arg0, RecordCodec arg1, RecordCodec arg2, @@ -246,12 +235,11 @@ public class RecordJoiners { RecordCodec arg10, Tuple.Func11 joiner ) { - return new RecordCompleteCodec<>() { + return new MapCodec<>() { @Override - public Result encode(O data, JsonLikeOperations ops) { + public Result, JsonLikeError> encode(O data, JsonLikeOperations ops) { return Stream.of(arg0.enc(data, ops), arg1.enc(data, ops), arg2.enc(data, ops), arg3.enc(data, ops), arg4.enc(data, ops), arg5.enc(data, ops), arg6.enc(data, ops), arg7.enc(data, ops), arg8.enc(data, ops), arg9.enc(data, ops), arg10.enc(data, ops)) - .reduce(Result.ok(ops.createObject()), RecordCodec::merge) - .map(RecordBuilder::complete); + .reduce(Result.ok(ops.createObject()), RecordCodec::merge); } @Override public Result decode(RecordView format, JsonLikeOperations ops) { @@ -260,7 +248,7 @@ public class RecordJoiners { } }; } - public static JsonCodec join( + public static MapCodec join( RecordCodec arg0, RecordCodec arg1, RecordCodec arg2, @@ -275,12 +263,11 @@ public class RecordJoiners { RecordCodec arg11, Tuple.Func12 joiner ) { - return new RecordCompleteCodec<>() { + return new MapCodec<>() { @Override - public Result encode(O data, JsonLikeOperations ops) { + public Result, JsonLikeError> encode(O data, JsonLikeOperations ops) { return Stream.of(arg0.enc(data, ops), arg1.enc(data, ops), arg2.enc(data, ops), arg3.enc(data, ops), arg4.enc(data, ops), arg5.enc(data, ops), arg6.enc(data, ops), arg7.enc(data, ops), arg8.enc(data, ops), arg9.enc(data, ops), arg10.enc(data, ops), arg11.enc(data, ops)) - .reduce(Result.ok(ops.createObject()), RecordCodec::merge) - .map(RecordBuilder::complete); + .reduce(Result.ok(ops.createObject()), RecordCodec::merge); } @Override public Result decode(RecordView format, JsonLikeOperations ops) { @@ -289,7 +276,7 @@ public class RecordJoiners { } }; } - public static JsonCodec join( + public static MapCodec join( RecordCodec arg0, RecordCodec arg1, RecordCodec arg2, @@ -305,12 +292,11 @@ public class RecordJoiners { RecordCodec arg12, Tuple.Func13 joiner ) { - return new RecordCompleteCodec<>() { + return new MapCodec<>() { @Override - public Result encode(O data, JsonLikeOperations ops) { + public Result, JsonLikeError> encode(O data, JsonLikeOperations ops) { return Stream.of(arg0.enc(data, ops), arg1.enc(data, ops), arg2.enc(data, ops), arg3.enc(data, ops), arg4.enc(data, ops), arg5.enc(data, ops), arg6.enc(data, ops), arg7.enc(data, ops), arg8.enc(data, ops), arg9.enc(data, ops), arg10.enc(data, ops), arg11.enc(data, ops), arg12.enc(data, ops)) - .reduce(Result.ok(ops.createObject()), RecordCodec::merge) - .map(RecordBuilder::complete); + .reduce(Result.ok(ops.createObject()), RecordCodec::merge); } @Override public Result decode(RecordView format, JsonLikeOperations ops) { @@ -319,7 +305,7 @@ public class RecordJoiners { } }; } - public static JsonCodec join( + public static MapCodec join( RecordCodec arg0, RecordCodec arg1, RecordCodec arg2, @@ -336,12 +322,11 @@ public class RecordJoiners { RecordCodec arg13, Tuple.Func14 joiner ) { - return new RecordCompleteCodec<>() { + return new MapCodec<>() { @Override - public Result encode(O data, JsonLikeOperations ops) { + public Result, JsonLikeError> encode(O data, JsonLikeOperations ops) { return Stream.of(arg0.enc(data, ops), arg1.enc(data, ops), arg2.enc(data, ops), arg3.enc(data, ops), arg4.enc(data, ops), arg5.enc(data, ops), arg6.enc(data, ops), arg7.enc(data, ops), arg8.enc(data, ops), arg9.enc(data, ops), arg10.enc(data, ops), arg11.enc(data, ops), arg12.enc(data, ops), arg13.enc(data, ops)) - .reduce(Result.ok(ops.createObject()), RecordCodec::merge) - .map(RecordBuilder::complete); + .reduce(Result.ok(ops.createObject()), RecordCodec::merge); } @Override public Result decode(RecordView format, JsonLikeOperations ops) { -- cgit