Skip to content

Commit 02ecda4

Browse files
committed
xor codec
1 parent c34b776 commit 02ecda4

File tree

8 files changed

+261
-4
lines changed

8 files changed

+261
-4
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ plugins {
55

66
base.archivesName = "TinyCodecs"
77
group = "io.github.cichlidmc"
8-
version = "1.1.1"
8+
version = "1.2.0"
99

1010
repositories {
1111
mavenCentral()

src/main/java/io/github/cichlidmc/tinycodecs/Codec.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.github.cichlidmc.tinycodecs;
22

3+
import io.github.cichlidmc.tinycodecs.util.Either;
34
import io.github.cichlidmc.tinyjson.value.JsonValue;
45

56
import java.util.List;
@@ -85,6 +86,10 @@ default Codec<T> optional(T fallback) {
8586
return Codecs.optional(this, fallback);
8687
}
8788

89+
default <B> Codec<Either<T, B>> xor(Codec<B> otherCodec) {
90+
return Codecs.xor(this, otherCodec);
91+
}
92+
8893
default MapCodec<T> fieldOf(String name) {
8994
return Codecs.fieldOf(this, name);
9095
}

src/main/java/io/github/cichlidmc/tinycodecs/Codecs.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22

33
import io.github.cichlidmc.tinycodecs.codec.AlternativeCodec;
44
import io.github.cichlidmc.tinycodecs.codec.ByNameCodec;
5-
import io.github.cichlidmc.tinycodecs.codec.map.CompositeCodec;
65
import io.github.cichlidmc.tinycodecs.codec.DispatchCodec;
7-
import io.github.cichlidmc.tinycodecs.codec.map.FieldOfCodec;
86
import io.github.cichlidmc.tinycodecs.codec.ListCodec;
9-
import io.github.cichlidmc.tinycodecs.codec.map.MapCodecAsCodec;
107
import io.github.cichlidmc.tinycodecs.codec.ThrowingCodec;
8+
import io.github.cichlidmc.tinycodecs.codec.UnitCodec;
9+
import io.github.cichlidmc.tinycodecs.codec.XorCodec;
10+
import io.github.cichlidmc.tinycodecs.codec.map.CompositeCodec;
11+
import io.github.cichlidmc.tinycodecs.codec.map.FieldOfCodec;
12+
import io.github.cichlidmc.tinycodecs.codec.map.MapCodecAsCodec;
1113
import io.github.cichlidmc.tinycodecs.codec.optional.DefaultedOptionalCodec;
1214
import io.github.cichlidmc.tinycodecs.codec.optional.OptionalCodec;
15+
import io.github.cichlidmc.tinycodecs.util.Either;
1316
import io.github.cichlidmc.tinyjson.value.JsonValue;
1417
import io.github.cichlidmc.tinyjson.value.primitive.JsonBool;
1518
import io.github.cichlidmc.tinyjson.value.primitive.JsonNumber;
@@ -94,6 +97,10 @@ static <T extends Enum<T>> Codec<T> byName(Class<T> clazz) {
9497
return byName(clazz, Enum::name);
9598
}
9699

100+
static <T> Codec<T> unit(T unit) {
101+
return new UnitCodec<>(unit);
102+
}
103+
97104
// transforms for existing codecs
98105

99106
static <T> Codec<T> validate(Codec<T> codec, Predicate<T> validator) {
@@ -157,6 +164,10 @@ static <T> Codec<T> optional(Codec<T> codec, T fallback) {
157164
return optional(codec, supplier);
158165
}
159166

167+
static <L, R> Codec<Either<L, R>> xor(Codec<L> left, Codec<R> right) {
168+
return new XorCodec<>(left, right);
169+
}
170+
160171
static <T> MapCodec<T> fieldOf(Codec<T> codec, String name) {
161172
return new FieldOfCodec<>(codec, name);
162173
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.github.cichlidmc.tinycodecs.codec;
2+
3+
import io.github.cichlidmc.tinycodecs.Codec;
4+
import io.github.cichlidmc.tinycodecs.DecodeResult;
5+
import io.github.cichlidmc.tinyjson.value.JsonValue;
6+
import io.github.cichlidmc.tinyjson.value.primitive.JsonNull;
7+
8+
public final class UnitCodec<T> implements Codec<T> {
9+
private final T unit;
10+
11+
public UnitCodec(T unit) {
12+
this.unit = unit;
13+
}
14+
15+
@Override
16+
public DecodeResult<T> decode(JsonValue value) {
17+
return DecodeResult.success(this.unit);
18+
}
19+
20+
@Override
21+
public JsonValue encode(T value) {
22+
return new JsonNull();
23+
}
24+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package io.github.cichlidmc.tinycodecs.codec;
2+
3+
import io.github.cichlidmc.tinycodecs.Codec;
4+
import io.github.cichlidmc.tinycodecs.DecodeResult;
5+
import io.github.cichlidmc.tinycodecs.util.Either;
6+
import io.github.cichlidmc.tinyjson.value.JsonValue;
7+
8+
public final class XorCodec<L, R> implements Codec<Either<L, R>> {
9+
private final Codec<L> left;
10+
private final Codec<R> right;
11+
12+
public XorCodec(Codec<L> left, Codec<R> right) {
13+
this.left = left;
14+
this.right = right;
15+
}
16+
17+
@Override
18+
public DecodeResult<Either<L, R>> decode(JsonValue value) {
19+
DecodeResult<L> leftResult = this.left.decode(value);
20+
DecodeResult<R> rightResult = this.right.decode(value);
21+
22+
if (leftResult.isSuccess() && rightResult.isSuccess()) {
23+
return DecodeResult.error("Both formats were successful");
24+
} else if (leftResult.isSuccess()) {
25+
return leftResult.map(Either::left);
26+
} else if (rightResult.isSuccess()) {
27+
return rightResult.map(Either::right);
28+
}
29+
30+
// both failed, merge errors
31+
String firstMessage = leftResult.asError().message;
32+
String secondMessage = rightResult.asError().message;
33+
return DecodeResult.error("Both formats failed to decode: " + firstMessage + "; " + secondMessage);
34+
}
35+
36+
@Override
37+
public JsonValue encode(Either<L, R> value) {
38+
return Either.merge(value.map(this.left::encode, this.right::encode));
39+
}
40+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package io.github.cichlidmc.tinycodecs.util;
2+
3+
import java.util.NoSuchElementException;
4+
import java.util.function.Function;
5+
6+
public interface Either<L, R> {
7+
8+
default boolean isLeft() {
9+
return false;
10+
}
11+
12+
default L left() {
13+
throw new NoSuchElementException();
14+
}
15+
16+
default boolean isRight() {
17+
return false;
18+
}
19+
20+
default R right() {
21+
throw new NoSuchElementException();
22+
}
23+
24+
<L2, R2> Either<L2, R2> map(Function<L, L2> left, Function<R, R2> right);
25+
26+
static <L, R> Either<L, R> left(L value) {
27+
return new Left<>(value);
28+
}
29+
30+
static <L, R> Either<L, R> right(R value) {
31+
return new Right<>(value);
32+
}
33+
34+
static <T> T merge(Either<T, T> either) {
35+
return either.isLeft() ? either.left() : either.right();
36+
}
37+
38+
final class Left<L, R> implements Either<L, R> {
39+
private final L value;
40+
41+
public Left(L value) {
42+
this.value = value;
43+
}
44+
45+
@Override
46+
public boolean isLeft() {
47+
return true;
48+
}
49+
50+
@Override
51+
public L left() {
52+
return this.value;
53+
}
54+
55+
@Override
56+
public <L2, R2> Either<L2, R2> map(Function<L, L2> left, Function<R, R2> right) {
57+
return Either.left(left.apply(this.value));
58+
}
59+
}
60+
61+
final class Right<L, R> implements Either<L, R> {
62+
private final R value;
63+
64+
public Right(R value) {
65+
this.value = value;
66+
}
67+
68+
@Override
69+
public boolean isRight() {
70+
return true;
71+
}
72+
73+
@Override
74+
public R right() {
75+
return this.value;
76+
}
77+
78+
@Override
79+
public <L2, R2> Either<L2, R2> map(Function<L, L2> left, Function<R, R2> right) {
80+
return Either.right(right.apply(this.value));
81+
}
82+
}
83+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package io.github.cichlidmc.tinycodecs.test;
2+
3+
import io.github.cichlidmc.tinycodecs.Codec;
4+
import io.github.cichlidmc.tinycodecs.DecodeResult;
5+
import io.github.cichlidmc.tinycodecs.test.types.Vec3;
6+
import io.github.cichlidmc.tinycodecs.util.Either;
7+
import io.github.cichlidmc.tinyjson.value.JsonValue;
8+
import io.github.cichlidmc.tinyjson.value.composite.JsonArray;
9+
import io.github.cichlidmc.tinyjson.value.composite.JsonObject;
10+
import io.github.cichlidmc.tinyjson.value.primitive.JsonNumber;
11+
import io.github.cichlidmc.tinyjson.value.primitive.JsonString;
12+
import org.junit.jupiter.api.Test;
13+
14+
import static org.junit.jupiter.api.Assertions.assertTrue;
15+
16+
public final class XorTests {
17+
@Test
18+
public void left() {
19+
JsonValue input = new JsonString("1,2,3");
20+
DecodeResult<Vec3> result = Vec3.STRING_OR_INT_ARRAY_CODEC.decode(input);
21+
assertTrue(result.isSuccess());
22+
}
23+
24+
@Test
25+
public void right() {
26+
JsonValue input = JsonArray.of(
27+
new JsonNumber(1), new JsonNumber(2), new JsonNumber(3)
28+
);
29+
DecodeResult<Vec3> result = Vec3.STRING_OR_INT_ARRAY_CODEC.decode(input);
30+
assertTrue(result.isSuccess(), () -> result.asError().message);
31+
}
32+
33+
@Test
34+
public void neither() {
35+
JsonValue input = new JsonObject();
36+
DecodeResult<Vec3> result = Vec3.STRING_OR_INT_ARRAY_CODEC.decode(input);
37+
assertTrue(result.isError());
38+
}
39+
40+
@Test
41+
public void both() {
42+
Codec<Vec3> codec = Vec3.STRING_CODEC.xor(Vec3.ZERO_UNIT_CODEC).xmap(Either::merge, Either::left);
43+
JsonValue input = new JsonString("4,5,6");
44+
DecodeResult<Vec3> result = codec.decode(input);
45+
assertTrue(result.isError());
46+
}
47+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package io.github.cichlidmc.tinycodecs.test.types;
2+
3+
import io.github.cichlidmc.tinycodecs.Codec;
4+
import io.github.cichlidmc.tinycodecs.Codecs;
5+
import io.github.cichlidmc.tinycodecs.DecodeResult;
6+
import io.github.cichlidmc.tinycodecs.util.Either;
7+
8+
import java.util.Arrays;
9+
10+
public final class Vec3 {
11+
public static final Codec<Vec3> INT_ARRAY_CODEC = Codecs.INT.listOf().flatXmap(
12+
list -> list.size() != 3 ? DecodeResult.error("Wrong size") : DecodeResult.success(new Vec3(list.get(0), list.get(1), list.get(2))),
13+
vec -> Arrays.asList(vec.x, vec.y, vec.z)
14+
);
15+
public static final Codec<Vec3> STRING_CODEC = Codecs.STRING.flatXmap(
16+
string -> {
17+
String[] split = string.split(",");
18+
if (split.length != 3) {
19+
return DecodeResult.error("Wrong size");
20+
}
21+
22+
int[] ints = new int[3];
23+
for (int i = 0; i < split.length; i++) {
24+
try {
25+
ints[i] = Integer.parseInt(split[i]);
26+
} catch (NumberFormatException e) {
27+
return DecodeResult.error("not an int");
28+
}
29+
}
30+
31+
return DecodeResult.success(new Vec3(ints[0], ints[1], ints[2]));
32+
},
33+
vec -> vec.x + "," + vec.y + ',' + vec.z
34+
);
35+
public static final Codec<Vec3> STRING_OR_INT_ARRAY_CODEC = STRING_CODEC.xor(INT_ARRAY_CODEC).xmap(Either::merge, Either::left);
36+
public static final Codec<Vec3> ZERO_UNIT_CODEC = Codecs.unit(new Vec3(0, 0, 0));
37+
38+
public final int x;
39+
public final int y;
40+
public final int z;
41+
42+
public Vec3(int x, int y, int z) {
43+
this.x = x;
44+
this.y = y;
45+
this.z = z;
46+
}
47+
}

0 commit comments

Comments
 (0)