feat: add structural equality class because java object equality stinks
This commit is contained in:
parent
0d2d7505f8
commit
f718d9b482
@ -15,13 +15,13 @@ public class ClientObserveStream {
|
|||||||
public ClientObserveStream(Client client, Message message) {
|
public ClientObserveStream(Client client, Message message) {
|
||||||
this.state = State.OPEN;
|
this.state = State.OPEN;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.message = message.modify().option(Observe.REGISTER).build();
|
this.message = message.buildCopy().option(Observe.REGISTER).build();
|
||||||
this.buffered = Optional.of(client.send(this.message));
|
this.buffered = Optional.of(client.send(this.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Void> close() {
|
public CompletableFuture<Void> close() {
|
||||||
return this.client.send(
|
return this.client.send(
|
||||||
this.message.modify().option(Observe.DEREGISTER).unsetId().build()
|
this.message.buildCopy().option(Observe.DEREGISTER).unsetId().build()
|
||||||
)
|
)
|
||||||
.thenAccept(m -> {
|
.thenAccept(m -> {
|
||||||
this.state = State.CLOSED;
|
this.state = State.CLOSED;
|
||||||
@ -29,7 +29,7 @@ public class ClientObserveStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<Message> next() {
|
public CompletableFuture<Message> next() {
|
||||||
if (this.state == State.CLOSED) {
|
if (State.eq.test(State.CLOSED, this.state)) {
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
"ClientObserveStream.next() invoked after .close()"
|
"ClientObserveStream.next() invoked after .close()"
|
||||||
);
|
);
|
||||||
@ -47,6 +47,8 @@ public class ClientObserveStream {
|
|||||||
|
|
||||||
public static final class State {
|
public static final class State {
|
||||||
|
|
||||||
|
public static final Eq<State> eq = Eq.int_.contramap((State s) -> s.state);
|
||||||
|
|
||||||
public static final State OPEN = new State(0);
|
public static final State OPEN = new State(0);
|
||||||
public static final State CLOSED = new State(1);
|
public static final State CLOSED = new State(1);
|
||||||
|
|
||||||
@ -56,14 +58,10 @@ public class ClientObserveStream {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean equals(State other) {
|
|
||||||
return this.state == other.state;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
return switch (other) {
|
return switch (other) {
|
||||||
case State s -> this.equals(s);
|
case State s -> State.eq.test(this, s);
|
||||||
default -> false;
|
default -> false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
87
src/main/java/dev.toad/Eq.java
Normal file
87
src/main/java/dev.toad/Eq.java
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package dev.toad;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public final class Eq<T> {
|
||||||
|
|
||||||
|
public static final Eq<Short> short_ = new Eq<>((a, b) -> a == b);
|
||||||
|
public static final Eq<Integer> int_ = new Eq<>((a, b) -> a == b);
|
||||||
|
public static final Eq<Long> long_ = new Eq<>((a, b) -> a == b);
|
||||||
|
public static final Eq<String> string = new Eq<>((a, b) ->
|
||||||
|
(a != null && b != null && a.equals(b)) || (a == null && b == null)
|
||||||
|
);
|
||||||
|
public static final Eq<byte[]> byteArray = new Eq<>((a, b) ->
|
||||||
|
Arrays.equals(a, b)
|
||||||
|
);
|
||||||
|
|
||||||
|
public static final Eq<java.net.InetSocketAddress> socketAddress = new Eq<>(
|
||||||
|
(a, b) ->
|
||||||
|
a.equals(b)
|
||||||
|
);
|
||||||
|
|
||||||
|
public static <T> Eq<T> all(List<Eq<T>> eqs) {
|
||||||
|
return new Eq<>((a, b) -> eqs.stream().allMatch(eq -> eq.test(a, b)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Eq<Optional<T>> optional(Eq<T> eq) {
|
||||||
|
return new Eq<>((a, b) -> {
|
||||||
|
if (!a.isEmpty() && !b.isEmpty()) {
|
||||||
|
return eq.test(a.get(), b.get());
|
||||||
|
} else {
|
||||||
|
return a.isEmpty() && b.isEmpty();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <K, V> Eq<Map<K, V>> map(Eq<V> eq) {
|
||||||
|
return new Eq<>((a, b) -> {
|
||||||
|
if (a.entrySet().size() != b.entrySet().size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var ent : a.entrySet()) {
|
||||||
|
var val = b.get(ent.getKey());
|
||||||
|
if (val == null || !eq.test(val, ent.getValue())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Eq<List<T>> list(Eq<T> eq) {
|
||||||
|
return new Eq<>((a, b) -> {
|
||||||
|
if (a.size() != b.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < a.size(); i++) {
|
||||||
|
if (!eq.test(a.get(i), b.get(i))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final BiFunction<T, T, Boolean> eq;
|
||||||
|
|
||||||
|
public Eq(BiFunction<T, T, Boolean> eq) {
|
||||||
|
this.eq = eq;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean test(T a, T b) {
|
||||||
|
return this.eq.apply(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <U> Eq<U> contramap(Function<U, T> from) {
|
||||||
|
return new Eq<>((a, b) -> this.test(from.apply(a), from.apply(b)));
|
||||||
|
}
|
||||||
|
}
|
@ -110,9 +110,7 @@ public final class Server {
|
|||||||
CompletableFuture.completedFuture(Optional.empty());
|
CompletableFuture.completedFuture(Optional.empty());
|
||||||
|
|
||||||
public static final Function<Message, Result> notFound = m -> {
|
public static final Function<Message, Result> notFound = m -> {
|
||||||
return Middleware.respond(
|
return Middleware.respond(m.buildResponse().code(Code.NOT_FOUND).build());
|
||||||
m.modify().unsetId().code(Code.NOT_FOUND).build()
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static final BiFunction<Message, Throwable, Result> debugExceptionHandler =
|
public static final BiFunction<Message, Throwable, Result> debugExceptionHandler =
|
||||||
@ -126,8 +124,7 @@ public final class Server {
|
|||||||
);
|
);
|
||||||
|
|
||||||
var rep = m
|
var rep = m
|
||||||
.modify()
|
.buildResponse()
|
||||||
.unsetId()
|
|
||||||
.code(Code.INTERNAL_SERVER_ERROR)
|
.code(Code.INTERNAL_SERVER_ERROR)
|
||||||
.payload(Payload.text(e.toString()))
|
.payload(Payload.text(e.toString()))
|
||||||
.build();
|
.build();
|
||||||
@ -144,7 +141,7 @@ public final class Server {
|
|||||||
String.format("while handling %s", m.toDebugString()),
|
String.format("while handling %s", m.toDebugString()),
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
var rep = m.modify().unsetId().code(Code.INTERNAL_SERVER_ERROR).build();
|
var rep = m.buildResponse().code(Code.INTERNAL_SERVER_ERROR).build();
|
||||||
return Middleware.respond(rep);
|
return Middleware.respond(rep);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -358,8 +355,11 @@ public final class Server {
|
|||||||
public Builder put(String path, Function<Message, Middleware.Result> f) {
|
public Builder put(String path, Function<Message, Middleware.Result> f) {
|
||||||
return this.when(
|
return this.when(
|
||||||
m ->
|
m ->
|
||||||
m.code().equals(Code.PUT) &&
|
Code.eq.test(m.code(), Code.PUT) &&
|
||||||
m.getPath().map(p -> p.matches(path)).orElse(path == ""),
|
m
|
||||||
|
.getPath()
|
||||||
|
.map(p -> p.matches(path))
|
||||||
|
.orElse(path == null || path.isEmpty()),
|
||||||
f
|
f
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -367,8 +367,11 @@ public final class Server {
|
|||||||
public Builder post(String path, Function<Message, Middleware.Result> f) {
|
public Builder post(String path, Function<Message, Middleware.Result> f) {
|
||||||
return this.when(
|
return this.when(
|
||||||
m ->
|
m ->
|
||||||
m.code().equals(Code.POST) &&
|
Code.eq.test(m.code(), Code.POST) &&
|
||||||
m.getPath().map(p -> p.matches(path)).orElse(path == ""),
|
m
|
||||||
|
.getPath()
|
||||||
|
.map(p -> p.matches(path))
|
||||||
|
.orElse(path == null || path.isEmpty()),
|
||||||
f
|
f
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -376,8 +379,11 @@ public final class Server {
|
|||||||
public Builder delete(String path, Function<Message, Middleware.Result> f) {
|
public Builder delete(String path, Function<Message, Middleware.Result> f) {
|
||||||
return this.when(
|
return this.when(
|
||||||
m ->
|
m ->
|
||||||
m.code().equals(Code.DELETE) &&
|
Code.eq.test(m.code(), Code.DELETE) &&
|
||||||
m.getPath().map(p -> p.matches(path)).orElse(path == ""),
|
m
|
||||||
|
.getPath()
|
||||||
|
.map(p -> p.matches(path))
|
||||||
|
.orElse(path == null || path.isEmpty()),
|
||||||
f
|
f
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -385,7 +391,7 @@ public final class Server {
|
|||||||
public Builder get(String path, Function<Message, Middleware.Result> f) {
|
public Builder get(String path, Function<Message, Middleware.Result> f) {
|
||||||
return this.when(
|
return this.when(
|
||||||
m ->
|
m ->
|
||||||
m.code().equals(Code.GET) &&
|
Code.eq.test(m.code(), Code.GET) &&
|
||||||
m
|
m
|
||||||
.getPath()
|
.getPath()
|
||||||
.map(p -> p.matches(path))
|
.map(p -> p.matches(path))
|
||||||
|
@ -208,7 +208,8 @@ public final class Toad implements AutoCloseable {
|
|||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
return switch (other) {
|
return switch (other) {
|
||||||
case Config o -> o.concurrency == this.concurrency && o.msg == this.msg;
|
case Config o -> o.concurrency.equals(this.concurrency) &&
|
||||||
|
o.msg.equals(this.msg);
|
||||||
default -> false;
|
default -> false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -258,11 +259,11 @@ public final class Toad implements AutoCloseable {
|
|||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
return switch (other) {
|
return switch (other) {
|
||||||
case Msg o -> o.tokenSeed == this.tokenSeed &&
|
case Msg o -> o.tokenSeed.equals(this.tokenSeed) &&
|
||||||
o.probingRateBytesPerSecond == this.probingRateBytesPerSecond &&
|
o.probingRateBytesPerSecond.equals(this.probingRateBytesPerSecond) &&
|
||||||
o.multicastResponseLeisure == this.multicastResponseLeisure &&
|
o.multicastResponseLeisure.equals(this.multicastResponseLeisure) &&
|
||||||
o.con == this.con &&
|
o.con.equals(this.con) &&
|
||||||
o.non == this.non;
|
o.non.equals(this.non);
|
||||||
default -> false;
|
default -> false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -362,9 +363,11 @@ public final class Toad implements AutoCloseable {
|
|||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
return switch (other) {
|
return switch (other) {
|
||||||
case Con o -> this.ackedRetryStrategy == o.ackedRetryStrategy &&
|
case Con o -> this.ackedRetryStrategy.equals(
|
||||||
this.unackedRetryStrategy == o.unackedRetryStrategy &&
|
o.ackedRetryStrategy
|
||||||
this.maxAttempts == o.maxAttempts;
|
) &&
|
||||||
|
this.unackedRetryStrategy.equals(o.unackedRetryStrategy) &&
|
||||||
|
this.maxAttempts.equals(o.maxAttempts);
|
||||||
default -> false;
|
default -> false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -435,8 +438,8 @@ public final class Toad implements AutoCloseable {
|
|||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
return switch (other) {
|
return switch (other) {
|
||||||
case Non o -> this.retryStrategy == o.retryStrategy &&
|
case Non o -> this.retryStrategy.equals(o.retryStrategy) &&
|
||||||
this.maxAttempts == o.maxAttempts;
|
this.maxAttempts.equals(o.maxAttempts);
|
||||||
default -> false;
|
default -> false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,22 @@
|
|||||||
package dev.toad.msg;
|
package dev.toad.msg;
|
||||||
|
|
||||||
import dev.toad.Debug;
|
import dev.toad.Debug;
|
||||||
|
import dev.toad.Eq;
|
||||||
import dev.toad.ffi.u8;
|
import dev.toad.ffi.u8;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public final class Code implements Debug {
|
public final class Code implements Debug {
|
||||||
|
|
||||||
final u8 clazz;
|
final u8 clazz;
|
||||||
final u8 detail;
|
final u8 detail;
|
||||||
|
|
||||||
|
public static Eq<Code> eq = Eq.all(
|
||||||
|
List.of(
|
||||||
|
Eq.short_.contramap(Code::codeClass),
|
||||||
|
Eq.short_.contramap(Code::codeDetail)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
public static final Code EMPTY = new Code(0, 0);
|
public static final Code EMPTY = new Code(0, 0);
|
||||||
|
|
||||||
public static final Code GET = new Code(0, 1);
|
public static final Code GET = new Code(0, 1);
|
||||||
@ -112,17 +121,10 @@ public final class Code implements Debug {
|
|||||||
return this.toString();
|
return this.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean equals(Code other) {
|
|
||||||
return (
|
|
||||||
this.codeClass() == other.codeClass() &&
|
|
||||||
this.codeDetail() == other.codeDetail()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
return switch (other) {
|
return switch (other) {
|
||||||
case Code c -> c.equals(this);
|
case Code c -> Code.eq.test(c, this);
|
||||||
default -> false;
|
default -> false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
package dev.toad.msg;
|
package dev.toad.msg;
|
||||||
|
|
||||||
import dev.toad.Debug;
|
import dev.toad.Debug;
|
||||||
|
import dev.toad.Eq;
|
||||||
import dev.toad.ffi.u16;
|
import dev.toad.ffi.u16;
|
||||||
|
|
||||||
public final class Id implements Debug {
|
public final class Id implements Debug {
|
||||||
|
|
||||||
public static native Id defaultId();
|
public static native Id defaultId();
|
||||||
|
|
||||||
|
public static final Eq<Id> eq = Eq.int_.contramap(Id::toInt);
|
||||||
|
|
||||||
final u16 id;
|
final u16 id;
|
||||||
|
|
||||||
public Id(int id) {
|
public Id(int id) {
|
||||||
@ -17,6 +20,14 @@ public final class Id implements Debug {
|
|||||||
return this.id.intValue();
|
return this.id.intValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
return switch (other) {
|
||||||
|
case Id i -> Id.eq.test(this, i);
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toDebugString() {
|
public String toDebugString() {
|
||||||
return String.format("Id(%d)", this.toInt());
|
return String.format("Id(%d)", this.toInt());
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package dev.toad.msg;
|
package dev.toad.msg;
|
||||||
|
|
||||||
import dev.toad.Debug;
|
import dev.toad.Debug;
|
||||||
|
import dev.toad.Eq;
|
||||||
import dev.toad.msg.option.Accept;
|
import dev.toad.msg.option.Accept;
|
||||||
import dev.toad.msg.option.ContentFormat;
|
import dev.toad.msg.option.ContentFormat;
|
||||||
import dev.toad.msg.option.Host;
|
import dev.toad.msg.option.Host;
|
||||||
@ -35,8 +36,12 @@ public interface Message extends Debug {
|
|||||||
|
|
||||||
public byte[] toBytes();
|
public byte[] toBytes();
|
||||||
|
|
||||||
public default dev.toad.msg.build.Message modify() {
|
public default dev.toad.msg.build.Message buildCopy() {
|
||||||
return dev.toad.msg.build.Message.from(this);
|
return dev.toad.msg.build.Message.copyOf(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public default dev.toad.msg.build.MessageNeeds.Code buildResponse() {
|
||||||
|
return dev.toad.msg.build.Message.respondTo(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public default Optional<Option> getOption(long number) {
|
public default Optional<Option> getOption(long number) {
|
||||||
@ -91,17 +96,24 @@ public interface Message extends Debug {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public default boolean equals(Message o) {
|
public static Eq<Message> eq() {
|
||||||
return (
|
return Eq.all(
|
||||||
this.addr().equals(o.addr()) &&
|
List.of(
|
||||||
this.options().equals(o.options()) &&
|
Eq.optional(Eq.socketAddress).contramap(m -> m.addr()),
|
||||||
this.id().equals(o.id()) &&
|
Eq.list(Option.eq()).contramap(m -> m.options()),
|
||||||
this.token().equals(o.token()) &&
|
Code.eq.contramap(m -> m.code()),
|
||||||
this.type().equals(o.type()) &&
|
Id.eq.contramap(m -> m.id()),
|
||||||
this.payload().equals(o.payload())
|
Token.eq.contramap(m -> m.token()),
|
||||||
|
Type.eq.contramap(m -> m.type()),
|
||||||
|
Payload.eq.contramap(m -> m.payload())
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public default boolean equals(Message m) {
|
||||||
|
return Message.eq().test(this, m);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public default String toDebugString() {
|
public default String toDebugString() {
|
||||||
return (
|
return (
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package dev.toad.msg;
|
package dev.toad.msg;
|
||||||
|
|
||||||
import dev.toad.Debug;
|
import dev.toad.Debug;
|
||||||
|
import dev.toad.Eq;
|
||||||
import dev.toad.msg.option.*;
|
import dev.toad.msg.option.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -10,8 +11,17 @@ public interface Option extends Debug {
|
|||||||
|
|
||||||
public List<OptionValue> values();
|
public List<OptionValue> values();
|
||||||
|
|
||||||
|
public static Eq<Option> eq() {
|
||||||
|
return Eq.all(
|
||||||
|
List.of(
|
||||||
|
Eq.long_.contramap(o -> o.number()),
|
||||||
|
Eq.list(OptionValue.eq()).contramap(Option::values)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public default boolean equals(Option o) {
|
public default boolean equals(Option o) {
|
||||||
return this.number() == o.number() && this.values().equals(o.values());
|
return Option.eq().test(this, o);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package dev.toad.msg;
|
package dev.toad.msg;
|
||||||
|
|
||||||
import dev.toad.Debug;
|
import dev.toad.Debug;
|
||||||
|
import dev.toad.Eq;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -11,6 +12,10 @@ public interface OptionValue extends Debug {
|
|||||||
|
|
||||||
public dev.toad.msg.owned.OptionValue toOwned();
|
public dev.toad.msg.owned.OptionValue toOwned();
|
||||||
|
|
||||||
|
public static Eq<OptionValue> eq() {
|
||||||
|
return Eq.byteArray.contramap(OptionValue::asBytes);
|
||||||
|
}
|
||||||
|
|
||||||
public default boolean equals(OptionValue o) {
|
public default boolean equals(OptionValue o) {
|
||||||
return this.asBytes().equals(o.asBytes());
|
return this.asBytes().equals(o.asBytes());
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package dev.toad.msg;
|
package dev.toad.msg;
|
||||||
|
|
||||||
import dev.toad.Debug;
|
import dev.toad.Debug;
|
||||||
|
import dev.toad.Eq;
|
||||||
import dev.toad.msg.option.ContentFormat;
|
import dev.toad.msg.option.ContentFormat;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public final class Payload implements Debug {
|
public final class Payload implements Debug {
|
||||||
@ -11,6 +14,19 @@ public final class Payload implements Debug {
|
|||||||
final byte[] bytes;
|
final byte[] bytes;
|
||||||
final Optional<ContentFormat> contentFormat;
|
final Optional<ContentFormat> contentFormat;
|
||||||
|
|
||||||
|
public static final Eq<Payload> eq = Eq.all(
|
||||||
|
List.of(
|
||||||
|
Eq.optional(ContentFormat.eq).contramap(Payload::contentFormat),
|
||||||
|
Eq.byteArray.contramap(Payload::bytes)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
public static boolean equals(Payload a, Payload b) {
|
||||||
|
return (
|
||||||
|
Arrays.equals(a.bytes, b.bytes) && a.contentFormat.equals(b.contentFormat)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public Payload() {
|
public Payload() {
|
||||||
this.contentFormat = Optional.empty();
|
this.contentFormat = Optional.empty();
|
||||||
this.bytes = new byte[] {};
|
this.bytes = new byte[] {};
|
||||||
@ -63,6 +79,14 @@ public final class Payload implements Debug {
|
|||||||
return new Payload(ContentFormat.OCTET_STREAM, bytes);
|
return new Payload(ContentFormat.OCTET_STREAM, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
return switch (other) {
|
||||||
|
case Payload p -> Payload.eq.test(this, p);
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toDebugString() {
|
public String toDebugString() {
|
||||||
if (this.contentFormat.map(ContentFormat::isUtf8Text).orElse(false)) {
|
if (this.contentFormat.map(ContentFormat::isUtf8Text).orElse(false)) {
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
package dev.toad.msg;
|
package dev.toad.msg;
|
||||||
|
|
||||||
import dev.toad.Debug;
|
import dev.toad.Debug;
|
||||||
|
import dev.toad.Eq;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
public final class Token implements Debug {
|
public final class Token implements Debug {
|
||||||
|
|
||||||
public static native Token defaultToken();
|
public static native Token defaultToken();
|
||||||
|
|
||||||
|
public static final Eq<Token> eq = Eq.byteArray.contramap(Token::toBytes);
|
||||||
|
|
||||||
final byte[] bytes;
|
final byte[] bytes;
|
||||||
|
|
||||||
public Token(byte[] bytes) {
|
public Token(byte[] bytes) {
|
||||||
@ -30,7 +34,7 @@ public final class Token implements Debug {
|
|||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
return switch (other) {
|
return switch (other) {
|
||||||
case Token t -> t.bytes.equals(this.bytes);
|
case Token t -> Token.eq.test(this, t);
|
||||||
default -> false;
|
default -> false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package dev.toad.msg;
|
package dev.toad.msg;
|
||||||
|
|
||||||
import dev.toad.Debug;
|
import dev.toad.Debug;
|
||||||
|
import dev.toad.Eq;
|
||||||
|
|
||||||
public enum Type implements Debug {
|
public enum Type implements Debug {
|
||||||
CON(1),
|
CON(1),
|
||||||
@ -8,6 +9,8 @@ public enum Type implements Debug {
|
|||||||
ACK(3),
|
ACK(3),
|
||||||
RESET(4);
|
RESET(4);
|
||||||
|
|
||||||
|
public static final Eq<Type> eq = new Eq<>((a, b) -> a == b);
|
||||||
|
|
||||||
private Type(int val) {}
|
private Type(int val) {}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
@ -35,21 +35,39 @@ public final class Message
|
|||||||
|
|
||||||
Message() {}
|
Message() {}
|
||||||
|
|
||||||
public static Message from(dev.toad.msg.Message other) {
|
public static MessageNeeds.Code respondTo(dev.toad.msg.Message other) {
|
||||||
|
return Message.respondTo(other, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MessageNeeds.Code respondTo(
|
||||||
|
dev.toad.msg.Message other,
|
||||||
|
boolean conResponseToNonRequest
|
||||||
|
) {
|
||||||
|
// prettier-ignore
|
||||||
|
Type type = Type.eq.test(other.type(), Type.CON) ? Type.ACK
|
||||||
|
: conResponseToNonRequest ? Type.CON
|
||||||
|
: Type.NON;
|
||||||
|
|
||||||
|
return Message.copyOf(other).unsetId().type(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Message copyOf(dev.toad.msg.Message other) {
|
||||||
var builder = new Message();
|
var builder = new Message();
|
||||||
|
|
||||||
Function<dev.toad.msg.Option, Long> key = o -> o.number();
|
Function<dev.toad.msg.Option, Long> key;
|
||||||
Function<dev.toad.msg.Option, ArrayList<dev.toad.msg.owned.OptionValue>> value =
|
Function<dev.toad.msg.Option, ArrayList<dev.toad.msg.owned.OptionValue>> value;
|
||||||
|
BinaryOperator<ArrayList<dev.toad.msg.owned.OptionValue>> merge;
|
||||||
|
|
||||||
|
key = o -> o.number();
|
||||||
|
value =
|
||||||
o ->
|
o ->
|
||||||
o
|
o
|
||||||
.values()
|
.values()
|
||||||
.stream()
|
.stream()
|
||||||
.map(v -> v.toOwned())
|
.map(dev.toad.msg.OptionValue::toOwned)
|
||||||
.collect(Collectors.toCollection(ArrayList::new));
|
.collect(Collectors.toCollection(ArrayList::new));
|
||||||
BinaryOperator<ArrayList<dev.toad.msg.owned.OptionValue>> merge = (
|
merge =
|
||||||
a,
|
(a, b) -> {
|
||||||
b
|
|
||||||
) -> {
|
|
||||||
a.addAll(b);
|
a.addAll(b);
|
||||||
return a;
|
return a;
|
||||||
};
|
};
|
||||||
@ -58,7 +76,7 @@ public final class Message
|
|||||||
other
|
other
|
||||||
.options()
|
.options()
|
||||||
.stream()
|
.stream()
|
||||||
.collect(Collectors.toMap(key, value, merge, () -> new HashMap<>()));
|
.collect(Collectors.toMap(key, value, merge, HashMap::new));
|
||||||
builder.id = Optional.of(other.id());
|
builder.id = Optional.of(other.id());
|
||||||
builder.code = Optional.of(other.code());
|
builder.code = Optional.of(other.code());
|
||||||
builder.token = Optional.of(other.token());
|
builder.token = Optional.of(other.token());
|
||||||
@ -73,15 +91,17 @@ public final class Message
|
|||||||
return new Message();
|
return new Message();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageNeeds.Type uri(String uriStr)
|
public Message uri(String uriStr)
|
||||||
throws URISyntaxException, UnknownHostException {
|
throws URISyntaxException, UnknownHostException {
|
||||||
var uri = new URI(uriStr);
|
var uri = new URI(uriStr);
|
||||||
var addr = InetAddress.getByName(uri.getHost());
|
var addr = InetAddress.getByName(uri.getHost());
|
||||||
var port = uri.getPort() > 0
|
var secure = uri.getScheme() != null && uri.getScheme().equals("coaps");
|
||||||
? uri.getPort()
|
|
||||||
: uri.getScheme() != null && uri.getScheme().equals("coaps")
|
// prettier-ignore
|
||||||
? 5684
|
var port = uri.getPort() > 0 ? uri.getPort()
|
||||||
|
: secure ? 5684
|
||||||
: 5683;
|
: 5683;
|
||||||
|
|
||||||
this.addr = Optional.of(new InetSocketAddress(addr, port));
|
this.addr = Optional.of(new InetSocketAddress(addr, port));
|
||||||
|
|
||||||
this.option(new Host(addr.getHostAddress()));
|
this.option(new Host(addr.getHostAddress()));
|
||||||
@ -97,12 +117,12 @@ public final class Message
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageNeeds.Type addr(InetSocketAddress addr) {
|
public Message addr(InetSocketAddress addr) {
|
||||||
this.addr = Optional.of(addr);
|
this.addr = Optional.of(addr);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageNeeds.Code type(Type type) {
|
public Message type(Type type) {
|
||||||
this.type = Optional.of(type);
|
this.type = Optional.of(type);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package dev.toad.msg.option;
|
package dev.toad.msg.option;
|
||||||
|
|
||||||
|
import dev.toad.Eq;
|
||||||
import dev.toad.ffi.u16;
|
import dev.toad.ffi.u16;
|
||||||
import dev.toad.msg.Option;
|
import dev.toad.msg.Option;
|
||||||
import dev.toad.msg.OptionValue;
|
import dev.toad.msg.OptionValue;
|
||||||
@ -11,6 +12,7 @@ import java.util.stream.Collectors;
|
|||||||
public final class Accept extends ContentFormat implements Option {
|
public final class Accept extends ContentFormat implements Option {
|
||||||
|
|
||||||
public static final long number = 17;
|
public static final long number = 17;
|
||||||
|
public static final Eq<Accept> eq = Eq.int_.contramap(Accept::value);
|
||||||
|
|
||||||
public static final Accept TEXT = new Accept(ContentFormat.TEXT);
|
public static final Accept TEXT = new Accept(ContentFormat.TEXT);
|
||||||
public static final Accept LINK_FORMAT = new Accept(
|
public static final Accept LINK_FORMAT = new Accept(
|
||||||
@ -46,7 +48,7 @@ public final class Accept extends ContentFormat implements Option {
|
|||||||
o
|
o
|
||||||
.values()
|
.values()
|
||||||
.stream()
|
.stream()
|
||||||
.map(v -> v.asString())
|
.map(OptionValue::asString)
|
||||||
.collect(Collectors.toList())
|
.collect(Collectors.toList())
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package dev.toad.msg.option;
|
package dev.toad.msg.option;
|
||||||
|
|
||||||
|
import dev.toad.Eq;
|
||||||
import dev.toad.ffi.u16;
|
import dev.toad.ffi.u16;
|
||||||
import dev.toad.msg.Option;
|
import dev.toad.msg.Option;
|
||||||
import dev.toad.msg.OptionValue;
|
import dev.toad.msg.OptionValue;
|
||||||
@ -13,6 +14,9 @@ public sealed class ContentFormat implements Option permits Accept {
|
|||||||
|
|
||||||
protected u16 value;
|
protected u16 value;
|
||||||
|
|
||||||
|
public static final Eq<ContentFormat> eq = Eq.int_.contramap(
|
||||||
|
ContentFormat::value
|
||||||
|
);
|
||||||
public static final long number = 12;
|
public static final long number = 12;
|
||||||
|
|
||||||
public ContentFormat(Option o) {
|
public ContentFormat(Option o) {
|
||||||
@ -76,48 +80,37 @@ public sealed class ContentFormat implements Option permits Accept {
|
|||||||
|
|
||||||
public boolean isUtf8Text() {
|
public boolean isUtf8Text() {
|
||||||
return (
|
return (
|
||||||
this.value() == ContentFormat.TEXT.value() ||
|
this.equals(ContentFormat.TEXT) ||
|
||||||
this.value() == ContentFormat.CSS.value() ||
|
this.equals(ContentFormat.CSS) ||
|
||||||
this.value() == ContentFormat.JSON.value() ||
|
this.equals(ContentFormat.JSON) ||
|
||||||
this.value() == ContentFormat.XML.value() ||
|
this.equals(ContentFormat.XML) ||
|
||||||
this.value() == ContentFormat.JAVASCRIPT.value() ||
|
this.equals(ContentFormat.JAVASCRIPT) ||
|
||||||
this.value() == ContentFormat.LINK_FORMAT.value() ||
|
this.equals(ContentFormat.LINK_FORMAT) ||
|
||||||
this.value() == ContentFormat.IMAGE_SVG.value()
|
this.equals(ContentFormat.IMAGE_SVG)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toMimeType() {
|
public String toMimeType() {
|
||||||
// https://www.iana.org/assignments/core-parameters/core-parameters.xhtml#content-formats
|
// https://www.iana.org/assignments/core-parameters/core-parameters.xhtml#content-formats
|
||||||
return this.value() == ContentFormat.TEXT.value()
|
// prettier-ignore
|
||||||
? "text/plain; charset=utf-8"
|
return this.equals(ContentFormat.TEXT) ? "text/plain; charset=utf-8"
|
||||||
: this.value() == ContentFormat.CSS.value()
|
: this.equals(ContentFormat.CSS) ? "text/css"
|
||||||
? "text/css"
|
: this.equals(ContentFormat.JSON) ? "application/json"
|
||||||
: this.value() == ContentFormat.JSON.value()
|
: this.equals(ContentFormat.XML) ? "application/xml"
|
||||||
? "application/json"
|
: this.equals(ContentFormat.EXI) ? "application/exi"
|
||||||
: this.value() == ContentFormat.XML.value()
|
: this.equals(ContentFormat.CBOR) ? "application/cbor"
|
||||||
? "application/xml"
|
: this.equals(ContentFormat.JAVASCRIPT) ? "application/javascript"
|
||||||
: this.value() == ContentFormat.EXI.value()
|
: this.equals(ContentFormat.OCTET_STREAM) ? "application/octet-stream"
|
||||||
? "application/exi"
|
: this.equals(ContentFormat.LINK_FORMAT) ? "application/link-format"
|
||||||
: this.value() == ContentFormat.CBOR.value()
|
: this.equals(ContentFormat.IMAGE_GIF) ? "image/gif"
|
||||||
? "application/cbor"
|
: this.equals(ContentFormat.IMAGE_JPG) ? "image/jpeg"
|
||||||
: this.value() == ContentFormat.JAVASCRIPT.value()
|
: this.equals(ContentFormat.IMAGE_PNG) ? "image/png"
|
||||||
? "application/javascript"
|
: this.equals(ContentFormat.IMAGE_SVG) ? "image/svg+xml"
|
||||||
: this.value() == ContentFormat.OCTET_STREAM.value()
|
: String.format("ContentFormat(%d)", this.value());
|
||||||
? "application/octet-stream"
|
}
|
||||||
: this.value() == ContentFormat.LINK_FORMAT.value()
|
|
||||||
? "application/link-format"
|
public boolean equals(ContentFormat cf) {
|
||||||
: this.value() == ContentFormat.IMAGE_GIF.value()
|
return ContentFormat.eq.test(this, cf);
|
||||||
? "image/gif"
|
|
||||||
: this.value() == ContentFormat.IMAGE_JPG.value()
|
|
||||||
? "image/jpeg"
|
|
||||||
: this.value() == ContentFormat.IMAGE_PNG.value()
|
|
||||||
? "image/png"
|
|
||||||
: this.value() == ContentFormat.IMAGE_SVG.value()
|
|
||||||
? "image/svg+xml"
|
|
||||||
: String.format(
|
|
||||||
"ContentFormat(%d)",
|
|
||||||
this.value()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -128,10 +121,6 @@ public sealed class ContentFormat implements Option permits Accept {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean equals(ContentFormat other) {
|
|
||||||
return this.value() == other.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("ContentFormat(%d)", this.value());
|
return String.format("ContentFormat(%d)", this.value());
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package dev.toad.msg.option;
|
package dev.toad.msg.option;
|
||||||
|
|
||||||
|
import dev.toad.Eq;
|
||||||
import dev.toad.msg.Option;
|
import dev.toad.msg.Option;
|
||||||
import dev.toad.msg.OptionValue;
|
import dev.toad.msg.OptionValue;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -12,6 +13,7 @@ public final class Host implements Option {
|
|||||||
final String host;
|
final String host;
|
||||||
|
|
||||||
public static final long number = 3;
|
public static final long number = 3;
|
||||||
|
public static final Eq<Host> eq = Eq.string.contramap(Host::toString);
|
||||||
|
|
||||||
public Host(Option o) {
|
public Host(Option o) {
|
||||||
if (o.number() != Host.number) {
|
if (o.number() != Host.number) {
|
||||||
@ -58,7 +60,7 @@ public final class Host implements Option {
|
|||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
return switch (other) {
|
return switch (other) {
|
||||||
case Host h -> this.toString().equals(h.toString());
|
case Host h -> Host.eq.test(this, h);
|
||||||
default -> false;
|
default -> false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package dev.toad.msg.option;
|
package dev.toad.msg.option;
|
||||||
|
|
||||||
|
import dev.toad.Eq;
|
||||||
import dev.toad.msg.Option;
|
import dev.toad.msg.Option;
|
||||||
import dev.toad.msg.OptionValue;
|
import dev.toad.msg.OptionValue;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -12,6 +13,9 @@ public final class Path implements Option {
|
|||||||
final ArrayList<String> segments;
|
final ArrayList<String> segments;
|
||||||
|
|
||||||
public static final long number = 11;
|
public static final long number = 11;
|
||||||
|
public static final Eq<Path> eq = Eq
|
||||||
|
.list(Eq.string)
|
||||||
|
.contramap(Path::segments);
|
||||||
|
|
||||||
public Path(Option o) {
|
public Path(Option o) {
|
||||||
if (o.number() != Path.number) {
|
if (o.number() != Path.number) {
|
||||||
@ -40,10 +44,6 @@ public final class Path implements Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean equals(Path other) {
|
|
||||||
return this.segments == other.segments;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> segments() {
|
public List<String> segments() {
|
||||||
return this.segments;
|
return this.segments;
|
||||||
}
|
}
|
||||||
@ -73,4 +73,12 @@ public final class Path implements Option {
|
|||||||
.map(s -> new dev.toad.msg.owned.OptionValue(s))
|
.map(s -> new dev.toad.msg.owned.OptionValue(s))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
return switch (other) {
|
||||||
|
case Path p -> Path.eq.test(this, p);
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package dev.toad.msg.option;
|
package dev.toad.msg.option;
|
||||||
|
|
||||||
|
import dev.toad.Eq;
|
||||||
import dev.toad.msg.Option;
|
import dev.toad.msg.Option;
|
||||||
import dev.toad.msg.OptionValue;
|
import dev.toad.msg.OptionValue;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -17,6 +18,9 @@ public final class Query implements Option {
|
|||||||
|
|
||||||
final ArrayList<String> query;
|
final ArrayList<String> query;
|
||||||
public static final long number = 15;
|
public static final long number = 15;
|
||||||
|
public static final Eq<Query> eq = Eq
|
||||||
|
.<String, List<Value>>map(Eq.list(Value.eq))
|
||||||
|
.contramap(Query::toMap);
|
||||||
|
|
||||||
public Query(Option o) {
|
public Query(Option o) {
|
||||||
if (o.number() != Query.number) {
|
if (o.number() != Query.number) {
|
||||||
@ -90,12 +94,24 @@ public final class Query implements Option {
|
|||||||
return String.format("Uri-Query: %s", this.toString());
|
return String.format("Uri-Query: %s", this.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
return switch (other) {
|
||||||
|
case Query q -> Query.eq.test(this, q);
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static final class Value {
|
public static final class Value {
|
||||||
|
|
||||||
|
public static final Eq<Value> eq = Eq
|
||||||
|
.optional(Eq.string)
|
||||||
|
.contramap(Value::value);
|
||||||
|
|
||||||
final Optional<String> val;
|
final Optional<String> val;
|
||||||
|
|
||||||
public Value(String val) {
|
public Value(String val) {
|
||||||
if (val == null || val == "") {
|
if (val == null || val.isEmpty()) {
|
||||||
this.val = Optional.empty();
|
this.val = Optional.empty();
|
||||||
} else {
|
} else {
|
||||||
this.val = Optional.of(val);
|
this.val = Optional.of(val);
|
||||||
@ -119,14 +135,10 @@ public final class Query implements Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean equals(Value other) {
|
|
||||||
return this.toString().equals(other.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
return switch (other) {
|
return switch (other) {
|
||||||
case Value v -> this.equals(v);
|
case Value v -> Value.eq.test(this, v);
|
||||||
default -> false;
|
default -> false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -14,36 +14,29 @@ import java.util.concurrent.TimeUnit
|
|||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
|
||||||
class E2E extends munit.FunSuite {
|
class E2E extends munit.FunSuite {
|
||||||
test("minimal client and server") {
|
test("little baby server") {
|
||||||
Toad.loadNativeLib()
|
Toad.loadNativeLib()
|
||||||
|
|
||||||
val serverThread = Thread((() => {
|
val server = Toad.builder
|
||||||
Toad.builder
|
|
||||||
.port(10102)
|
.port(10102)
|
||||||
.logLevel(Level.INFO)
|
.logLevel(Level.INFO)
|
||||||
.server
|
.server
|
||||||
.post(
|
.post("exit", _msg => Server.Middleware.exit)
|
||||||
"exit",
|
|
||||||
_msg => {
|
|
||||||
Server.Middleware.exit
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.get(
|
.get(
|
||||||
"hello",
|
"hello",
|
||||||
msg => {
|
msg => {
|
||||||
val name = msg.payload.toString
|
val rep = msg.buildResponse
|
||||||
val rep = msg.modify.unsetId
|
|
||||||
.`type`(Type.NON)
|
|
||||||
.code(Code.OK_CONTENT)
|
.code(Code.OK_CONTENT)
|
||||||
.payload(Payload.text(s"Hello, $name!"))
|
.`type`(Type.NON)
|
||||||
|
.payload(Payload.text(s"Hello, ${msg.payload.toString}!"))
|
||||||
.build
|
.build
|
||||||
|
|
||||||
Server.Middleware.respond(rep)
|
Server.Middleware.respond(rep)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.build
|
.build;
|
||||||
.run
|
|
||||||
}): java.lang.Runnable)
|
|
||||||
|
|
||||||
|
val serverThread = Thread((() => server.run()): java.lang.Runnable)
|
||||||
serverThread.start()
|
serverThread.start()
|
||||||
|
|
||||||
val req = Message.builder
|
val req = Message.builder
|
||||||
@ -68,6 +61,6 @@ class E2E extends munit.FunSuite {
|
|||||||
client.sendNoResponse(exit)
|
client.sendNoResponse(exit)
|
||||||
}
|
}
|
||||||
|
|
||||||
serverThread.join
|
serverThread.join()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
126
src/test/scala/Eq.scala
Normal file
126
src/test/scala/Eq.scala
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import scala.jdk.CollectionConverters.*
|
||||||
|
import dev.toad.*
|
||||||
|
import dev.toad.msg.*
|
||||||
|
import dev.toad.msg.option.*
|
||||||
|
|
||||||
|
class Eq extends munit.FunSuite {
|
||||||
|
test("byteArray") {
|
||||||
|
assert(dev.toad.Eq.byteArray.test(Array[Byte](), Array[Byte]()))
|
||||||
|
assert(dev.toad.Eq.byteArray.test(Array[Byte](1), Array[Byte](1)))
|
||||||
|
assert(dev.toad.Eq.byteArray.test(Array[Byte](1, 2), Array[Byte](1, 2)))
|
||||||
|
assert(!dev.toad.Eq.byteArray.test(Array[Byte](1), Array[Byte]()))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("int, short, long") {
|
||||||
|
assert(dev.toad.Eq.int_.test(0, 0))
|
||||||
|
assert(dev.toad.Eq.int_.test(1, 1))
|
||||||
|
assert(dev.toad.Eq.long_.test(1, 1))
|
||||||
|
assert(dev.toad.Eq.short_.test(1.shortValue, 1.shortValue))
|
||||||
|
assert(!dev.toad.Eq.int_.test(1, 0))
|
||||||
|
assert(!dev.toad.Eq.long_.test(1, 0))
|
||||||
|
assert(!dev.toad.Eq.short_.test(1.shortValue, 0.shortValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("String") {
|
||||||
|
assert(dev.toad.Eq.string.test(null, null))
|
||||||
|
assert(dev.toad.Eq.string.test("a", "a"))
|
||||||
|
assert(!dev.toad.Eq.string.test("", null))
|
||||||
|
assert(!dev.toad.Eq.string.test("", "a"))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("contramap") {
|
||||||
|
class Foo(val bar: String)
|
||||||
|
|
||||||
|
val a = Foo("baz")
|
||||||
|
val b = Foo("bingo")
|
||||||
|
|
||||||
|
val eq = dev.toad.Eq.string.contramap((foo: Foo) => foo.bar)
|
||||||
|
|
||||||
|
assert(eq.test(a, a))
|
||||||
|
assert(!eq.test(a, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("contramap + all") {
|
||||||
|
class Foo(val bar: String, val baz: String)
|
||||||
|
|
||||||
|
val a = Foo("a", "b")
|
||||||
|
val b = Foo("b", "c")
|
||||||
|
val c = Foo("a", "c")
|
||||||
|
|
||||||
|
val eq = dev.toad.Eq.all(
|
||||||
|
Seq(
|
||||||
|
dev.toad.Eq.string.contramap((f: Foo) => f.bar),
|
||||||
|
dev.toad.Eq.string.contramap((f: Foo) => f.baz)
|
||||||
|
).asJava
|
||||||
|
)
|
||||||
|
|
||||||
|
assert(eq.test(a, a))
|
||||||
|
assert(eq.test(b, b))
|
||||||
|
assert(!eq.test(a, b))
|
||||||
|
assert(!eq.test(a, c))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Map") {
|
||||||
|
val map = dev.toad.Eq.map[String, String](dev.toad.Eq.string)
|
||||||
|
assert(map.test(Map().asJava, Map().asJava))
|
||||||
|
assert(
|
||||||
|
map.test(
|
||||||
|
Map("a" -> "a", "foo" -> "bar").asJava,
|
||||||
|
Map("a" -> "a", "foo" -> "bar").asJava
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
!map.test(
|
||||||
|
Map("foo" -> "bar").asJava,
|
||||||
|
Map("a" -> "a", "foo" -> "bar").asJava
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert(!map.test(Map().asJava, Map("a" -> "a", "foo" -> "bar").asJava))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("List") {
|
||||||
|
val list = dev.toad.Eq.list(dev.toad.Eq.string)
|
||||||
|
assert(list.test(Seq("a", "b").asJava, Seq("a", "b").asJava))
|
||||||
|
assert(!list.test(Seq("b", "b").asJava, Seq("a", "b").asJava))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Id") {
|
||||||
|
assert(Id.eq.test(Id(1), Id(1)))
|
||||||
|
assert(!Id.eq.test(Id(0), Id(1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Token") {
|
||||||
|
assert(
|
||||||
|
Token.eq.test(Token(Array[Byte](1, 2, 3)), Token(Array[Byte](1, 2, 3)))
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
!Token.eq.test(Token(Array[Byte](2, 2, 3)), Token(Array[Byte](1, 2, 3)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Code") {
|
||||||
|
assert(Code.eq.test(Code.OK_CONTENT, Code.OK_CONTENT))
|
||||||
|
assert(!Code.eq.test(Code.GET, Code.OK_CONTENT))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Message") {
|
||||||
|
val a = Message.builder
|
||||||
|
.uri("coap://localhost:1234/a/b/c")
|
||||||
|
.`type`(Type.CON)
|
||||||
|
.code(Code.GET)
|
||||||
|
.id(Id(0))
|
||||||
|
.token(Token(Array[Byte](1, 2, 3)))
|
||||||
|
.payload(Payload.text("foo"))
|
||||||
|
.build
|
||||||
|
assert(Message.eq.test(a, a))
|
||||||
|
assert(!Message.eq.test(a, a.buildCopy.`type`(Type.NON).build))
|
||||||
|
assert(!Message.eq.test(a, a.buildCopy.option(Path("b/c")).build))
|
||||||
|
assert(!Message.eq.test(a, a.buildCopy.code(Code.PUT).build))
|
||||||
|
assert(!Message.eq.test(a, a.buildCopy.id(Id(1)).build))
|
||||||
|
assert(!Message.eq.test(a, a.buildCopy.token(Token(Array[Byte](2))).build))
|
||||||
|
assert(
|
||||||
|
!Message.eq.test(a, a.buildCopy.uri("coap://google.com:1234/a/b/c").build)
|
||||||
|
)
|
||||||
|
assert(!Message.eq.test(a, a.buildCopy.payload(Payload.json("foo")).build))
|
||||||
|
}
|
||||||
|
}
|
@ -2,10 +2,10 @@ import sys.process._
|
|||||||
|
|
||||||
class Glue extends munit.FunSuite {
|
class Glue extends munit.FunSuite {
|
||||||
test("cargo test") {
|
test("cargo test") {
|
||||||
// Seq(
|
Seq(
|
||||||
// "sh",
|
"sh",
|
||||||
// "-c",
|
"-c",
|
||||||
// "cd glue; RUST_BACKTRACE=full cargo test --quiet --features e2e"
|
"cd glue; RUST_BACKTRACE=full cargo test --quiet --features e2e"
|
||||||
// ).!!
|
).!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,54 @@ class MessageBuilder extends munit.FunSuite {
|
|||||||
.payload(Payload.json("[\"fart\"]"))
|
.payload(Payload.json("[\"fart\"]"))
|
||||||
.build
|
.build
|
||||||
|
|
||||||
assertEquals(msg, dev.toad.msg.build.Message.from(msg).build)
|
assertEquals(msg, dev.toad.msg.build.Message.copyOf(msg).build)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("respondTo(Message) unsets Id") {
|
||||||
|
val msg = dev.toad.msg.build.Message
|
||||||
|
.builder()
|
||||||
|
.uri("coap://localhost")
|
||||||
|
.`type`(Type.NON)
|
||||||
|
.code(Code.GET)
|
||||||
|
.payload(Payload.json("[\"fart\"]"))
|
||||||
|
.build
|
||||||
|
|
||||||
|
val rep =
|
||||||
|
dev.toad.msg.build.Message.respondTo(msg).code(Code.OK_CONTENT).build
|
||||||
|
|
||||||
|
assertEquals(rep.id, Id(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("respondTo(Message) copies token") {
|
||||||
|
val msg = dev.toad.msg.build.Message
|
||||||
|
.builder()
|
||||||
|
.uri("coap://localhost")
|
||||||
|
.`type`(Type.NON)
|
||||||
|
.code(Code.GET)
|
||||||
|
.token(Token(Array[Byte](4, 5, 6)))
|
||||||
|
.payload(Payload.json("[\"fart\"]"))
|
||||||
|
.build
|
||||||
|
|
||||||
|
val rep =
|
||||||
|
dev.toad.msg.build.Message.respondTo(msg).code(Code.OK_CONTENT).build
|
||||||
|
|
||||||
|
assertEquals(rep.token, Token(Array[Byte](4, 5, 6)))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("respondTo(Message) sets type to ACK on CON response") {
|
||||||
|
val msg = dev.toad.msg.build.Message
|
||||||
|
.builder()
|
||||||
|
.uri("coap://localhost")
|
||||||
|
.`type`(Type.CON)
|
||||||
|
.code(Code.GET)
|
||||||
|
.token(Token(Array[Byte](4, 5, 6)))
|
||||||
|
.payload(Payload.json("[\"fart\"]"))
|
||||||
|
.build
|
||||||
|
|
||||||
|
val rep =
|
||||||
|
dev.toad.msg.build.Message.respondTo(msg).code(Code.OK_CONTENT).build
|
||||||
|
|
||||||
|
assertEquals(rep.`type`(), Type.ACK)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("payload(Payload) sets content format to ContentFormat.JSON") {
|
test("payload(Payload) sets content format to ContentFormat.JSON") {
|
||||||
|
Loading…
Reference in New Issue
Block a user