From 2d613531b808a03a357cf08c8c6583ec85791b4b Mon Sep 17 00:00:00 2001 From: nick avlo Date: Wed, 26 Feb 2025 22:08:53 -0800 Subject: [PATCH 1/2] multiple filters, aka list of filters, plus tests --- .../event/json/codec/BaseMessageDecoder.java | 41 ++++---- .../java/nostr/test/event/ApiEventTest.java | 98 +++++++++++++++++++ .../test/filters/FiltersEncoderTest.java | 20 +--- .../java/nostr/test/json/JsonParseTest.java | 41 +++++++- 4 files changed, 160 insertions(+), 40 deletions(-) diff --git a/nostr-java-event/src/main/java/nostr/event/json/codec/BaseMessageDecoder.java b/nostr-java-event/src/main/java/nostr/event/json/codec/BaseMessageDecoder.java index 385d7f461..e3586aa79 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/codec/BaseMessageDecoder.java +++ b/nostr-java-event/src/main/java/nostr/event/json/codec/BaseMessageDecoder.java @@ -2,9 +2,9 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.NonNull; +import lombok.SneakyThrows; import nostr.base.IDecoder; import nostr.event.BaseMessage; import nostr.event.impl.GenericMessage; @@ -19,12 +19,15 @@ import java.util.List; import java.util.Map; +import java.util.stream.IntStream; /** * @author eric */ public class BaseMessageDecoder implements IDecoder { - public static final int MAX_JSON_NODE_THRESHOLD = 99; + public static final int COMMAND_INDEX = 0; + public static final int ARG_INDEX = 1; + public static final int FILTERS_START_INDEX = 2; private final ObjectMapper mapper = new ObjectMapper(); public BaseMessageDecoder() { @@ -33,9 +36,9 @@ public BaseMessageDecoder() { @Override public T decode(@NonNull String jsonString) throws JsonProcessingException { - ValidJsonNodeFirstPair validJsonNodeFirstPair = jsonFirstPair(jsonString); + ValidJsonNodeFirstPair validJsonNodeFirstPair = json_strCmd_arg(jsonString); String command = validJsonNodeFirstPair.formerly_strCmd(); - Object subscriptionId = validJsonNodeFirstPair.formerly_arg(); // subscriptionId + Object subscriptionId = validJsonNodeFirstPair.formerly_arg(); Object[] msgArr = mapper.readValue(jsonString, Object[].class); // TODO: replace with jsonNode after ReqMessage.decode() is finished @@ -48,35 +51,31 @@ public T decode(@NonNull String jsonString) throws JsonProcessingException { case "EVENT" -> EventMessage.decode(msgArr, mapper); case "NOTICE" -> NoticeMessage.decode(subscriptionId); case "OK" -> OkMessage.decode(msgArr); - case "REQ" -> { - String filtersJson = jsonSecondPair(jsonString).formerly_msgArr(); // filters - yield ReqMessage.decode(subscriptionId, List.of(filtersJson)); - } + case "REQ" -> ReqMessage.decode(subscriptionId, json_msgArr(jsonString)); default -> GenericMessage.decode(msgArr); }; } - private ValidJsonNodeFirstPair jsonFirstPair(@NonNull String jsonString) throws JsonProcessingException { - final JsonNode jsonNode = mapper.readTree(jsonString); - + private ValidJsonNodeFirstPair json_strCmd_arg(@NonNull String jsonString) throws JsonProcessingException { return new ValidJsonNodeFirstPair( - jsonNode.get(0).asText(), - jsonNode.get(1).asText()); + mapper.readTree(jsonString).get(COMMAND_INDEX).asText(), + mapper.readTree(jsonString).get(ARG_INDEX).asText()); } - private ValidJsonNodeSecondPair jsonSecondPair(@NonNull String jsonString) throws JsonProcessingException { - final JsonNode jsonNode = mapper.readTree(jsonString); + private List json_msgArr(@NonNull String jsonString) throws JsonProcessingException { + return IntStream.range(FILTERS_START_INDEX, mapper.readTree(jsonString).size()) + .mapToObj(idx -> readTree(jsonString, idx)).toList(); + } - return new ValidJsonNodeSecondPair( - jsonNode.get(2).toString()); + @SneakyThrows + private String readTree(String jsonString, int idx) { + return new ValidJsonNodeSecondPair(mapper.readTree(jsonString).get(idx).toString()).formerly_msgArr(); } private record ValidJsonNodeFirstPair( @NonNull String formerly_strCmd, - @NonNull Object formerly_arg) { - } + @NonNull Object formerly_arg) {} private record ValidJsonNodeSecondPair( - @NonNull String formerly_msgArr) { - } + @NonNull String formerly_msgArr) {} } diff --git a/nostr-java-test/src/test/java/nostr/test/event/ApiEventTest.java b/nostr-java-test/src/test/java/nostr/test/event/ApiEventTest.java index a5c2fae55..76fe053b2 100644 --- a/nostr-java-test/src/test/java/nostr/test/event/ApiEventTest.java +++ b/nostr-java-test/src/test/java/nostr/test/event/ApiEventTest.java @@ -217,6 +217,104 @@ public void testNIP01SendTextNoteEventCustomGenericTag() throws IOException { nip01.close(); } + @Test + public void testFiltersListReturnSameSingularEvent() throws IOException { + System.out.println("testFiltersListReturnSameSingularEvent"); + + Identity identity = Identity.generateRandomIdentity(); + + String geoHashTagTarget = "geohash_tag-location_SameSingularEvent"; + GeohashTag geohashTag = new GeohashTag(geoHashTagTarget); + + String genericTagTarget = "generic-tag-value_SameSingularEvent"; + GenericTag genericTag = GenericTag.create("m", 1, genericTagTarget); + + NIP01 nip01 = new NIP01<>(identity); + + nip01.createTextNoteEvent(List.of(geohashTag, genericTag), "Multiple Filters").signAndSend(Map.of("local", "ws://localhost:5555")); + + Filters filters1 = new Filters( + new GeohashTagFilter<>(new GeohashTag(geoHashTagTarget))); + Filters filters2 = new Filters( + new GenericTagQueryFilter<>(new GenericTagQuery("#m", genericTagTarget))); + + List result = nip01.sendRequest(List.of(filters1, filters2), UUID.randomUUID().toString()); + + assertFalse(result.isEmpty()); + assertEquals(2, result.size()); + assertTrue(result.stream().anyMatch(s -> s.contains(geoHashTagTarget))); + + nip01.close(); + } + + @Test + public void testFiltersListReturnTwoDifferentEvents() throws IOException { + System.out.println("testFiltersListReturnTwoDifferentEvents"); + +// first event + Identity identity1 = Identity.generateRandomIdentity(); + String geoHashTagTarget1 = "geohash_tag-location-1"; + GeohashTag geohashTag1 = new GeohashTag(geoHashTagTarget1); + String genericTagTarget1 = "generic-tag-value-1"; + GenericTag genericTag1 = GenericTag.create("m", 1, genericTagTarget1); + NIP01 nip01_1 = new NIP01<>(identity1); + nip01_1.createTextNoteEvent(List.of(geohashTag1, genericTag1), "Multiple Filters 1").signAndSend(Map.of("local", "ws://localhost:5555")); + +// second event + Identity identity2 = Identity.generateRandomIdentity(); + String geoHashTagTarget2 = "geohash_tag-location-2"; + GeohashTag geohashTag2 = new GeohashTag(geoHashTagTarget2); + String genericTagTarget2 = "generic-tag-value-2"; + GenericTag genericTag2 = GenericTag.create("m", 1, genericTagTarget2); + NIP01 nip01_2 = new NIP01<>(identity2); + nip01_2.createTextNoteEvent(List.of(geohashTag2, genericTag2), "Multiple Filters 2").signAndSend(Map.of("local", "ws://localhost:5555")); + + Filters filters1 = new Filters( + new GeohashTagFilter<>(new GeohashTag(geoHashTagTarget1))); // 1st filter should find match in 1st event + + Filters filters2 = new Filters( + new GenericTagQueryFilter<>(new GenericTagQuery("#m", genericTagTarget2))); // 2nd filter should find match in 2nd event + + List result = nip01_1.sendRequest(List.of(filters1, filters2), UUID.randomUUID().toString()); + + assertFalse(result.isEmpty()); + assertEquals(3, result.size()); + assertTrue(result.stream().anyMatch(s -> s.contains(geoHashTagTarget1))); + assertTrue(result.stream().anyMatch(s -> s.contains(genericTagTarget2))); + + nip01_1.close(); + nip01_2.close(); + } + + @Test + public void testMultipleFiltersDifferentTypesReturnSameEvent() throws IOException { + System.out.println("testMultipleFilters"); + + Identity identity = Identity.generateRandomIdentity(); + + String geoHashTagTarget = "geohash_tag-location-DifferentTypesReturnSameEvent"; + GeohashTag geohashTag = new GeohashTag(geoHashTagTarget); + + String genericTagTarget = "generic-tag-value-DifferentTypesReturnSameEvent"; + GenericTag genericTag = GenericTag.create("m", 1, genericTagTarget); + + NIP01 nip01 = new NIP01<>(identity); + + nip01.createTextNoteEvent(List.of(geohashTag, genericTag), "Multiple Filters").signAndSend(Map.of("local", "ws://localhost:5555")); + + Filters filters = new Filters( + new GeohashTagFilter<>(new GeohashTag(geoHashTagTarget)), + new GenericTagQueryFilter<>(new GenericTagQuery("#m", genericTagTarget))); + + List result = nip01.sendRequest(filters, UUID.randomUUID().toString()); + + assertFalse(result.isEmpty()); + assertEquals(2, result.size()); + assertTrue(result.stream().anyMatch(s -> s.contains(geoHashTagTarget))); + + nip01.close(); + } + @Test public void testNIP04EncryptDecrypt() { System.out.println("testNIP04EncryptDecrypt"); diff --git a/nostr-java-test/src/test/java/nostr/test/filters/FiltersEncoderTest.java b/nostr-java-test/src/test/java/nostr/test/filters/FiltersEncoderTest.java index 7870c2004..5d0937011 100644 --- a/nostr-java-test/src/test/java/nostr/test/filters/FiltersEncoderTest.java +++ b/nostr-java-test/src/test/java/nostr/test/filters/FiltersEncoderTest.java @@ -7,7 +7,6 @@ import nostr.event.filter.AddressableTagFilter; import nostr.event.filter.AuthorFilter; import nostr.event.filter.EventFilter; -import nostr.event.filter.Filterable; import nostr.event.filter.Filters; import nostr.event.filter.GenericTagQueryFilter; import nostr.event.filter.GeohashTagFilter; @@ -30,7 +29,6 @@ import org.junit.jupiter.api.Test; import java.time.Instant; -import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -42,8 +40,8 @@ public class FiltersEncoderTest { @Test - public void testEventFilterEncoderUsingVarArgs() { - log.info("testEventFilterEncoderUsingVarArgs"); + public void testEventFilterEncoder() { + log.info("testEventFilterEncoder"); String eventId = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; @@ -53,20 +51,6 @@ public void testEventFilterEncoderUsingVarArgs() { assertEquals("{\"ids\":[\"" + eventId + "\"]}", encodedFilters); } - @Test - public void testEventFilterEncoderUsingList() { - log.info("testEventFilterEncoderUsingList"); - - List expectedFilters = new ArrayList<>(); - - String eventId = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - expectedFilters.add(new EventFilter<>(new GenericEvent(eventId))); - - FiltersEncoder encoder = new FiltersEncoder(new Filters(expectedFilters)); - String encodedFilters = encoder.encode(); - assertEquals("{\"ids\":[\"" + eventId + "\"]}", encodedFilters); - } - @Test public void testMultipleEventFilterEncoder() { log.info("testMultipleEventFilterEncoder"); diff --git a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java index 19745be45..1041ac5e1 100644 --- a/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java +++ b/nostr-java-test/src/test/java/nostr/test/json/JsonParseTest.java @@ -717,5 +717,44 @@ public void testReqMessageSubscriptionIdTooShort() { assertThrows(IllegalArgumentException.class, () -> new BaseMessageDecoder<>().decode(parseTarget)); } -} + @Test + public void testBaseEventMessageDecoderMultipleFiltersJson() throws JsonProcessingException { + log.info("testBaseEventMessageDecoderMultipleFiltersJson"); + + final String eventJson + = "[\"EVENT\"," + + "{" + + "\"content\":\"直ん直んないわ。まあええか\"," + + "\"created_at\":1786199583," + + "\"id\":\"ec7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712\"," + + "\"kind\":1," + + "\"pubkey\":\"9c59239319637f97e007dad0d681e65ce35b1ace333b629e2d33f9465c132608\"," + + "\"sig\":\"9584afd231c52fcbcec6ce668a2cc4b6dc9b4d9da20510dcb9005c6844679b4844edb7a2e1e0591958b0295241567c774dbf7d39a73932877542de1a5f963f4b\"," + + "\"tags\":[]" + + "}]"; + + final var eventMessage = new BaseMessageDecoder<>().decode(eventJson); + + assertEquals(Command.EVENT.toString(), eventMessage.getCommand()); + + final var event = (GenericEvent) (((EventMessage) eventMessage).getEvent()); + assertEquals(1, event.getKind().intValue()); + assertEquals(1786199583, event.getCreatedAt().longValue()); + assertEquals("ec7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712", event.getId()); + + String subscriptionId = "npub27x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh"; + final String requestJson = + "[\"REQ\", " + + "\"" + subscriptionId + "\", " + + "{\"kinds\": [1], \"authors\": [\"9c59239319637f97e007dad0d681e65ce35b1ace333b629e2d33f9465c132608\"]}," + // first filter set + "{\"kinds\": [1], \"#p\": [\"ec7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712\"]}" + // second filter set + "]"; + + final var message = new BaseMessageDecoder<>().decode(requestJson); + + assertEquals(Command.REQ.toString(), message.getCommand()); + assertEquals(subscriptionId, ((ReqMessage) message).getSubscriptionId()); + assertEquals(2, ((ReqMessage) message).getFiltersList().size()); + } +} From ea6a9b747d0aebe19ebcab29fcb3f9f719a4b7b6 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Wed, 26 Feb 2025 22:26:18 -0800 Subject: [PATCH 2/2] cleanup --- .../java/nostr/event/json/codec/BaseMessageDecoder.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/nostr-java-event/src/main/java/nostr/event/json/codec/BaseMessageDecoder.java b/nostr-java-event/src/main/java/nostr/event/json/codec/BaseMessageDecoder.java index e3586aa79..b84c1917d 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/codec/BaseMessageDecoder.java +++ b/nostr-java-event/src/main/java/nostr/event/json/codec/BaseMessageDecoder.java @@ -28,6 +28,7 @@ public class BaseMessageDecoder implements IDecoder { public static final int COMMAND_INDEX = 0; public static final int ARG_INDEX = 1; public static final int FILTERS_START_INDEX = 2; + private final ObjectMapper mapper = new ObjectMapper(); public BaseMessageDecoder() { @@ -69,13 +70,10 @@ private List json_msgArr(@NonNull String jsonString) throws JsonProcessi @SneakyThrows private String readTree(String jsonString, int idx) { - return new ValidJsonNodeSecondPair(mapper.readTree(jsonString).get(idx).toString()).formerly_msgArr(); + return mapper.readTree(jsonString).get(idx).toString(); } private record ValidJsonNodeFirstPair( @NonNull String formerly_strCmd, @NonNull Object formerly_arg) {} - - private record ValidJsonNodeSecondPair( - @NonNull String formerly_msgArr) {} }