From 552df19e8f0ff770699d8308785285529b4294bf Mon Sep 17 00:00:00 2001 From: nick avlo Date: Fri, 21 Mar 2025 02:54:32 -0700 Subject: [PATCH 01/37] fixed old integrated tests for events containing generic tag and requests returning them --- .../nostr/api/integration/ApiEventIT.java | 30 ++++++++- .../api/integration/ApiNIP52RequestIT.java | 9 ++- .../src/test/java/nostr/id/EventTest.java | 62 +------------------ 3 files changed, 38 insertions(+), 63 deletions(-) diff --git a/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java b/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java index 7cc37d63b..5be6102e5 100644 --- a/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java +++ b/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java @@ -56,7 +56,6 @@ import static org.junit.jupiter.api.Assertions.*; @SpringJUnitConfig(RelayProperties.class) -@ActiveProfiles("test") public class ApiEventIT { @Autowired private Map relays; @@ -295,6 +294,35 @@ public void testMultipleFiltersDifferentTypesReturnSameEvent() throws IOExceptio // nip01.close(); } + @Test + public void testCreateUnsupportedGenericTagAttribute() { + System.out.println("testCreateUnsupportedGenericTagAttribute"); + + String aCustomTagKey = "a-custom-tag"; + String aCustomTagValue = "a custom tag value"; + GenericTag genericTag1 = GenericTag.create(aCustomTagKey, aCustomTagValue); + + String anotherCustomTagKey = "another-custom-tag"; + String anotherCustomTagValue = "another custom tag value"; + GenericTag genericTag2 = GenericTag.create(anotherCustomTagKey, anotherCustomTagValue); + + NIP01 nip01 = new NIP01<>(Identity.generateRandomIdentity()); + + nip01.createTextNoteEvent(List.of(genericTag1, genericTag2), "Multiple Generic Tags").signAndSend(relays); + + Filters filters1 = new Filters( + new GenericTagQueryFilter<>(new GenericTagQuery(aCustomTagKey, aCustomTagValue))); + Filters filters2 = new Filters( + new GenericTagQueryFilter<>(new GenericTagQuery(anotherCustomTagKey, anotherCustomTagValue))); + + 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(aCustomTagValue))); + assertTrue(result.stream().anyMatch(s -> s.contains(anotherCustomTagValue))); + } + @Test public void testNIP04EncryptDecrypt() { System.out.println("testNIP04EncryptDecrypt"); diff --git a/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP52RequestIT.java b/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP52RequestIT.java index c12167a27..fda198345 100644 --- a/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP52RequestIT.java +++ b/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP52RequestIT.java @@ -131,8 +131,12 @@ void testNIP99CalendarContentPreRequest() throws IOException { // springWebSocketEventClient.closeSocket(); - // TODO - This assertion fails with superdonductor and nostr-rs-relay -///* + /* TODO - This assertion fails with superdonductor and nostr-rs-relay + above, supplemental: SpringWebSocketClient api updated to properly handle this use/use-case + integration testing successful against superconductor. + integration testing against nostr-rs-relay still pending + */ + SpringWebSocketClient springWebSocketRequestClient = new SpringWebSocketClient(RELAY_URI); String subscriberId = UUID.randomUUID().toString(); String reqJson = createReqJson(subscriberId, eventId); @@ -145,7 +149,6 @@ void testNIP99CalendarContentPreRequest() throws IOException { MAPPER_AFTERBURNER.readTree(reqResponse))); // springWebSocketRequestClient.closeSocket(); -//*/ } private String expectedEventResponseJson(String subscriptionId) { diff --git a/nostr-java-id/src/test/java/nostr/id/EventTest.java b/nostr-java-id/src/test/java/nostr/id/EventTest.java index 9292ba99d..a2122a0a7 100644 --- a/nostr-java-id/src/test/java/nostr/id/EventTest.java +++ b/nostr-java-id/src/test/java/nostr/id/EventTest.java @@ -6,7 +6,6 @@ import org.junit.jupiter.api.Test; import nostr.base.PublicKey; -import nostr.base.Relay; import nostr.crypto.bech32.Bech32; import nostr.crypto.bech32.Bech32Prefix; import nostr.event.BaseTag; @@ -63,66 +62,11 @@ public void testCreateGenericTag() { @Test public void testCreateUnsupportedGenericTagAttribute() { -// try { -// System.out.println("testCreateUnsupportedGenericTagAttribute"); -// PublicKey publicKey = this.identity.getPublicKey(); -// GenericTag genericTag = EntityFactory.Events.createGenericTag(publicKey); -// -// Relay relay = Relay.builder().uri("wss://secret.relay.com").build(); -// relay.addNipSupport(1); -// relay.addNipSupport(genericTag.getNip()); -// -// BaseEventEncoder marshaller = new BaseEventEncoder(genericTag.getParent(), relay); -// var strJsonEvent = marshaller.marshall(); -// -// var jsonValue = new JsonObjectUnmarshaller(strJsonEvent).unmarshall(); -// -// IValue tags = ((ObjectValue) jsonValue).get("tags").get(); -// -// Assertions.assertEquals(2, ((ArrayValue) tags).length()); -// -// IValue tag = ((ArrayValue) tags).get(1).get(); -// -// Assertions.assertTrue(tag instanceof ArrayValue); -// -// IValue code = ((ArrayValue) tag).get(0).get(); -// -// Assertions.assertTrue(code instanceof StringValue); -// -// Assertions.assertEquals("devil", code.getValue()); -// Assertions.assertEquals(1, ((ArrayValue) tag).length()); -// -// } catch (NostrException ex) { -// Assertions.fail(ex); -// } + /** + * test of this functionality relocated to nostr-java-api {@link nostr.api.integration.ApiEventIT#testCreateUnsupportedGenericTagAttribute()} + */ } - // TODO - Rewrite the test class after implementing the configuration -/* - @Test - public void testCreateUnsupportedGenericTag() { - System.out.println("testCreateUnsupportedGenericTag"); - //PublicKey publicKey = this.identity.getPublicKey(); - PublicKey publicKey = Identity.getInstance().getPublicKey(); - IEvent event = EntityFactory.Events.createOtsEvent(publicKey); - GenericTag genericTag = EntityFactory.Events.createGenericTag(publicKey, event, 7); - - Relay relay = new Relay("wss://secret.relay.com"); - relay.addNipSupport(0); - - var encoder = new BaseEventEncoder((BaseEvent) genericTag.getParent(), relay); - - RuntimeException thrown = Assertions.assertThrows(RuntimeException.class, - () -> { - encoder.encode(); - }, - "This event is not supported. List of relay supported NIP(s): " + relay.printSupportedNips() - ); - - Assertions.assertNotNull(thrown); - } -*/ - @Test public void testNip05Validator() { System.out.println("testNip05Validator"); From a96b4a93ee333a53da51829a347de84b0bf4e08b Mon Sep 17 00:00:00 2001 From: nick avlo Date: Fri, 21 Mar 2025 15:16:04 -0700 Subject: [PATCH 02/37] BaseTagEncoder as record (removes need for lombok), TagSerializer refactor --- .../event/json/codec/BaseTagEncoder.java | 16 +--- .../event/json/serializer/TagSerializer.java | 81 +++++++------------ 2 files changed, 30 insertions(+), 67 deletions(-) diff --git a/nostr-java-event/src/main/java/nostr/event/json/codec/BaseTagEncoder.java b/nostr-java-event/src/main/java/nostr/event/json/codec/BaseTagEncoder.java index 71eeefce2..3fd898a36 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/codec/BaseTagEncoder.java +++ b/nostr-java-event/src/main/java/nostr/event/json/codec/BaseTagEncoder.java @@ -3,31 +3,21 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; -import lombok.Data; -import lombok.NonNull; import nostr.base.Encoder; import nostr.event.BaseTag; import nostr.event.json.serializer.TagSerializer; -@Data -public class BaseTagEncoder implements Encoder { - public static final ObjectMapper BASETAGENCODER_MAPPED_AFTERBURNER = +public record BaseTagEncoder(BaseTag tag) implements Encoder { + public static final ObjectMapper BASETAG_ENCODER_MAPPED_AFTERBURNER = ENCODER_MAPPED_AFTERBURNER.copy() .registerModule( new SimpleModule().addSerializer( new TagSerializer())); - private final BaseTag tag; - - public BaseTagEncoder(@NonNull BaseTag tag) { - this.tag = tag; - } - @Override public String encode() { try { - String s = BASETAGENCODER_MAPPED_AFTERBURNER.writeValueAsString(tag); - return s; + return BASETAG_ENCODER_MAPPED_AFTERBURNER.writeValueAsString(tag); } catch (JsonProcessingException e) { throw new RuntimeException(e); } diff --git a/nostr-java-event/src/main/java/nostr/event/json/serializer/TagSerializer.java b/nostr-java-event/src/main/java/nostr/event/json/serializer/TagSerializer.java index f3619a65f..b1982a6ae 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/serializer/TagSerializer.java +++ b/nostr-java-event/src/main/java/nostr/event/json/serializer/TagSerializer.java @@ -3,72 +3,45 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import nostr.base.ElementAttribute; import nostr.event.BaseTag; import nostr.event.impl.GenericTag; import nostr.util.NostrException; +import org.apache.commons.lang3.stream.Streams; import java.io.IOException; import java.io.Serial; -import java.lang.reflect.Field; import java.util.List; -import static nostr.event.json.codec.BaseTagEncoder.BASETAGENCODER_MAPPED_AFTERBURNER; +import static nostr.event.json.codec.BaseTagEncoder.BASETAG_ENCODER_MAPPED_AFTERBURNER; -/** - * @author guilhermegps - * - */ public class TagSerializer extends StdSerializer { - @Serial - private static final long serialVersionUID = -3877972991082754068L; - - public TagSerializer() { - super(BaseTag.class); - } - - @Override - public void serialize(BaseTag value, JsonGenerator gen, SerializerProvider serializers) { - try { - // -- Create the node - final ObjectNode node = BASETAGENCODER_MAPPED_AFTERBURNER.getNodeFactory().objectNode(); - List fields = value.getSupportedFields(); - - // Populate the node with the fields data - fields.forEach((Field f) -> { - try { - node.put(f.getName(), value.getFieldValue(f)); - } catch (NostrException ex) { - throw new RuntimeException(ex); - } - }); - - // Populate the node with the attributes data - if (value instanceof GenericTag genericTag) { - List attrs = genericTag.getAttributes(); - attrs.forEach(a -> node.put(a.getName(), a.getValue().toString())); - } - - // Extract the property values from the node and serialize them as an array - if (node.isObject()) { - ArrayNode arrayNode = node.objectNode().putArray("values"); - - // Add the tag code as the first element - arrayNode.add(value.getCode()); - node.fields().forEachRemaining(entry -> arrayNode.add(entry.getValue().asText())); - - gen.writePOJO(arrayNode); - } else { - throw new AssertionError("node.isObject()", new RuntimeException()); - } - - } catch (IOException | NostrException e) { - throw new RuntimeException(e); - } - } - + @Serial + private static final long serialVersionUID = -3877972991082754068L; + + public TagSerializer() { + super(BaseTag.class); + } + + @Override + public void serialize(BaseTag value, JsonGenerator gen, SerializerProvider serializers) { + try { + final ObjectNode node = BASETAG_ENCODER_MAPPED_AFTERBURNER.getNodeFactory().objectNode(); + Streams.failableStream(value.getSupportedFields().stream()).forEach(f -> node.put(f.getName(), value.getFieldValue(f))); + + if (value instanceof GenericTag genericTag) { + List attrs = genericTag.getAttributes(); + attrs.forEach(a -> node.put(a.getName(), a.getValue().toString())); + } + + ArrayNode arrayNode = node.objectNode().putArray("values").add(value.getCode()); + node.fields().forEachRemaining(entry -> arrayNode.add(entry.getValue().asText())); + gen.writePOJO(arrayNode); + } catch (IOException | NostrException e) { + throw new RuntimeException(e); + } + } } From 166a2b964173b5ff16760d480629c9d76cc5ad25 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Fri, 21 Mar 2025 16:03:07 -0700 Subject: [PATCH 03/37] properly relocate GenericTag --- .../src/main/java/nostr/api/NIP09.java | 2 +- .../src/main/java/nostr/api/NIP23.java | 2 +- .../src/main/java/nostr/api/NIP25.java | 3 +- .../src/main/java/nostr/api/NIP31.java | 2 +- .../src/main/java/nostr/api/NIP32.java | 2 +- .../src/main/java/nostr/api/NIP42.java | 4 +- .../src/main/java/nostr/api/NIP57.java | 2 +- .../src/main/java/nostr/api/NIP60.java | 2 +- .../src/main/java/nostr/api/NIP61.java | 2 +- .../java/nostr/api/factory/TagFactory.java | 2 +- .../nostr/api/integration/ApiEventIT.java | 3 +- .../api/integration/ApiNIP52RequestIT.java | 2 +- .../api/integration/ApiNIP99EventIT.java | 2 +- .../api/integration/ApiNIP99RequestIT.java | 2 +- .../api/unit/CalendarTimeBasedEventTest.java | 2 +- .../java/nostr/api/unit/JsonParseTest.java | 2 +- .../java/nostr/api/unit/NIP52ImplTest.java | 2 +- .../test/java/nostr/api/unit/NIP60Test.java | 2 +- .../test/java/nostr/api/unit/NIP61Test.java | 2 +- .../java/nostr/api/unit/NIP99ImplTest.java | 2 +- .../src/main/java/nostr/event/BaseTag.java | 1 + .../event/filter/GenericTagQueryFilter.java | 2 +- .../nostr/event/impl/CalendarRsvpEvent.java | 1 + .../event/impl/CalendarTimeBasedEvent.java | 1 + .../impl/CanonicalAuthenticationEvent.java | 1 + .../event/impl/ClassifiedListingEvent.java | 1 + .../java/nostr/event/impl/GenericEvent.java | 1 + .../event/json/codec/GenericTagDecoder.java | 2 +- .../serializer/AbstractTagSerializer.java | 37 +++++++++++++++++++ .../json/serializer/GenericTagSerializer.java | 23 ++++++++++++ .../event/json/serializer/TagSerializer.java | 2 +- .../CanonicalAuthenticationMessage.java | 2 +- .../nostr/event/{impl => tag}/GenericTag.java | 2 +- .../java/nostr/event/unit/BaseTagTest.java | 2 +- .../nostr/id/ClassifiedListingEventTest.java | 2 +- .../src/test/java/nostr/id/EntityFactory.java | 2 +- .../src/test/java/nostr/id/EventTest.java | 2 +- 37 files changed, 96 insertions(+), 32 deletions(-) create mode 100644 nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java create mode 100644 nostr-java-event/src/main/java/nostr/event/json/serializer/GenericTagSerializer.java rename nostr-java-event/src/main/java/nostr/event/{impl => tag}/GenericTag.java (98%) diff --git a/nostr-java-api/src/main/java/nostr/api/NIP09.java b/nostr-java-api/src/main/java/nostr/api/NIP09.java index 3836077cb..98470710b 100644 --- a/nostr-java-api/src/main/java/nostr/api/NIP09.java +++ b/nostr-java-api/src/main/java/nostr/api/NIP09.java @@ -8,7 +8,7 @@ import nostr.event.Deleteable; import nostr.event.NIP09Event; import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.tag.AddressTag; import nostr.event.tag.EventTag; import nostr.event.tag.IdentifierTag; diff --git a/nostr-java-api/src/main/java/nostr/api/NIP23.java b/nostr-java-api/src/main/java/nostr/api/NIP23.java index b945b7be7..f791e673b 100644 --- a/nostr-java-api/src/main/java/nostr/api/NIP23.java +++ b/nostr-java-api/src/main/java/nostr/api/NIP23.java @@ -11,7 +11,7 @@ import nostr.api.factory.impl.NIP23Impl.SummaryTagFactory; import nostr.api.factory.impl.NIP23Impl.TitleTagFactory; import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.tag.AddressTag; import nostr.event.tag.EventTag; import nostr.id.Identity; diff --git a/nostr-java-api/src/main/java/nostr/api/NIP25.java b/nostr-java-api/src/main/java/nostr/api/NIP25.java index c687d5c01..9d6e32789 100644 --- a/nostr-java-api/src/main/java/nostr/api/NIP25.java +++ b/nostr-java-api/src/main/java/nostr/api/NIP25.java @@ -11,8 +11,7 @@ import nostr.event.NIP25Event; import nostr.event.Reaction; import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericTag; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.tag.EmojiTag; import nostr.id.Identity; diff --git a/nostr-java-api/src/main/java/nostr/api/NIP31.java b/nostr-java-api/src/main/java/nostr/api/NIP31.java index 1f7108bc5..d969b0714 100644 --- a/nostr-java-api/src/main/java/nostr/api/NIP31.java +++ b/nostr-java-api/src/main/java/nostr/api/NIP31.java @@ -2,7 +2,7 @@ import lombok.NonNull; import nostr.api.factory.TagFactory; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; public class NIP31 { diff --git a/nostr-java-api/src/main/java/nostr/api/NIP32.java b/nostr-java-api/src/main/java/nostr/api/NIP32.java index 777f51284..aad2c03de 100644 --- a/nostr-java-api/src/main/java/nostr/api/NIP32.java +++ b/nostr-java-api/src/main/java/nostr/api/NIP32.java @@ -9,7 +9,7 @@ import nostr.api.factory.impl.NIP32Impl.LabelTagFactory; import nostr.api.factory.impl.NIP32Impl.NameSpace; import nostr.api.factory.impl.NIP32Impl.NamespaceTagFactory; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import java.util.Map; diff --git a/nostr-java-api/src/main/java/nostr/api/NIP42.java b/nostr-java-api/src/main/java/nostr/api/NIP42.java index 65decc882..8aa62c23d 100644 --- a/nostr-java-api/src/main/java/nostr/api/NIP42.java +++ b/nostr-java-api/src/main/java/nostr/api/NIP42.java @@ -14,7 +14,7 @@ import nostr.event.impl.CanonicalAuthenticationEvent; import nostr.event.impl.GenericEvent; import nostr.event.impl.GenericMessage; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.message.CanonicalAuthenticationMessage; /** @@ -83,4 +83,4 @@ public static CanonicalAuthenticationMessage createClientAuthenticationMessage(@ public static GenericMessage createRelayAuthenticationMessage(@NonNull String challenge) { return new RelayAuthenticationMessageFactory(challenge).create(); } -} \ No newline at end of file +} diff --git a/nostr-java-api/src/main/java/nostr/api/NIP57.java b/nostr-java-api/src/main/java/nostr/api/NIP57.java index c3c0cb1e4..a7ae15cff 100644 --- a/nostr-java-api/src/main/java/nostr/api/NIP57.java +++ b/nostr-java-api/src/main/java/nostr/api/NIP57.java @@ -9,7 +9,7 @@ import nostr.base.Relay; import nostr.event.BaseTag; import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.impl.ZapRequest; import nostr.event.tag.AddressTag; import nostr.event.tag.EventTag; diff --git a/nostr-java-api/src/main/java/nostr/api/NIP60.java b/nostr-java-api/src/main/java/nostr/api/NIP60.java index bc5b0d367..123bee06d 100644 --- a/nostr-java-api/src/main/java/nostr/api/NIP60.java +++ b/nostr-java-api/src/main/java/nostr/api/NIP60.java @@ -24,7 +24,7 @@ import nostr.base.Wallet; import nostr.event.BaseTag; import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.json.codec.BaseTagEncoder; import nostr.event.tag.EventTag; import nostr.id.Identity; diff --git a/nostr-java-api/src/main/java/nostr/api/NIP61.java b/nostr-java-api/src/main/java/nostr/api/NIP61.java index 08487f58c..9d27cf3b3 100644 --- a/nostr-java-api/src/main/java/nostr/api/NIP61.java +++ b/nostr-java-api/src/main/java/nostr/api/NIP61.java @@ -13,7 +13,7 @@ import nostr.base.Relay; import nostr.event.BaseTag; import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.tag.EventTag; import nostr.id.Identity; diff --git a/nostr-java-api/src/main/java/nostr/api/factory/TagFactory.java b/nostr-java-api/src/main/java/nostr/api/factory/TagFactory.java index 1b6f2a581..7c5197f40 100644 --- a/nostr-java-api/src/main/java/nostr/api/factory/TagFactory.java +++ b/nostr-java-api/src/main/java/nostr/api/factory/TagFactory.java @@ -7,7 +7,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NonNull; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; /** * diff --git a/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java b/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java index 5be6102e5..e72f5527d 100644 --- a/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java +++ b/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java @@ -26,7 +26,7 @@ import nostr.event.impl.CreateOrUpdateStallEvent.Stall; import nostr.event.impl.DirectMessageEvent; import nostr.event.impl.EncryptedPayloadEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.impl.NostrMarketplaceEvent; import nostr.event.impl.NostrMarketplaceEvent.Product.Spec; import nostr.event.impl.TextNoteEvent; @@ -40,7 +40,6 @@ import nostr.id.Identity; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import java.io.IOException; diff --git a/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP52RequestIT.java b/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP52RequestIT.java index fda198345..6775956f3 100644 --- a/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP52RequestIT.java +++ b/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP52RequestIT.java @@ -7,7 +7,7 @@ import nostr.event.BaseTag; import nostr.event.impl.CalendarContent; import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.message.EventMessage; import nostr.event.tag.EventTag; import nostr.event.tag.GeohashTag; diff --git a/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP99EventIT.java b/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP99EventIT.java index 04197533b..b9133d1f4 100644 --- a/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP99EventIT.java +++ b/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP99EventIT.java @@ -7,7 +7,7 @@ import nostr.event.BaseTag; import nostr.event.impl.ClassifiedListing; import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.message.EventMessage; import nostr.event.tag.EventTag; import nostr.event.tag.GeohashTag; diff --git a/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP99RequestIT.java b/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP99RequestIT.java index 70c5255f1..21d7dea93 100644 --- a/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP99RequestIT.java +++ b/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP99RequestIT.java @@ -7,7 +7,7 @@ import nostr.event.BaseTag; import nostr.event.impl.ClassifiedListing; import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.message.EventMessage; import nostr.event.tag.EventTag; import nostr.event.tag.GeohashTag; diff --git a/nostr-java-api/src/test/java/nostr/api/unit/CalendarTimeBasedEventTest.java b/nostr-java-api/src/test/java/nostr/api/unit/CalendarTimeBasedEventTest.java index 9a2266c30..f65a5503e 100644 --- a/nostr-java-api/src/test/java/nostr/api/unit/CalendarTimeBasedEventTest.java +++ b/nostr-java-api/src/test/java/nostr/api/unit/CalendarTimeBasedEventTest.java @@ -9,7 +9,7 @@ import nostr.event.NIP52Event; import nostr.event.impl.CalendarContent; import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.json.codec.BaseEventEncoder; import nostr.event.tag.GeohashTag; import nostr.event.tag.HashtagTag; diff --git a/nostr-java-api/src/test/java/nostr/api/unit/JsonParseTest.java b/nostr-java-api/src/test/java/nostr/api/unit/JsonParseTest.java index ccfff117e..76b6a3dbb 100644 --- a/nostr-java-api/src/test/java/nostr/api/unit/JsonParseTest.java +++ b/nostr-java-api/src/test/java/nostr/api/unit/JsonParseTest.java @@ -27,7 +27,7 @@ import nostr.event.filter.ReferencedEventFilter; import nostr.event.filter.ReferencedPublicKeyFilter; import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.json.codec.BaseEventEncoder; import nostr.event.json.codec.BaseMessageDecoder; import nostr.event.json.codec.BaseTagDecoder; diff --git a/nostr-java-api/src/test/java/nostr/api/unit/NIP52ImplTest.java b/nostr-java-api/src/test/java/nostr/api/unit/NIP52ImplTest.java index 034148118..5512abce8 100644 --- a/nostr-java-api/src/test/java/nostr/api/unit/NIP52ImplTest.java +++ b/nostr-java-api/src/test/java/nostr/api/unit/NIP52ImplTest.java @@ -5,7 +5,7 @@ import nostr.event.BaseTag; import nostr.event.impl.CalendarContent; import nostr.event.impl.CalendarTimeBasedEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.tag.GeohashTag; import nostr.event.tag.HashtagTag; import nostr.event.tag.IdentifierTag; diff --git a/nostr-java-api/src/test/java/nostr/api/unit/NIP60Test.java b/nostr-java-api/src/test/java/nostr/api/unit/NIP60Test.java index d24fc641c..47f99b0fb 100644 --- a/nostr-java-api/src/test/java/nostr/api/unit/NIP60Test.java +++ b/nostr-java-api/src/test/java/nostr/api/unit/NIP60Test.java @@ -13,7 +13,7 @@ import nostr.event.BaseTag; import nostr.event.Marker; import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.tag.AddressTag; import nostr.event.tag.EventTag; import nostr.id.Identity; diff --git a/nostr-java-api/src/test/java/nostr/api/unit/NIP61Test.java b/nostr-java-api/src/test/java/nostr/api/unit/NIP61Test.java index c30e826f8..d5cb50905 100644 --- a/nostr-java-api/src/test/java/nostr/api/unit/NIP61Test.java +++ b/nostr-java-api/src/test/java/nostr/api/unit/NIP61Test.java @@ -13,7 +13,7 @@ import nostr.base.Relay; import nostr.event.BaseTag; import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.tag.EventTag; import nostr.event.tag.PubKeyTag; import nostr.id.Identity; diff --git a/nostr-java-api/src/test/java/nostr/api/unit/NIP99ImplTest.java b/nostr-java-api/src/test/java/nostr/api/unit/NIP99ImplTest.java index 284e06766..33414c482 100644 --- a/nostr-java-api/src/test/java/nostr/api/unit/NIP99ImplTest.java +++ b/nostr-java-api/src/test/java/nostr/api/unit/NIP99ImplTest.java @@ -4,7 +4,7 @@ import nostr.event.BaseTag; import nostr.event.impl.ClassifiedListing; import nostr.event.impl.ClassifiedListingEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.tag.PriceTag; import nostr.id.Identity; import org.junit.jupiter.api.BeforeAll; diff --git a/nostr-java-event/src/main/java/nostr/event/BaseTag.java b/nostr-java-event/src/main/java/nostr/event/BaseTag.java index 5c0aaff75..501cedd42 100644 --- a/nostr-java-event/src/main/java/nostr/event/BaseTag.java +++ b/nostr-java-event/src/main/java/nostr/event/BaseTag.java @@ -34,6 +34,7 @@ @Data @ToString @EqualsAndHashCode(callSuper = false) + @JsonDeserialize(using = TagDeserializer.class) @JsonSerialize(using = TagSerializer.class) public abstract class BaseTag implements ITag { diff --git a/nostr-java-event/src/main/java/nostr/event/filter/GenericTagQueryFilter.java b/nostr-java-event/src/main/java/nostr/event/filter/GenericTagQueryFilter.java index c92ffeb98..a9f8290fc 100644 --- a/nostr-java-event/src/main/java/nostr/event/filter/GenericTagQueryFilter.java +++ b/nostr-java-event/src/main/java/nostr/event/filter/GenericTagQueryFilter.java @@ -5,7 +5,7 @@ import nostr.base.ElementAttribute; import nostr.base.GenericTagQuery; import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import java.util.function.Function; import java.util.function.Predicate; diff --git a/nostr-java-event/src/main/java/nostr/event/impl/CalendarRsvpEvent.java b/nostr-java-event/src/main/java/nostr/event/impl/CalendarRsvpEvent.java index 722323c84..a10a0c4b8 100644 --- a/nostr-java-event/src/main/java/nostr/event/impl/CalendarRsvpEvent.java +++ b/nostr-java-event/src/main/java/nostr/event/impl/CalendarRsvpEvent.java @@ -18,6 +18,7 @@ import nostr.event.NIP52Event; import nostr.event.impl.CalendarRsvpEvent.CalendarRsvpEventDeserializer; import nostr.event.tag.AddressTag; +import nostr.event.tag.GenericTag; import nostr.event.tag.IdentifierTag; import nostr.event.tag.PubKeyTag; diff --git a/nostr-java-event/src/main/java/nostr/event/impl/CalendarTimeBasedEvent.java b/nostr-java-event/src/main/java/nostr/event/impl/CalendarTimeBasedEvent.java index 11b5647a7..9f5e15cc8 100644 --- a/nostr-java-event/src/main/java/nostr/event/impl/CalendarTimeBasedEvent.java +++ b/nostr-java-event/src/main/java/nostr/event/impl/CalendarTimeBasedEvent.java @@ -17,6 +17,7 @@ import nostr.event.Kind; import nostr.event.NIP52Event; import nostr.event.impl.CalendarTimeBasedEvent.CalendarTimeBasedEventDeserializer; +import nostr.event.tag.GenericTag; import nostr.event.tag.IdentifierTag; import nostr.event.tag.PubKeyTag; diff --git a/nostr-java-event/src/main/java/nostr/event/impl/CanonicalAuthenticationEvent.java b/nostr-java-event/src/main/java/nostr/event/impl/CanonicalAuthenticationEvent.java index d11b6db12..132d6f2f1 100644 --- a/nostr-java-event/src/main/java/nostr/event/impl/CanonicalAuthenticationEvent.java +++ b/nostr-java-event/src/main/java/nostr/event/impl/CanonicalAuthenticationEvent.java @@ -7,6 +7,7 @@ import nostr.base.annotation.Event; import nostr.event.BaseTag; import nostr.event.Kind; +import nostr.event.tag.GenericTag; import java.util.ArrayList; import java.util.List; diff --git a/nostr-java-event/src/main/java/nostr/event/impl/ClassifiedListingEvent.java b/nostr-java-event/src/main/java/nostr/event/impl/ClassifiedListingEvent.java index c0520514e..f2ad150c8 100644 --- a/nostr-java-event/src/main/java/nostr/event/impl/ClassifiedListingEvent.java +++ b/nostr-java-event/src/main/java/nostr/event/impl/ClassifiedListingEvent.java @@ -18,6 +18,7 @@ import nostr.event.Kind; import nostr.event.NIP99Event; import nostr.event.impl.ClassifiedListingEvent.ClassifiedListingEventDeserializer; +import nostr.event.tag.GenericTag; import nostr.event.tag.PriceTag; import java.io.IOException; diff --git a/nostr-java-event/src/main/java/nostr/event/impl/GenericEvent.java b/nostr-java-event/src/main/java/nostr/event/impl/GenericEvent.java index 59098896d..abe33f000 100644 --- a/nostr-java-event/src/main/java/nostr/event/impl/GenericEvent.java +++ b/nostr-java-event/src/main/java/nostr/event/impl/GenericEvent.java @@ -24,6 +24,7 @@ import nostr.event.Kind; import nostr.event.json.deserializer.PublicKeyDeserializer; import nostr.event.json.deserializer.SignatureDeserializer; +import nostr.event.tag.GenericTag; import nostr.util.NostrException; import nostr.util.NostrUtil; import nostr.util.thread.HexStringValidator; diff --git a/nostr-java-event/src/main/java/nostr/event/json/codec/GenericTagDecoder.java b/nostr-java-event/src/main/java/nostr/event/json/codec/GenericTagDecoder.java index d65ba7c22..3ffe238fa 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/codec/GenericTagDecoder.java +++ b/nostr-java-event/src/main/java/nostr/event/json/codec/GenericTagDecoder.java @@ -5,7 +5,7 @@ import lombok.NonNull; import nostr.base.ElementAttribute; import nostr.base.IDecoder; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import java.util.stream.IntStream; diff --git a/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java b/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java new file mode 100644 index 000000000..6f394a64f --- /dev/null +++ b/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java @@ -0,0 +1,37 @@ +package nostr.event.json.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import nostr.event.BaseTag; +import nostr.util.NostrException; +import org.apache.commons.lang3.stream.Streams; + +import java.io.IOException; + +import static nostr.event.json.codec.BaseTagEncoder.BASETAG_ENCODER_MAPPED_AFTERBURNER; + +public abstract class AbstractTagSerializer extends StdSerializer { + protected AbstractTagSerializer(Class t) { + super(t); + } + + public void serialize(T value, JsonGenerator gen, SerializerProvider serializers) { + try { + final ObjectNode node = BASETAG_ENCODER_MAPPED_AFTERBURNER.getNodeFactory().objectNode(); + Streams.failableStream(value.getSupportedFields().stream()).forEach(f -> node.put(f.getName(), value.getFieldValue(f))); + + processNode(value); + + ArrayNode arrayNode = node.objectNode().putArray("values").add(value.getCode()); + node.fields().forEachRemaining(entry -> arrayNode.add(entry.getValue().asText())); + gen.writePOJO(arrayNode); + } catch (IOException | NostrException e) { + throw new RuntimeException(e); + } + } + + public void processNode(T value) {} +} diff --git a/nostr-java-event/src/main/java/nostr/event/json/serializer/GenericTagSerializer.java b/nostr-java-event/src/main/java/nostr/event/json/serializer/GenericTagSerializer.java new file mode 100644 index 000000000..18d586f4c --- /dev/null +++ b/nostr-java-event/src/main/java/nostr/event/json/serializer/GenericTagSerializer.java @@ -0,0 +1,23 @@ +package nostr.event.json.serializer; + +import nostr.base.ElementAttribute; +import nostr.event.tag.GenericTag; + +import java.io.Serial; +import java.util.List; + +public class GenericTagSerializer extends AbstractTagSerializer { + + @Serial + private static final long serialVersionUID = -5318614324350049034L; + + public GenericTagSerializer() { + super((Class) GenericTag.class); + } + + @Override + public void processNode(T value) { + List attrs = genericTag.getAttributes(); + attrs.forEach(a -> node.put(a.getName(), a.getValue().toString())); + } +} diff --git a/nostr-java-event/src/main/java/nostr/event/json/serializer/TagSerializer.java b/nostr-java-event/src/main/java/nostr/event/json/serializer/TagSerializer.java index b1982a6ae..2a333016d 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/serializer/TagSerializer.java +++ b/nostr-java-event/src/main/java/nostr/event/json/serializer/TagSerializer.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.databind.ser.std.StdSerializer; import nostr.base.ElementAttribute; import nostr.event.BaseTag; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.util.NostrException; import org.apache.commons.lang3.stream.Streams; diff --git a/nostr-java-event/src/main/java/nostr/event/message/CanonicalAuthenticationMessage.java b/nostr-java-event/src/main/java/nostr/event/message/CanonicalAuthenticationMessage.java index c7ea876b5..35ab07166 100644 --- a/nostr-java-event/src/main/java/nostr/event/message/CanonicalAuthenticationMessage.java +++ b/nostr-java-event/src/main/java/nostr/event/message/CanonicalAuthenticationMessage.java @@ -12,7 +12,7 @@ import nostr.event.BaseMessage; import nostr.event.impl.CanonicalAuthenticationEvent; import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.json.codec.BaseEventEncoder; import java.util.List; diff --git a/nostr-java-event/src/main/java/nostr/event/impl/GenericTag.java b/nostr-java-event/src/main/java/nostr/event/tag/GenericTag.java similarity index 98% rename from nostr-java-event/src/main/java/nostr/event/impl/GenericTag.java rename to nostr-java-event/src/main/java/nostr/event/tag/GenericTag.java index 3148f8a59..b85220724 100644 --- a/nostr-java-event/src/main/java/nostr/event/impl/GenericTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/GenericTag.java @@ -1,4 +1,4 @@ -package nostr.event.impl; +package nostr.event.tag; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/nostr-java-event/src/test/java/nostr/event/unit/BaseTagTest.java b/nostr-java-event/src/test/java/nostr/event/unit/BaseTagTest.java index e4548c451..6a958dd8c 100644 --- a/nostr-java-event/src/test/java/nostr/event/unit/BaseTagTest.java +++ b/nostr-java-event/src/test/java/nostr/event/unit/BaseTagTest.java @@ -1,7 +1,7 @@ package nostr.event.unit; import nostr.event.BaseTag; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/nostr-java-id/src/test/java/nostr/id/ClassifiedListingEventTest.java b/nostr-java-id/src/test/java/nostr/id/ClassifiedListingEventTest.java index baad1eb9f..497216ad1 100644 --- a/nostr-java-id/src/test/java/nostr/id/ClassifiedListingEventTest.java +++ b/nostr-java-id/src/test/java/nostr/id/ClassifiedListingEventTest.java @@ -4,7 +4,7 @@ import nostr.event.BaseTag; import nostr.event.Kind; import nostr.event.impl.ClassifiedListingEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.tag.EventTag; import nostr.event.tag.GeohashTag; import nostr.event.tag.HashtagTag; diff --git a/nostr-java-id/src/test/java/nostr/id/EntityFactory.java b/nostr-java-id/src/test/java/nostr/id/EntityFactory.java index 66173ac1a..694b2b254 100644 --- a/nostr-java-id/src/test/java/nostr/id/EntityFactory.java +++ b/nostr-java-id/src/test/java/nostr/id/EntityFactory.java @@ -12,7 +12,7 @@ import nostr.event.impl.DirectMessageEvent; import nostr.event.impl.EphemeralEvent; import nostr.event.impl.GenericEvent; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.impl.InternetIdentifierMetadataEvent; import nostr.event.impl.MentionsEvent; import nostr.event.impl.MetadataEvent; diff --git a/nostr-java-id/src/test/java/nostr/id/EventTest.java b/nostr-java-id/src/test/java/nostr/id/EventTest.java index a2122a0a7..35456b40e 100644 --- a/nostr-java-id/src/test/java/nostr/id/EventTest.java +++ b/nostr-java-id/src/test/java/nostr/id/EventTest.java @@ -11,7 +11,7 @@ import nostr.event.BaseTag; import nostr.event.impl.GenericEvent; import nostr.event.impl.GenericMessage; -import nostr.event.impl.GenericTag; +import nostr.event.tag.GenericTag; import nostr.event.json.codec.BaseTagEncoder; import nostr.event.util.Nip05Validator; import nostr.util.NostrUtil; From 29297818f22409536d8cbeb5f9ceda94ff297684 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Fri, 21 Mar 2025 17:06:36 -0700 Subject: [PATCH 04/37] add tag serializer polymorphism --- .../src/main/java/nostr/event/BaseTag.java | 73 +++++---- .../event/json/codec/BaseTagEncoder.java | 4 +- .../event/json/codec/FiltersEncoder.java | 41 ++--- .../serializer/AbstractTagSerializer.java | 6 +- .../json/serializer/BaseTagSerializer.java | 14 ++ .../json/serializer/GenericTagSerializer.java | 9 +- .../event/json/serializer/TagSerializer.java | 47 ------ .../main/java/nostr/event/tag/GenericTag.java | 144 +++++++++--------- 8 files changed, 144 insertions(+), 194 deletions(-) create mode 100644 nostr-java-event/src/main/java/nostr/event/json/serializer/BaseTagSerializer.java delete mode 100644 nostr-java-event/src/main/java/nostr/event/json/serializer/TagSerializer.java diff --git a/nostr-java-event/src/main/java/nostr/event/BaseTag.java b/nostr-java-event/src/main/java/nostr/event/BaseTag.java index 501cedd42..cce67680c 100644 --- a/nostr-java-event/src/main/java/nostr/event/BaseTag.java +++ b/nostr-java-event/src/main/java/nostr/event/BaseTag.java @@ -13,10 +13,9 @@ import nostr.base.annotation.Key; import nostr.base.annotation.Tag; import nostr.event.json.deserializer.TagDeserializer; -import nostr.event.json.serializer.TagSerializer; +import nostr.event.json.serializer.BaseTagSerializer; import nostr.util.NostrException; import org.apache.commons.lang3.stream.Streams; - import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; @@ -28,53 +27,49 @@ import java.util.function.BiConsumer; import java.util.stream.Collectors; -/** - * @author squirrel - */ @Data @ToString @EqualsAndHashCode(callSuper = false) - @JsonDeserialize(using = TagDeserializer.class) -@JsonSerialize(using = TagSerializer.class) +@JsonSerialize(using = BaseTagSerializer.class) public abstract class BaseTag implements ITag { - @JsonIgnore - private IEvent parent; + @JsonIgnore + private IEvent parent; - @Override - public void setParent(@NonNull IEvent event) { - this.parent = event; - } + @Override + public void setParent(@NonNull IEvent event) { + this.parent = event; + } - @Override - public String getCode() { - return this.getClass().getAnnotation(Tag.class).code(); - } + @Override + public String getCode() { + return this.getClass().getAnnotation(Tag.class).code(); + } - public String getFieldValue(Field field) throws NostrException { - try { - Object f = new PropertyDescriptor(field.getName(), this.getClass()).getReadMethod().invoke(this); - return f != null ? f.toString() : null; - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | IntrospectionException ex) { - throw new NostrException(ex); - } - } + public String getFieldValue(Field field) throws NostrException { + try { + Object f = new PropertyDescriptor(field.getName(), this.getClass()).getReadMethod().invoke(this); + return f != null ? f.toString() : null; + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | IntrospectionException ex) { + throw new NostrException(ex); + } + } - public List getSupportedFields() throws NostrException { - return new Streams.FailableStream<>(Arrays.stream(this.getClass().getDeclaredFields())) - .filter(f -> - Objects.nonNull(f.getAnnotation(Key.class))) - .filter(f -> - Objects.nonNull(getFieldValue(f))) - .collect(Collectors.toList()); - } + public List getSupportedFields() throws NostrException { + return new Streams.FailableStream<>(Arrays.stream(this.getClass().getDeclaredFields())) + .filter(f -> + Objects.nonNull(f.getAnnotation(Key.class))) + .filter(f -> + Objects.nonNull(getFieldValue(f))) + .collect(Collectors.toList()); + } - protected static void setOptionalField(JsonNode node, BiConsumer con, T tag) { - Optional.ofNullable(node).ifPresent(n -> con.accept(n, tag)); - } + protected static void setOptionalField(JsonNode node, BiConsumer con, T tag) { + Optional.ofNullable(node).ifPresent(n -> con.accept(n, tag)); + } - protected static void setRequiredField(JsonNode node, BiConsumer con, T tag) { - con.accept(Optional.ofNullable(node).orElseThrow(), tag); - } + protected static void setRequiredField(JsonNode node, BiConsumer con, T tag) { + con.accept(Optional.ofNullable(node).orElseThrow(), tag); + } } diff --git a/nostr-java-event/src/main/java/nostr/event/json/codec/BaseTagEncoder.java b/nostr-java-event/src/main/java/nostr/event/json/codec/BaseTagEncoder.java index 3fd898a36..3066652a2 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/codec/BaseTagEncoder.java +++ b/nostr-java-event/src/main/java/nostr/event/json/codec/BaseTagEncoder.java @@ -5,14 +5,14 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import nostr.base.Encoder; import nostr.event.BaseTag; -import nostr.event.json.serializer.TagSerializer; +import nostr.event.json.serializer.BaseTagSerializer; public record BaseTagEncoder(BaseTag tag) implements Encoder { public static final ObjectMapper BASETAG_ENCODER_MAPPED_AFTERBURNER = ENCODER_MAPPED_AFTERBURNER.copy() .registerModule( new SimpleModule().addSerializer( - new TagSerializer())); + new BaseTagSerializer<>())); @Override public String encode() { diff --git a/nostr-java-event/src/main/java/nostr/event/json/codec/FiltersEncoder.java b/nostr-java-event/src/main/java/nostr/event/json/codec/FiltersEncoder.java index 2325bd924..8aa310ac6 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/codec/FiltersEncoder.java +++ b/nostr-java-event/src/main/java/nostr/event/json/codec/FiltersEncoder.java @@ -1,35 +1,26 @@ package nostr.event.json.codec; import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.Data; -import lombok.EqualsAndHashCode; import nostr.base.Encoder; import nostr.event.filter.Filters; -@Data -@EqualsAndHashCode(callSuper = false) -public class FiltersEncoder implements Encoder { - private final Filters filters; +public record FiltersEncoder(Filters filters) implements Encoder { - public FiltersEncoder(Filters filters) { - this.filters = filters; - } + @Override + public String encode() { + ObjectNode root = ENCODER_MAPPED_AFTERBURNER.createObjectNode(); - @Override - public String encode() { - ObjectNode root = ENCODER_MAPPED_AFTERBURNER.createObjectNode(); + filters.getFiltersMap().forEach((key, filterableList) -> { + final ObjectNode objectNode = ENCODER_MAPPED_AFTERBURNER.createObjectNode(); + root.setAll( + filterableList + .stream() + .map(filterable -> + filterable.toObjectNode(objectNode)) + .toList() + .getFirst()); + }); - filters.getFiltersMap().forEach((key, filterableList) -> { - final ObjectNode objectNode = ENCODER_MAPPED_AFTERBURNER.createObjectNode(); - root.setAll( - filterableList - .stream() - .map(filterable -> - filterable.toObjectNode(objectNode)) - .toList() - .getFirst()); - }); - - return root.toString(); - } + return root.toString(); + } } diff --git a/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java b/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java index 6f394a64f..0e3cf1fd6 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java +++ b/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java @@ -13,7 +13,7 @@ import static nostr.event.json.codec.BaseTagEncoder.BASETAG_ENCODER_MAPPED_AFTERBURNER; -public abstract class AbstractTagSerializer extends StdSerializer { +abstract class AbstractTagSerializer extends StdSerializer { protected AbstractTagSerializer(Class t) { super(t); } @@ -23,7 +23,7 @@ public void serialize(T value, JsonGenerator gen, SerializerProvider serializers final ObjectNode node = BASETAG_ENCODER_MAPPED_AFTERBURNER.getNodeFactory().objectNode(); Streams.failableStream(value.getSupportedFields().stream()).forEach(f -> node.put(f.getName(), value.getFieldValue(f))); - processNode(value); + processNode(node, value); ArrayNode arrayNode = node.objectNode().putArray("values").add(value.getCode()); node.fields().forEachRemaining(entry -> arrayNode.add(entry.getValue().asText())); @@ -33,5 +33,5 @@ public void serialize(T value, JsonGenerator gen, SerializerProvider serializers } } - public void processNode(T value) {} + protected void processNode(ObjectNode node, T value) {} } diff --git a/nostr-java-event/src/main/java/nostr/event/json/serializer/BaseTagSerializer.java b/nostr-java-event/src/main/java/nostr/event/json/serializer/BaseTagSerializer.java new file mode 100644 index 000000000..f21a86fe1 --- /dev/null +++ b/nostr-java-event/src/main/java/nostr/event/json/serializer/BaseTagSerializer.java @@ -0,0 +1,14 @@ +package nostr.event.json.serializer; + +import nostr.event.BaseTag; +import java.io.Serial; + +public class BaseTagSerializer extends AbstractTagSerializer { + + @Serial + private static final long serialVersionUID = -3877972991082754068L; + + public BaseTagSerializer() { + super((Class) BaseTag.class); + } +} diff --git a/nostr-java-event/src/main/java/nostr/event/json/serializer/GenericTagSerializer.java b/nostr-java-event/src/main/java/nostr/event/json/serializer/GenericTagSerializer.java index 18d586f4c..386adabe5 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/serializer/GenericTagSerializer.java +++ b/nostr-java-event/src/main/java/nostr/event/json/serializer/GenericTagSerializer.java @@ -1,10 +1,8 @@ package nostr.event.json.serializer; -import nostr.base.ElementAttribute; +import com.fasterxml.jackson.databind.node.ObjectNode; import nostr.event.tag.GenericTag; - import java.io.Serial; -import java.util.List; public class GenericTagSerializer extends AbstractTagSerializer { @@ -16,8 +14,7 @@ public GenericTagSerializer() { } @Override - public void processNode(T value) { - List attrs = genericTag.getAttributes(); - attrs.forEach(a -> node.put(a.getName(), a.getValue().toString())); + protected void processNode(ObjectNode node, T value) { + value.getAttributes().forEach(a -> node.put(a.getName(), a.getValue().toString())); } } diff --git a/nostr-java-event/src/main/java/nostr/event/json/serializer/TagSerializer.java b/nostr-java-event/src/main/java/nostr/event/json/serializer/TagSerializer.java deleted file mode 100644 index 2a333016d..000000000 --- a/nostr-java-event/src/main/java/nostr/event/json/serializer/TagSerializer.java +++ /dev/null @@ -1,47 +0,0 @@ -package nostr.event.json.serializer; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import nostr.base.ElementAttribute; -import nostr.event.BaseTag; -import nostr.event.tag.GenericTag; -import nostr.util.NostrException; -import org.apache.commons.lang3.stream.Streams; - -import java.io.IOException; -import java.io.Serial; -import java.util.List; - -import static nostr.event.json.codec.BaseTagEncoder.BASETAG_ENCODER_MAPPED_AFTERBURNER; - -public class TagSerializer extends StdSerializer { - - @Serial - private static final long serialVersionUID = -3877972991082754068L; - - public TagSerializer() { - super(BaseTag.class); - } - - @Override - public void serialize(BaseTag value, JsonGenerator gen, SerializerProvider serializers) { - try { - final ObjectNode node = BASETAG_ENCODER_MAPPED_AFTERBURNER.getNodeFactory().objectNode(); - Streams.failableStream(value.getSupportedFields().stream()).forEach(f -> node.put(f.getName(), value.getFieldValue(f))); - - if (value instanceof GenericTag genericTag) { - List attrs = genericTag.getAttributes(); - attrs.forEach(a -> node.put(a.getName(), a.getValue().toString())); - } - - ArrayNode arrayNode = node.objectNode().putArray("values").add(value.getCode()); - node.fields().forEachRemaining(entry -> arrayNode.add(entry.getValue().asText())); - gen.writePOJO(arrayNode); - } catch (IOException | NostrException e) { - throw new RuntimeException(e); - } - } -} diff --git a/nostr-java-event/src/main/java/nostr/event/tag/GenericTag.java b/nostr-java-event/src/main/java/nostr/event/tag/GenericTag.java index b85220724..22cb60189 100644 --- a/nostr-java-event/src/main/java/nostr/event/tag/GenericTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/GenericTag.java @@ -1,90 +1,90 @@ package nostr.event.tag; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NonNull; import nostr.base.ElementAttribute; import nostr.base.IGenericElement; import nostr.event.BaseTag; +import nostr.event.json.serializer.GenericTagSerializer; import java.util.ArrayList; import java.util.List; import java.util.stream.IntStream; -/** - * @author squirrel - */ @Data @EqualsAndHashCode(callSuper = false) +@JsonSerialize(using = GenericTagSerializer.class) public class GenericTag extends BaseTag implements IGenericElement { - private final String code; - - private final List attributes; - - public GenericTag(String code) { - this(code, new ArrayList<>()); - } - - /** - * nip parameter to be removed - * - * @deprecated use any available proper constructor variant instead - */ - @Deprecated(forRemoval = true) - public GenericTag(String code, Integer nip) { - this(code, new ArrayList<>()); - } - - public GenericTag(@NonNull String code, @NonNull ElementAttribute... attribute) { - this(code, List.of(attribute)); - } - - public GenericTag(@NonNull String code, @NonNull List attributes) { - this.code = code; - this.attributes = attributes; - } - - @Override - public void addAttribute(@NonNull ElementAttribute... attribute) { - this.addAttributes(List.of(attribute)); - } - - @Override - public void addAttributes(@NonNull List attributes) { - this.attributes.addAll(attributes); - } - - /** - * nip parameter to be removed - * - * @deprecated use {@link #create(String, String...)} instead. - */ - @Deprecated(forRemoval = true) - public static GenericTag create(String code, Integer nip, String... params) { - return create(code, List.of(params)); - } - - /** - * nip parameter to be removed - * - * @deprecated use {@link #create(String, List)} instead. - */ - - @Deprecated(forRemoval = true) - public static GenericTag create(String code, Integer nip, List params) { - return create(code, params); - } - - public static GenericTag create(@NonNull String code, @NonNull String... params) { - return create(code, List.of(params)); - } - - public static GenericTag create(@NonNull String code, @NonNull List params) { - return new GenericTag(code, - IntStream.range(0, params.size()) - .mapToObj(i -> - new ElementAttribute("param".concat(String.valueOf(i)), params.get(i))) - .toList()); - } + private final String code; + + private final List attributes; + + public GenericTag(String code) { + this(code, new ArrayList<>()); + } + + /** + * nip parameter to be removed + * + * @deprecated use any available proper constructor variant instead + */ + @Deprecated(forRemoval = true) + public GenericTag(String code, Integer nip) { + this(code, new ArrayList<>()); + } + + public GenericTag(@NonNull String code, @NonNull ElementAttribute... attribute) { + this(code, List.of(attribute)); + } + + public GenericTag(@NonNull String code, @NonNull List attributes) { + this.code = code; + this.attributes = attributes; + } + + @Override + public void addAttribute(@NonNull ElementAttribute... attribute) { + this.addAttributes(List.of(attribute)); + } + + @Override + public void addAttributes(@NonNull List attributes) { + this.attributes.addAll(attributes); + } + + /** + * nip parameter to be removed + * + * @deprecated use {@link #create(String, String...)} instead. + */ + @Deprecated(forRemoval = true) + public static GenericTag create(String code, Integer nip, String... params) { + return create(code, List.of(params)); + } + + /** + * nip parameter to be removed + * + * @deprecated use {@link #create(String, List)} instead. + */ + + @Deprecated(forRemoval = true) + public static GenericTag create(String code, Integer nip, List params) { + return create(code, params); + } + + public static GenericTag create(@NonNull String code, @NonNull String... params) { + return create(code, List.of(params)); + } + + public static GenericTag create(@NonNull String code, @NonNull List params) { + return new GenericTag(code, + IntStream.range(0, params.size()) + .mapToObj(i -> + new ElementAttribute("param".concat(String.valueOf(i)), params.get(i))) + .toList()); + } } From 04056067bac815dde82d17190d52c25bfdc4cb01 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Fri, 21 Mar 2025 18:01:32 -0700 Subject: [PATCH 05/37] cleanup --- .../event/json/codec/FiltersEncoder.java | 28 +++++++++---------- .../deserializer/PublicKeyDeserializer.java | 9 ++---- .../deserializer/SignatureDeserializer.java | 13 ++------- .../serializer/AbstractTagSerializer.java | 8 ++++-- .../json/serializer/GenericTagSerializer.java | 2 +- .../main/java/nostr/event/tag/NonceTag.java | 1 - .../main/java/nostr/event/tag/PubKeyTag.java | 5 ---- .../main/java/nostr/event/tag/SubjectTag.java | 1 - 8 files changed, 23 insertions(+), 44 deletions(-) diff --git a/nostr-java-event/src/main/java/nostr/event/json/codec/FiltersEncoder.java b/nostr-java-event/src/main/java/nostr/event/json/codec/FiltersEncoder.java index 8aa310ac6..3f35fadc8 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/codec/FiltersEncoder.java +++ b/nostr-java-event/src/main/java/nostr/event/json/codec/FiltersEncoder.java @@ -6,21 +6,19 @@ public record FiltersEncoder(Filters filters) implements Encoder { - @Override - public String encode() { - ObjectNode root = ENCODER_MAPPED_AFTERBURNER.createObjectNode(); + @Override + public String encode() { + ObjectNode root = ENCODER_MAPPED_AFTERBURNER.createObjectNode(); - filters.getFiltersMap().forEach((key, filterableList) -> { - final ObjectNode objectNode = ENCODER_MAPPED_AFTERBURNER.createObjectNode(); - root.setAll( - filterableList - .stream() - .map(filterable -> - filterable.toObjectNode(objectNode)) - .toList() - .getFirst()); - }); + filters.getFiltersMap().forEach((key, filterableList) -> + root.setAll( + filterableList + .stream() + .map(filterable -> + filterable.toObjectNode(root)) + .toList() + .getFirst())); - return root.toString(); - } + return root.toString(); + } } diff --git a/nostr-java-event/src/main/java/nostr/event/json/deserializer/PublicKeyDeserializer.java b/nostr-java-event/src/main/java/nostr/event/json/deserializer/PublicKeyDeserializer.java index cf12c1a7b..5fb78dc6b 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/deserializer/PublicKeyDeserializer.java +++ b/nostr-java-event/src/main/java/nostr/event/json/deserializer/PublicKeyDeserializer.java @@ -1,20 +1,15 @@ - package nostr.event.json.deserializer; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; -import java.io.IOException; import nostr.base.PublicKey; +import java.io.IOException; public class PublicKeyDeserializer extends JsonDeserializer { - @Override public PublicKey deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { - JsonNode node = jsonParser.readValueAsTree(); - String hexPubKey = node.asText(); - - return new PublicKey(hexPubKey); + return new PublicKey(jsonParser.readValueAsTree().asText()); } } diff --git a/nostr-java-event/src/main/java/nostr/event/json/deserializer/SignatureDeserializer.java b/nostr-java-event/src/main/java/nostr/event/json/deserializer/SignatureDeserializer.java index 3d7e8c08c..25ef2ee57 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/deserializer/SignatureDeserializer.java +++ b/nostr-java-event/src/main/java/nostr/event/json/deserializer/SignatureDeserializer.java @@ -4,25 +4,16 @@ import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import java.io.IOException; import nostr.base.Signature; import nostr.util.NostrUtil; +import java.io.IOException; public class SignatureDeserializer extends JsonDeserializer { @Override public Signature deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { - ObjectMapper objectMapper = (ObjectMapper) jsonParser.getCodec(); - JsonNode node = objectMapper.readTree(jsonParser); - - String sigValue = node.asText(); - byte[] rawData = NostrUtil.hex128ToBytes(sigValue); - Signature signature = new Signature(); - signature.setRawData(rawData); - + signature.setRawData(NostrUtil.hex128ToBytes(jsonParser.getCodec().readTree(jsonParser).asText())); return signature; } } diff --git a/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java b/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java index 0e3cf1fd6..8ca6697f6 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java +++ b/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java @@ -21,9 +21,11 @@ protected AbstractTagSerializer(Class t) { public void serialize(T value, JsonGenerator gen, SerializerProvider serializers) { try { final ObjectNode node = BASETAG_ENCODER_MAPPED_AFTERBURNER.getNodeFactory().objectNode(); - Streams.failableStream(value.getSupportedFields().stream()).forEach(f -> node.put(f.getName(), value.getFieldValue(f))); + Streams.failableStream( + value.getSupportedFields().stream()).forEach(f -> + node.put(f.getName(), value.getFieldValue(f))); - processNode(node, value); + applyCustomAttributes(node, value); ArrayNode arrayNode = node.objectNode().putArray("values").add(value.getCode()); node.fields().forEachRemaining(entry -> arrayNode.add(entry.getValue().asText())); @@ -33,5 +35,5 @@ public void serialize(T value, JsonGenerator gen, SerializerProvider serializers } } - protected void processNode(ObjectNode node, T value) {} + protected void applyCustomAttributes(ObjectNode node, T value) {} } diff --git a/nostr-java-event/src/main/java/nostr/event/json/serializer/GenericTagSerializer.java b/nostr-java-event/src/main/java/nostr/event/json/serializer/GenericTagSerializer.java index 386adabe5..7a14fb7b9 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/serializer/GenericTagSerializer.java +++ b/nostr-java-event/src/main/java/nostr/event/json/serializer/GenericTagSerializer.java @@ -14,7 +14,7 @@ public GenericTagSerializer() { } @Override - protected void processNode(ObjectNode node, T value) { + protected void applyCustomAttributes(ObjectNode node, T value) { value.getAttributes().forEach(a -> node.put(a.getName(), a.getValue().toString())); } } diff --git a/nostr-java-event/src/main/java/nostr/event/tag/NonceTag.java b/nostr-java-event/src/main/java/nostr/event/tag/NonceTag.java index 370bd8cd7..86f91e0de 100644 --- a/nostr-java-event/src/main/java/nostr/event/tag/NonceTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/NonceTag.java @@ -1,4 +1,3 @@ - package nostr.event.tag; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/nostr-java-event/src/main/java/nostr/event/tag/PubKeyTag.java b/nostr-java-event/src/main/java/nostr/event/tag/PubKeyTag.java index 2c725de4f..6c68816fc 100644 --- a/nostr-java-event/src/main/java/nostr/event/tag/PubKeyTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/PubKeyTag.java @@ -1,8 +1,3 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package nostr.event.tag; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/nostr-java-event/src/main/java/nostr/event/tag/SubjectTag.java b/nostr-java-event/src/main/java/nostr/event/tag/SubjectTag.java index 6950cab64..cf111b59d 100644 --- a/nostr-java-event/src/main/java/nostr/event/tag/SubjectTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/SubjectTag.java @@ -1,4 +1,3 @@ - package nostr.event.tag; import com.fasterxml.jackson.annotation.JsonProperty; From 55057940b6033dc7cfc7bf15567280cd8651e68e Mon Sep 17 00:00:00 2001 From: nick avlo Date: Fri, 21 Mar 2025 18:19:07 -0700 Subject: [PATCH 06/37] cleanup --- .../src/main/java/nostr/event/message/EoseMessage.java | 1 - 1 file changed, 1 deletion(-) diff --git a/nostr-java-event/src/main/java/nostr/event/message/EoseMessage.java b/nostr-java-event/src/main/java/nostr/event/message/EoseMessage.java index 204f09623..4b78c5275 100644 --- a/nostr-java-event/src/main/java/nostr/event/message/EoseMessage.java +++ b/nostr-java-event/src/main/java/nostr/event/message/EoseMessage.java @@ -1,4 +1,3 @@ - package nostr.event.message; import com.fasterxml.jackson.annotation.JsonProperty; From 53877d8f359f6e489f081fe7777a3bcce5b333da Mon Sep 17 00:00:00 2001 From: nick avlo Date: Fri, 21 Mar 2025 19:37:54 -0700 Subject: [PATCH 07/37] basemessagedecor, eventmessage refactor --- .../event/json/codec/BaseMessageDecoder.java | 18 ++----- .../nostr/event/message/EventMessage.java | 18 +++---- .../java/nostr/event/message/ReqMessage.java | 51 +++++++++++-------- 3 files changed, 44 insertions(+), 43 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 b688e1193..efb2d847a 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 @@ -27,7 +27,6 @@ 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; @Override public T decode(@NonNull String jsonString) throws JsonProcessingException { @@ -35,7 +34,8 @@ public T decode(@NonNull String jsonString) throws JsonProcessingException { String command = validJsonNodeFirstPair.formerly_strCmd(); Object subscriptionId = validJsonNodeFirstPair.formerly_arg(); - Object[] msgArr = I_DECODER_MAPPER_AFTERBURNER.readValue(jsonString, Object[].class); // TODO: replace with jsonNode after ReqMessage.decode() is finished + // TODO: replace with jsonNode after ReqMessage.decode() is finished + Object[] msgArr = I_DECODER_MAPPER_AFTERBURNER.readValue(jsonString, Object[].class); return switch (command) { case "AUTH" -> subscriptionId instanceof Map map ? @@ -43,10 +43,10 @@ public T decode(@NonNull String jsonString) throws JsonProcessingException { RelayAuthenticationMessage.decode(subscriptionId); case "CLOSE" -> CloseMessage.decode(subscriptionId); case "EOSE" -> EoseMessage.decode(subscriptionId); - case "EVENT" -> EventMessage.decode(msgArr, I_DECODER_MAPPER_AFTERBURNER); + case "EVENT" -> EventMessage.decode(jsonString); case "NOTICE" -> NoticeMessage.decode(subscriptionId); case "OK" -> OkMessage.decode(msgArr); - case "REQ" -> ReqMessage.decode(subscriptionId, json_msgArr(jsonString)); + case "REQ" -> ReqMessage.decode(subscriptionId, jsonString); default -> GenericMessage.decode(msgArr); }; } @@ -57,16 +57,6 @@ private ValidJsonNodeFirstPair json_strCmd_arg(@NonNull String jsonString) throw I_DECODER_MAPPER_AFTERBURNER.readTree(jsonString).get(ARG_INDEX).asText()); } - private List json_msgArr(@NonNull String jsonString) throws JsonProcessingException { - return IntStream.range(FILTERS_START_INDEX, I_DECODER_MAPPER_AFTERBURNER.readTree(jsonString).size()) - .mapToObj(idx -> readTree(jsonString, idx)).toList(); - } - - @SneakyThrows - private String readTree(String jsonString, int idx) { - return I_DECODER_MAPPER_AFTERBURNER.readTree(jsonString).get(idx).toString(); - } - private record ValidJsonNodeFirstPair( @NonNull String formerly_strCmd, @NonNull Object formerly_arg) {} diff --git a/nostr-java-event/src/main/java/nostr/event/message/EventMessage.java b/nostr-java-event/src/main/java/nostr/event/message/EventMessage.java index 0897e9684..e4ac91912 100644 --- a/nostr-java-event/src/main/java/nostr/event/message/EventMessage.java +++ b/nostr-java-event/src/main/java/nostr/event/message/EventMessage.java @@ -13,11 +13,10 @@ import nostr.event.BaseMessage; import nostr.event.impl.GenericEvent; import nostr.event.json.codec.BaseEventEncoder; - import java.util.Map; import java.util.Optional; - import static nostr.base.Encoder.ENCODER_MAPPED_AFTERBURNER; +import static nostr.base.IDecoder.I_DECODER_MAPPER_AFTERBURNER; @Setter @Getter @@ -43,26 +42,27 @@ public EventMessage(@NonNull IEvent event, String subscriptionId) { public String encode() throws JsonProcessingException { var arrayNode = getArrayNode().add(getCommand()); Optional.ofNullable(getSubscriptionId()) - .ifPresent(arrayNode::add); + .ifPresent(arrayNode::add); arrayNode.add(ENCODER_MAPPED_AFTERBURNER.readTree( - new BaseEventEncoder<>((BaseEvent)getEvent()).encode())); + new BaseEventEncoder<>((BaseEvent)getEvent()).encode())); return ENCODER_MAPPED_AFTERBURNER.writeValueAsString(arrayNode); } -// TODO: refactor into stream returning optional - public static T decode(@NonNull Object[] msgArr, ObjectMapper mapper) { + // TODO: refactor into stream returning optional + public static T decode(@NonNull String jsonString) throws JsonProcessingException { + Object[] msgArr = I_DECODER_MAPPER_AFTERBURNER.readValue(jsonString, Object[].class); var arg = msgArr[1]; if (msgArr.length == 2 && arg instanceof Map map) { return (T) new EventMessage( - convertValue(mapper, map) + convertValue(I_DECODER_MAPPER_AFTERBURNER, map) ); } if (msgArr.length == 3 && arg instanceof String) { if (msgArr[2] instanceof Map map) { return (T) new EventMessage( - convertValue(mapper, map), - arg.toString() + convertValue(I_DECODER_MAPPER_AFTERBURNER, map), + arg.toString() ); } } diff --git a/nostr-java-event/src/main/java/nostr/event/message/ReqMessage.java b/nostr-java-event/src/main/java/nostr/event/message/ReqMessage.java index 18e520ae1..735a12557 100644 --- a/nostr-java-event/src/main/java/nostr/event/message/ReqMessage.java +++ b/nostr-java-event/src/main/java/nostr/event/message/ReqMessage.java @@ -6,17 +6,18 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NonNull; +import lombok.SneakyThrows; import lombok.ToString; import nostr.base.Command; import nostr.event.BaseMessage; import nostr.event.filter.Filters; import nostr.event.json.codec.FiltersDecoder; import nostr.event.json.codec.FiltersEncoder; - import java.time.temporal.ValueRange; import java.util.List; - +import java.util.stream.IntStream; import static nostr.base.Encoder.ENCODER_MAPPED_AFTERBURNER; +import static nostr.base.IDecoder.I_DECODER_MAPPER_AFTERBURNER; /** * @author squirrel @@ -25,6 +26,8 @@ @EqualsAndHashCode(callSuper = false) @ToString(callSuper = true) public class ReqMessage extends BaseMessage { + public static final int FILTERS_START_INDEX = 2; + @JsonProperty private final String subscriptionId; @@ -45,26 +48,32 @@ public ReqMessage(@NonNull String subscriptionId, List filtersList) { @Override public String encode() throws JsonProcessingException { getArrayNode() - .add(getCommand()) - .add(getSubscriptionId()); + .add(getCommand()) + .add(getSubscriptionId()); filtersList.stream() - .map(FiltersEncoder::new) - .map(FiltersEncoder::encode) - .map(ReqMessage::createJsonNode) - .forEach(jsonNode -> - getArrayNode().add(jsonNode)); + .map(FiltersEncoder::new) + .map(FiltersEncoder::encode) + .map(ReqMessage::createJsonNode) + .forEach(jsonNode -> getArrayNode().add(jsonNode)); return ENCODER_MAPPED_AFTERBURNER.writeValueAsString(getArrayNode()); } - public static T decode(@NonNull Object subscriptionId, @NonNull List jsonFiltersList) { + private static JsonNode createJsonNode(String jsonNode) { + try { + return ENCODER_MAPPED_AFTERBURNER.readTree(jsonNode); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException(String.format("Malformed encoding ReqMessage json: [%s]", jsonNode), e); + } + } + + public static T decode(@NonNull Object subscriptionId, @NonNull String jsonString) throws JsonProcessingException { validateSubscriptionId(subscriptionId.toString()); - ReqMessage reqMessage = new ReqMessage( - subscriptionId.toString(), - jsonFiltersList.stream().map(filtersList -> + return (T) new ReqMessage( + subscriptionId.toString(), + getJsonFiltersList(jsonString).stream().map(filtersList -> new FiltersDecoder().decode(filtersList)).toList()); - return (T) reqMessage; } private static void validateSubscriptionId(String subscriptionId) { @@ -73,11 +82,13 @@ private static void validateSubscriptionId(String subscriptionId) { } } - private static JsonNode createJsonNode(String jsonNode) { - try { - return ENCODER_MAPPED_AFTERBURNER.readTree(jsonNode); - } catch (JsonProcessingException e) { - throw new IllegalArgumentException(String.format("Malformed encoding ReqMessage json: [%s]", jsonNode), e); - } + private static List getJsonFiltersList(@NonNull String jsonString) throws JsonProcessingException { + return IntStream.range(FILTERS_START_INDEX, I_DECODER_MAPPER_AFTERBURNER.readTree(jsonString).size()) + .mapToObj(idx -> readTree(jsonString, idx)).toList(); + } + + @SneakyThrows + private static String readTree(String jsonString, int idx) { + return I_DECODER_MAPPER_AFTERBURNER.readTree(jsonString).get(idx).toString(); } } From ace31b8087decb04ac12c28ced3a6def22c403e8 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Fri, 21 Mar 2025 21:17:18 -0700 Subject: [PATCH 08/37] EventMessage refactor & cleanup --- .../nostr/event/message/EventMessage.java | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/nostr-java-event/src/main/java/nostr/event/message/EventMessage.java b/nostr-java-event/src/main/java/nostr/event/message/EventMessage.java index e4ac91912..c401e76c3 100644 --- a/nostr-java-event/src/main/java/nostr/event/message/EventMessage.java +++ b/nostr-java-event/src/main/java/nostr/event/message/EventMessage.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.NonNull; import lombok.Setter; @@ -14,13 +13,18 @@ import nostr.event.impl.GenericEvent; import nostr.event.json.codec.BaseEventEncoder; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.function.Function; import static nostr.base.Encoder.ENCODER_MAPPED_AFTERBURNER; import static nostr.base.IDecoder.I_DECODER_MAPPER_AFTERBURNER; @Setter @Getter public class EventMessage extends BaseMessage { + private static final int SIZE_JSON_EVENT_wo_SIG_ID = 2; + private static final Function isEventWoSig = (objArr) -> + Objects.equals(SIZE_JSON_EVENT_wo_SIG_ID, objArr.length); @JsonProperty private final IEvent event; @@ -42,36 +46,30 @@ public EventMessage(@NonNull IEvent event, String subscriptionId) { public String encode() throws JsonProcessingException { var arrayNode = getArrayNode().add(getCommand()); Optional.ofNullable(getSubscriptionId()) - .ifPresent(arrayNode::add); + .ifPresent(arrayNode::add); arrayNode.add(ENCODER_MAPPED_AFTERBURNER.readTree( - new BaseEventEncoder<>((BaseEvent)getEvent()).encode())); + new BaseEventEncoder<>((BaseEvent) getEvent()).encode())); return ENCODER_MAPPED_AFTERBURNER.writeValueAsString(arrayNode); } - // TODO: refactor into stream returning optional - public static T decode(@NonNull String jsonString) throws JsonProcessingException { - Object[] msgArr = I_DECODER_MAPPER_AFTERBURNER.readValue(jsonString, Object[].class); - var arg = msgArr[1]; - if (msgArr.length == 2 && arg instanceof Map map) { - return (T) new EventMessage( - convertValue(I_DECODER_MAPPER_AFTERBURNER, map) - ); + public static T decode(@NonNull String jsonString) { + try { + Object[] msgArr = I_DECODER_MAPPER_AFTERBURNER.readValue(jsonString, Object[].class); + return isEventWoSig.apply(msgArr) ? processEvent(msgArr[1]) : processEvent(msgArr); + } catch (Exception e) { + throw new AssertionError("Invalid argument: " + jsonString); } + } - if (msgArr.length == 3 && arg instanceof String) { - if (msgArr[2] instanceof Map map) { - return (T) new EventMessage( - convertValue(I_DECODER_MAPPER_AFTERBURNER, map), - arg.toString() - ); - } - } + public static T processEvent(Object o) { + return (T) new EventMessage(convertValue((Map) o)); + } - throw new AssertionError("Invalid argument: " + arg); + public static T processEvent(Object[] msgArr) { + return (T) new EventMessage(convertValue((Map) msgArr[2]), msgArr[1].toString()); } - private static GenericEvent convertValue(ObjectMapper mapper, Map map) { - return mapper.convertValue(map, new TypeReference() { - }); + private static GenericEvent convertValue(Map map) { + return nostr.base.IDecoder.I_DECODER_MAPPER_AFTERBURNER.convertValue(map, new TypeReference<>() {}); } } From 85b8fcff4cf944007adcdc1d52e86d36c6f0612d Mon Sep 17 00:00:00 2001 From: nick avlo Date: Sat, 22 Mar 2025 13:08:37 -0700 Subject: [PATCH 09/37] BaseMessageDecoder and downstream Message classes cleaned up / refactored --- .../nostr/api/integration/ApiEventIT.java | 26 +++---- .../java/nostr/event/impl/GenericMessage.java | 73 +++++++++++++------ .../event/json/codec/BaseMessageDecoder.java | 26 +++---- .../nostr/event/message/EventMessage.java | 8 +- .../java/nostr/event/message/OkMessage.java | 19 ++--- 5 files changed, 86 insertions(+), 66 deletions(-) diff --git a/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java b/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java index e72f5527d..3bdc0b149 100644 --- a/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java +++ b/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java @@ -82,7 +82,7 @@ public void testNIP01CreateTextNoteEvent() throws Exception { } @Test - public void testNIP01SendTextNoteEvent() throws IOException { + public void testNIP01SendTextNoteEvent() { System.out.println("testNIP01SendTextNoteEvent"); var nip01 = new NIP01(Identity.generateRandomIdentity()); @@ -96,7 +96,7 @@ public void testNIP01SendTextNoteEvent() throws IOException { } @Test - public void testNIP04SendDirectMessage() throws IOException { + public void testNIP04SendDirectMessage() { System.out.println("testNIP04SendDirectMessage"); var nip04 = new NIP04( @@ -116,7 +116,7 @@ public void testNIP04SendDirectMessage() throws IOException { } @Test - public void testNIP44SendDirectMessage() throws IOException { + public void testNIP44SendDirectMessage() { System.out.println("testNIP44SendDirectMessage"); var nip44 = new NIP44( @@ -133,7 +133,7 @@ public void testNIP44SendDirectMessage() throws IOException { } @Test - public void testNIP01SendTextNoteEventGeoHashTag() throws IOException { + public void testNIP01SendTextNoteEventGeoHashTag() { System.out.println("testNIP01SendTextNoteEventGeoHashTag"); String targetString = "geohash_tag-location-testNIP01SendTextNoteEventGeoHashTag"; @@ -155,7 +155,7 @@ public void testNIP01SendTextNoteEventGeoHashTag() throws IOException { } @Test - public void testNIP01SendTextNoteEventHashtagTag() throws IOException { + public void testNIP01SendTextNoteEventHashtagTag() { System.out.println("testNIP01SendTextNoteEventHashtagTag"); String targetString = "hashtag-tag-value-testNIP01SendTextNoteEventHashtagTag"; @@ -177,7 +177,7 @@ public void testNIP01SendTextNoteEventHashtagTag() throws IOException { } @Test - public void testNIP01SendTextNoteEventCustomGenericTag() throws IOException { + public void testNIP01SendTextNoteEventCustomGenericTag() { System.out.println("testNIP01SendTextNoteEventCustomGenericTag"); String targetString = "custom-generic-tag-testNIP01SendTextNoteEventCustomGenericTag"; @@ -203,7 +203,7 @@ public void testNIP01SendTextNoteEventCustomGenericTag() throws IOException { } @Test - public void testFiltersListReturnSameSingularEvent() throws IOException { + public void testFiltersListReturnSameSingularEvent() { System.out.println("testFiltersListReturnSameSingularEvent"); String geoHashTagTarget = "geohash_tag-location_SameSingularEvent"; @@ -231,7 +231,7 @@ public void testFiltersListReturnSameSingularEvent() throws IOException { } @Test - public void testFiltersListReturnTwoDifferentEvents() throws IOException { + public void testFiltersListReturnTwoDifferentEvents() { System.out.println("testFiltersListReturnTwoDifferentEvents"); // first event @@ -268,7 +268,7 @@ public void testFiltersListReturnTwoDifferentEvents() throws IOException { } @Test - public void testMultipleFiltersDifferentTypesReturnSameEvent() throws IOException { + public void testMultipleFiltersDifferentTypesReturnSameEvent() { System.out.println("testMultipleFilters"); String geoHashTagTarget = "geohash_tag-location-DifferentTypesReturnSameEvent"; @@ -371,7 +371,7 @@ public void testNIP15CreateStallEvent() throws JsonProcessingException { } @Test - public void testNIP15UpdateStallEvent() throws IOException { + public void testNIP15UpdateStallEvent() { System.out.println("testNIP15UpdateStallEvent"); var stall = createStall(); @@ -399,7 +399,7 @@ public void testNIP15UpdateStallEvent() throws IOException { } @Test - public void testNIP15CreateProductEvent() throws IOException { + public void testNIP15CreateProductEvent() { System.out.println("testNIP15CreateProductEvent"); @@ -423,7 +423,7 @@ public void testNIP15CreateProductEvent() throws IOException { } @Test - public void testNIP15UpdateProductEvent() throws IOException { + public void testNIP15UpdateProductEvent() { System.out.println("testNIP15UpdateProductEvent"); @@ -497,7 +497,7 @@ public void testNIP32CreateLabel2() { } @Test - public void testNIP52CalendarTimeBasedEventEvent() throws IOException { + public void testNIP52CalendarTimeBasedEventEvent() { System.out.println("testNIP52CalendarTimeBasedEventEvent"); CalendarContent calendarContent = CalendarContent.builder( diff --git a/nostr-java-event/src/main/java/nostr/event/impl/GenericMessage.java b/nostr-java-event/src/main/java/nostr/event/impl/GenericMessage.java index c96d40e63..79ba8469f 100644 --- a/nostr-java-event/src/main/java/nostr/event/impl/GenericMessage.java +++ b/nostr-java-event/src/main/java/nostr/event/impl/GenericMessage.java @@ -9,44 +9,54 @@ import nostr.base.IElement; import nostr.base.IGenericElement; import nostr.event.BaseMessage; - import java.util.ArrayList; import java.util.List; - +import java.util.stream.IntStream; import static nostr.base.Encoder.ENCODER_MAPPED_AFTERBURNER; - -/** - * - * @author squirrel - */ +import static nostr.base.IDecoder.I_DECODER_MAPPER_AFTERBURNER; @Setter @Getter public class GenericMessage extends BaseMessage implements IGenericElement, IElement { - @JsonIgnore private final List attributes; - public GenericMessage(String command) { - this(command, new ArrayList<>(), 1); + public GenericMessage(@NonNull String command) { + this(command, new ArrayList<>()); } - public GenericMessage(String command, Integer nip) { - this(command, new ArrayList<>(), nip); + /** + * nip ctor parameter to be removed + * + * @deprecated use any available proper constructor variant instead + */ + @Deprecated(forRemoval = true) + public GenericMessage(@NonNull String command, @NonNull Integer nip) { + this(command, new ArrayList<>()); } - public GenericMessage(String command, List attributes, Integer nip) { + public GenericMessage(@NonNull String command, @NonNull List attributes) { super(command); this.attributes = attributes; } + /** + * nip ctor parameter to be removed + * + * @deprecated use any available proper constructor variant instead + */ + @Deprecated(forRemoval = true) + public GenericMessage(@NonNull String command, @NonNull List attributes, @NonNull Integer nip) { + this(command, attributes); + } + @Override - public void addAttribute(ElementAttribute... attribute) { + public void addAttribute(@NonNull ElementAttribute... attribute) { addAttributes(List.of(attribute)); } @Override - public void addAttributes(List attributes) { + public void addAttributes(@NonNull List attributes) { this.attributes.addAll(attributes); } @@ -57,13 +67,34 @@ public String encode() throws JsonProcessingException { return ENCODER_MAPPED_AFTERBURNER.writeValueAsString(getArrayNode()); } - public static T decode(@NonNull Object[] msgArr) { - GenericMessage gm = new GenericMessage(msgArr[0].toString()); - for (int i = 1; i < msgArr.length; i++) { - if (msgArr[i] instanceof String) { - gm.addAttribute(ElementAttribute.builder().value(msgArr[i]).build()); + public static T decode(@NonNull String jsonString) { + try { + Object[] msgArr = I_DECODER_MAPPER_AFTERBURNER.readValue(jsonString, Object[].class); + GenericMessage gm = new GenericMessage(msgArr[0].toString()); + for (int i = 1; i < msgArr.length; i++) { +// TODO: does below ever resolve to String? because RxR stream says it'll always be false. check eric's tests and see what's happening there + if (msgArr[i] instanceof String) { + gm.addAttribute(ElementAttribute.builder().value(msgArr[i]).build()); + } } + return (T) gm; + } catch (Exception e) { + throw new AssertionError(e); + } + } + + public static T decodeRxR(@NonNull String json) { + try { + Object[] msgArr = I_DECODER_MAPPER_AFTERBURNER.readValue(json, Object[].class); + GenericMessage gm = new GenericMessage( + msgArr[0].toString(), + IntStream.of(1, msgArr.length-1) + .mapToObj(i -> ElementAttribute.builder().value(msgArr[i]).build()) + .distinct() + .toList()); + return (T) gm; + } catch (Exception ex) { + throw new AssertionError(ex); } - return (T) gm; } } 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 efb2d847a..6671eb260 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 @@ -3,7 +3,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import lombok.NoArgsConstructor; import lombok.NonNull; -import lombok.SneakyThrows; import nostr.base.IDecoder; import nostr.event.BaseMessage; import nostr.event.impl.GenericMessage; @@ -16,9 +15,7 @@ import nostr.event.message.RelayAuthenticationMessage; import nostr.event.message.ReqMessage; -import java.util.List; import java.util.Map; -import java.util.stream.IntStream; /** * @author eric @@ -30,12 +27,9 @@ public class BaseMessageDecoder implements IDecoder { @Override public T decode(@NonNull String jsonString) throws JsonProcessingException { - ValidJsonNodeFirstPair validJsonNodeFirstPair = json_strCmd_arg(jsonString); - String command = validJsonNodeFirstPair.formerly_strCmd(); - Object subscriptionId = validJsonNodeFirstPair.formerly_arg(); - - // TODO: replace with jsonNode after ReqMessage.decode() is finished - Object[] msgArr = I_DECODER_MAPPER_AFTERBURNER.readValue(jsonString, Object[].class); + ValidNostrJsonStructure validNostrJsonStructure = validateProperlyFormedJson(jsonString); + String command = validNostrJsonStructure.getCommand(); + Object subscriptionId = validNostrJsonStructure.getSubscriptionId(); return switch (command) { case "AUTH" -> subscriptionId instanceof Map map ? @@ -45,19 +39,19 @@ public T decode(@NonNull String jsonString) throws JsonProcessingException { case "EOSE" -> EoseMessage.decode(subscriptionId); case "EVENT" -> EventMessage.decode(jsonString); case "NOTICE" -> NoticeMessage.decode(subscriptionId); - case "OK" -> OkMessage.decode(msgArr); + case "OK" -> OkMessage.decode(jsonString); case "REQ" -> ReqMessage.decode(subscriptionId, jsonString); - default -> GenericMessage.decode(msgArr); + default -> GenericMessage.decode(jsonString); }; } - private ValidJsonNodeFirstPair json_strCmd_arg(@NonNull String jsonString) throws JsonProcessingException { - return new ValidJsonNodeFirstPair( + private ValidNostrJsonStructure validateProperlyFormedJson(@NonNull String jsonString) throws JsonProcessingException { + return new ValidNostrJsonStructure( I_DECODER_MAPPER_AFTERBURNER.readTree(jsonString).get(COMMAND_INDEX).asText(), I_DECODER_MAPPER_AFTERBURNER.readTree(jsonString).get(ARG_INDEX).asText()); } - private record ValidJsonNodeFirstPair( - @NonNull String formerly_strCmd, - @NonNull Object formerly_arg) {} + private record ValidNostrJsonStructure( + @NonNull String getCommand, + @NonNull Object getSubscriptionId) {} } diff --git a/nostr-java-event/src/main/java/nostr/event/message/EventMessage.java b/nostr-java-event/src/main/java/nostr/event/message/EventMessage.java index c401e76c3..7853ad2d0 100644 --- a/nostr-java-event/src/main/java/nostr/event/message/EventMessage.java +++ b/nostr-java-event/src/main/java/nostr/event/message/EventMessage.java @@ -33,12 +33,12 @@ public class EventMessage extends BaseMessage { private String subscriptionId; public EventMessage(@NonNull IEvent event) { - this(event, null); - } - - public EventMessage(@NonNull IEvent event, String subscriptionId) { super(Command.EVENT.name()); this.event = event; + } + + public EventMessage(@NonNull IEvent event, @NonNull String subscriptionId) { + this(event); this.subscriptionId = subscriptionId; } diff --git a/nostr-java-event/src/main/java/nostr/event/message/OkMessage.java b/nostr-java-event/src/main/java/nostr/event/message/OkMessage.java index 4c8375b12..3836d3a32 100644 --- a/nostr-java-event/src/main/java/nostr/event/message/OkMessage.java +++ b/nostr-java-event/src/main/java/nostr/event/message/OkMessage.java @@ -7,13 +7,9 @@ import lombok.Setter; import nostr.base.Command; import nostr.event.BaseMessage; - import static nostr.base.Encoder.ENCODER_MAPPED_AFTERBURNER; +import static nostr.base.IDecoder.I_DECODER_MAPPER_AFTERBURNER; -/** - * - * @author squirrel - */ @Setter @Getter public class OkMessage extends BaseMessage { @@ -44,13 +40,12 @@ public String encode() throws JsonProcessingException { .add(getMessage())); } - // TODO: refactor into stream returning optional - public static T decode(@NonNull Object[] msgArr) { - if (msgArr.length == 4 && msgArr[2] instanceof Boolean duplicate) { - String msgArg = msgArr[3].toString(); - return (T) new OkMessage(msgArr[1].toString(), duplicate, msgArg); - } else { - throw new AssertionError("Invalid argument: " + msgArr[2]); + public static T decode(@NonNull String jsonString) { + try { + Object[] msgArr = I_DECODER_MAPPER_AFTERBURNER.readValue(jsonString, Object[].class); + return (T) new OkMessage(msgArr[1].toString(), (Boolean) msgArr[2], msgArr[3].toString()); + } catch (JsonProcessingException e) { + throw new AssertionError(e); } } } From 0a6a873e5809bc13eeb12231b836afc55d9dbdeb Mon Sep 17 00:00:00 2001 From: nick avlo Date: Sat, 22 Mar 2025 21:37:51 -0700 Subject: [PATCH 10/37] add spring boot junit retry --- .../{RelayProperties.java => RelayConfig.java} | 2 +- .../test/java/nostr/api/integration/ApiEventIT.java | 5 ++--- .../ApiEventTestUsingSpringWebSocketClientIT.java | 4 ++-- .../api/integration/ZDoLastApiNIP09EventIT.java | 5 +++-- pom.xml | 13 +++++++++++++ 5 files changed, 21 insertions(+), 8 deletions(-) rename nostr-java-api/src/main/java/nostr/config/{RelayProperties.java => RelayConfig.java} (95%) diff --git a/nostr-java-api/src/main/java/nostr/config/RelayProperties.java b/nostr-java-api/src/main/java/nostr/config/RelayConfig.java similarity index 95% rename from nostr-java-api/src/main/java/nostr/config/RelayProperties.java rename to nostr-java-api/src/main/java/nostr/config/RelayConfig.java index ae8d4f066..41c2d5972 100644 --- a/nostr-java-api/src/main/java/nostr/config/RelayProperties.java +++ b/nostr-java-api/src/main/java/nostr/config/RelayConfig.java @@ -10,7 +10,7 @@ @Configuration @PropertySource("classpath:relays.properties") -public class RelayProperties { +public class RelayConfig { @Bean public Map relays() { diff --git a/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java b/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java index 3bdc0b149..03af0b732 100644 --- a/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java +++ b/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java @@ -12,7 +12,7 @@ import nostr.base.ElementAttribute; import nostr.base.GenericTagQuery; import nostr.base.PrivateKey; -import nostr.config.RelayProperties; +import nostr.config.RelayConfig; import nostr.crypto.bech32.Bech32; import nostr.crypto.bech32.Bech32Prefix; import nostr.event.BaseTag; @@ -42,7 +42,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import java.io.IOException; import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; @@ -54,7 +53,7 @@ import static org.awaitility.Awaitility.await; import static org.junit.jupiter.api.Assertions.*; -@SpringJUnitConfig(RelayProperties.class) +@SpringJUnitConfig(RelayConfig.class) public class ApiEventIT { @Autowired private Map relays; diff --git a/nostr-java-api/src/test/java/nostr/api/integration/ApiEventTestUsingSpringWebSocketClientIT.java b/nostr-java-api/src/test/java/nostr/api/integration/ApiEventTestUsingSpringWebSocketClientIT.java index b6b38c8da..93d6d7bbb 100644 --- a/nostr-java-api/src/test/java/nostr/api/integration/ApiEventTestUsingSpringWebSocketClientIT.java +++ b/nostr-java-api/src/test/java/nostr/api/integration/ApiEventTestUsingSpringWebSocketClientIT.java @@ -4,7 +4,7 @@ import nostr.api.NIP15; import nostr.base.PrivateKey; import nostr.client.springwebsocket.SpringWebSocketClient; -import nostr.config.RelayProperties; +import nostr.config.RelayConfig; import nostr.event.impl.GenericEvent; import nostr.event.message.EventMessage; import nostr.id.Identity; @@ -22,7 +22,7 @@ import static nostr.base.IEvent.MAPPER_AFTERBURNER; import static org.junit.jupiter.api.Assertions.assertEquals; -@SpringJUnitConfig(RelayProperties.class) +@SpringJUnitConfig(RelayConfig.class) @ActiveProfiles("test") class ApiEventTestUsingSpringWebSocketClientIT { private final List springWebSocketClients; diff --git a/nostr-java-api/src/test/java/nostr/api/integration/ZDoLastApiNIP09EventIT.java b/nostr-java-api/src/test/java/nostr/api/integration/ZDoLastApiNIP09EventIT.java index 95857593d..edc366f6a 100644 --- a/nostr-java-api/src/test/java/nostr/api/integration/ZDoLastApiNIP09EventIT.java +++ b/nostr-java-api/src/test/java/nostr/api/integration/ZDoLastApiNIP09EventIT.java @@ -3,7 +3,7 @@ import nostr.api.NIP01; import nostr.api.NIP09; import nostr.base.Relay; -import nostr.config.RelayProperties; +import nostr.config.RelayConfig; import nostr.event.BaseMessage; import nostr.event.BaseTag; import nostr.event.Kind; @@ -31,8 +31,9 @@ import static org.junit.jupiter.api.Assertions.*; -@SpringJUnitConfig(RelayProperties.class) +@SpringJUnitConfig(RelayConfig.class) @ActiveProfiles("test") +@Retry public class ZDoLastApiNIP09EventIT { @Autowired private Map relays; diff --git a/pom.xml b/pom.xml index 59ab17bd1..3c4a02641 100644 --- a/pom.xml +++ b/pom.xml @@ -100,6 +100,7 @@ 3.3.0 1.6.0 0.8.12 + 2.0.10 @@ -148,6 +149,18 @@ ${guava.version} test + + org.springframework.retry + spring-retry + ${spring-retry.version} + test + + + org.springframework + spring-aspects + 6.2.3 + test + From 6601bd0ed63b692ce514b25080192cc93324cc42 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Sat, 22 Mar 2025 21:43:59 -0700 Subject: [PATCH 11/37] add and set failsafe retry count to 1 --- .../api/integration/ZDoLastApiNIP09EventIT.java | 1 - pom.xml | 16 +++------------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/nostr-java-api/src/test/java/nostr/api/integration/ZDoLastApiNIP09EventIT.java b/nostr-java-api/src/test/java/nostr/api/integration/ZDoLastApiNIP09EventIT.java index edc366f6a..50ea7b6d9 100644 --- a/nostr-java-api/src/test/java/nostr/api/integration/ZDoLastApiNIP09EventIT.java +++ b/nostr-java-api/src/test/java/nostr/api/integration/ZDoLastApiNIP09EventIT.java @@ -33,7 +33,6 @@ @SpringJUnitConfig(RelayConfig.class) @ActiveProfiles("test") -@Retry public class ZDoLastApiNIP09EventIT { @Autowired private Map relays; diff --git a/pom.xml b/pom.xml index 3c4a02641..9f2ae4beb 100644 --- a/pom.xml +++ b/pom.xml @@ -100,7 +100,6 @@ 3.3.0 1.6.0 0.8.12 - 2.0.10 @@ -149,18 +148,6 @@ ${guava.version} test - - org.springframework.retry - spring-retry - ${spring-retry.version} - test - - - org.springframework - spring-aspects - 6.2.3 - test - @@ -304,6 +291,9 @@ integration-test verify + + 1 + From 2f68096a4b944c4ca776e3d1c18957f589ab6442 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Wed, 26 Mar 2025 01:35:50 -0700 Subject: [PATCH 12/37] gradle test/check phase diff, pom reorg --- .../main/groovy/nostr-java.conventions.gradle | 7 ++- nostr-java-api/build.gradle | 20 ++++++++ pom.xml | 49 +++++++------------ 3 files changed, 44 insertions(+), 32 deletions(-) diff --git a/buildSrc/src/main/groovy/nostr-java.conventions.gradle b/buildSrc/src/main/groovy/nostr-java.conventions.gradle index cfa437d7a..b6e19f20b 100644 --- a/buildSrc/src/main/groovy/nostr-java.conventions.gradle +++ b/buildSrc/src/main/groovy/nostr-java.conventions.gradle @@ -72,7 +72,6 @@ dependencies { testImplementation 'org.awaitility:awaitility:' + awaitility testImplementation 'com.adarshr:gradle-test-logger-plugin:' + logger - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testAnnotationProcessor 'org.projectlombok:lombok:' + lombok } @@ -93,6 +92,12 @@ tasks.test { // } } +test { + filter { + excludeTestsMatching("nostr.api.integration.*"); + } +} + tasks.bootJar { enabled = false } diff --git a/nostr-java-api/build.gradle b/nostr-java-api/build.gradle index c07288efb..6d8ec99ad 100644 --- a/nostr-java-api/build.gradle +++ b/nostr-java-api/build.gradle @@ -8,3 +8,23 @@ dependencies { api project(':nostr-java-client') api project(':nostr-java-encryption') } + +tasks.register('integrationTest', Test) { + description = 'api integration tests.' + group = 'verification' + systemProperty("spring.profiles.active", "test") + useJUnitPlatform() + filter { + excludeTestsMatching("nostr.api.unit.*"); + includeTestsMatching("nostr.api.integration.*"); + } +} + +integrationTest { + retry { + failOnPassedAfterRetry = false + maxRetries = 1 + } +} + +check.dependsOn integrationTest diff --git a/pom.xml b/pom.xml index 9f2ae4beb..5de38fe04 100644 --- a/pom.xml +++ b/pom.xml @@ -248,11 +248,26 @@ org.apache.maven.plugins maven-surefire-plugin ${maven-surefire-plugin.version} + + + + test + + + org.jacoco jacoco-maven-plugin ${jacoco-maven-plugin.version} + + + + prepare-agent + report + + + org.apache.maven.plugins @@ -264,6 +279,9 @@ integration-test verify + + 1 + @@ -274,49 +292,18 @@ maven-surefire-plugin ${maven-surefire-plugin.version} - - - - test - - - maven-failsafe-plugin ${maven-failsafe-plugin.version} - - - - integration-test - verify - - - 1 - - - org.jacoco jacoco-maven-plugin - ${jacoco-maven-plugin.version} - - - - prepare-agent - report - - - org.apache.maven.plugins maven-compiler-plugin - - ${maven.compiler.source} - ${maven.compiler.target} - From 351c9a40f70660fa8fd36d0a5d0223c3c6495fc5 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Wed, 26 Mar 2025 02:40:07 -0700 Subject: [PATCH 13/37] add failed test assertion --- .../unit/BaseMessageCommandMapperTest.java | 145 +++++++++++------- 1 file changed, 86 insertions(+), 59 deletions(-) diff --git a/nostr-java-event/src/test/java/nostr/event/unit/BaseMessageCommandMapperTest.java b/nostr-java-event/src/test/java/nostr/event/unit/BaseMessageCommandMapperTest.java index b2c2621f5..4d421109f 100644 --- a/nostr-java-event/src/test/java/nostr/event/unit/BaseMessageCommandMapperTest.java +++ b/nostr-java-event/src/test/java/nostr/event/unit/BaseMessageCommandMapperTest.java @@ -7,66 +7,93 @@ import nostr.event.message.EoseMessage; import nostr.event.message.ReqMessage; import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; @Log public class BaseMessageCommandMapperTest { -// TODO: flesh out remaining commands - public final static String REQ_JSON = - "[\"REQ\", " + - "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + - "{\"kinds\": [1], " + - "\"authors\": [\"f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75\"]," + - "\"#e\": [\"fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712\"]}]"; - - @Test - public void testReqMessageDecoder() throws JsonProcessingException { - log.info("testReqMessageDecoder"); - - BaseMessage decode = new BaseMessageDecoder<>().decode(REQ_JSON); - assertInstanceOf(ReqMessage.class, decode); - } - - @Test - public void testReqMessageDecoderType() { - log.info("testReqMessageDecoderType"); - - assertDoesNotThrow(() -> { - new BaseMessageDecoder().decode(REQ_JSON); - }); - - assertDoesNotThrow(() -> { - ReqMessage reqMessage = new BaseMessageDecoder().decode(REQ_JSON); - }); - } - - @Test - public void testReqMessageDecoderThrows() { - log.info("testReqMessageDecoderThrows"); - - assertThrows(ClassCastException.class, () -> { - EoseMessage decode = new BaseMessageDecoder().decode(REQ_JSON); - }); - } - - @Test - public void testReqMessageDecoderDoesNotThrow() { - log.info("testReqMessageDecoderDoesNotThrow"); - - assertDoesNotThrow(() -> { - new BaseMessageDecoder().decode(REQ_JSON); - }); - } - - @Test - public void testReqMessageDecoderThrows3() { - log.info("testReqMessageDecoderThrows"); - - assertThrows(ClassCastException.class, () -> { - EoseMessage decode = new BaseMessageDecoder().decode(REQ_JSON); - }); - } + // TODO: flesh out remaining commands + public final static String REQ_JSON = + "[\"REQ\", " + + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + + "{\"kinds\": [1], " + + "\"authors\": [\"f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75\"]," + + "\"#e\": [\"fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712\"]}]"; + + public final static String INVALID_COMMAND_JSON = + "[\"OTHER\", " + + "\"npub17x6pn22ukq3n5yw5x9prksdyyu6ww9jle2ckpqwdprh3ey8qhe6stnpujh\", " + + "{\"kinds\": [1], " + + "\"authors\": [\"f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75\"]," + + "\"#e\": [\"fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712\"]}]"; + + @Test + void testReqMessageDecoder() throws JsonProcessingException { + log.info("testReqMessageDecoder"); + + BaseMessage decode = new BaseMessageDecoder<>().decode(REQ_JSON); + assertInstanceOf(ReqMessage.class, decode); + } + + @Test + void testReqMessageDecoderType() { + log.info("testReqMessageDecoderType"); + + assertDoesNotThrow(() -> { + new BaseMessageDecoder().decode(REQ_JSON); + }); + + assertDoesNotThrow(() -> { + ReqMessage reqMessage = new BaseMessageDecoder().decode(REQ_JSON); + }); + } + + @Test + void testReqMessageDecoderThrows() { + log.info("testReqMessageDecoderThrows"); + + assertThrows(ClassCastException.class, () -> { + EoseMessage decode = new BaseMessageDecoder().decode(REQ_JSON); + }); + } + + @Test + void testReqMessageDecoderDoesNotThrow() { + log.info("testReqMessageDecoderDoesNotThrow"); + + assertDoesNotThrow(() -> { + new BaseMessageDecoder().decode(REQ_JSON); + }); + } + + @Test + void testReqMessageDecoderThrows3() { + log.info("testReqMessageDecoderThrows"); + + assertThrows(ClassCastException.class, () -> { + EoseMessage decode = new BaseMessageDecoder().decode(REQ_JSON); + }); + } + + @Test + void testInvalidMessageDecoder() { + log.info("testInvalidMessageDecoder"); + + assertThrows(IllegalArgumentException.class, () -> { + new BaseMessageDecoder().decode(INVALID_COMMAND_JSON); + }); + } + +// @Test +// void assertionFail() { +// assertEquals(1, 2); +// } + +// @Test +// void catastrophicTestFail() { +// log.info("makeSureIntegrationTestsFailhere"); +// +// assertDoesNotThrow(() -> { +// new BaseMessageDecoder().decode(INVALID_COMMAND_JSON); +// }); +// } } From ea558cfb7048826a222fa6d090b1bdfb8ee08d0d Mon Sep 17 00:00:00 2001 From: nick avlo Date: Wed, 26 Mar 2025 22:23:50 -0700 Subject: [PATCH 14/37] build.gradle & conventions minimizing --- .../src/main/groovy/nostr-java.conventions.gradle | 15 +-------------- nostr-java-api/build.gradle | 6 +++--- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/buildSrc/src/main/groovy/nostr-java.conventions.gradle b/buildSrc/src/main/groovy/nostr-java.conventions.gradle index b6e19f20b..526ea811d 100644 --- a/buildSrc/src/main/groovy/nostr-java.conventions.gradle +++ b/buildSrc/src/main/groovy/nostr-java.conventions.gradle @@ -15,7 +15,7 @@ plugins { id 'org.gradle.test-retry' } -group = version = rootProject.property("nostr-java.group") +group = rootProject.property("nostr-java.group") version = rootProject.property("nostr-java.version") description = rootProject.property("nostr-java.description") @@ -38,7 +38,6 @@ publishing { } } - dependencies { def springBootVersion = rootProject.property("nostr-java.springBootVersion") def apacheCommonsLang3 = rootProject.property("nostr-java.apacheCommonsLang3") @@ -75,21 +74,9 @@ dependencies { testAnnotationProcessor 'org.projectlombok:lombok:' + lombok } -configurations { - compileOnly { - extendsFrom annotationProcessor - } -} - tasks.test { systemProperty("spring.profiles.active", "test") useJUnitPlatform() -// retry { -// maxRetries.set(1) -//// maxFailures.set(20) -//// failOnPassedAfterRetry.set(false) -//// failOnSkippedAfterRetry.set(true) -// } } test { diff --git a/nostr-java-api/build.gradle b/nostr-java-api/build.gradle index 6d8ec99ad..639865ef7 100644 --- a/nostr-java-api/build.gradle +++ b/nostr-java-api/build.gradle @@ -9,7 +9,7 @@ dependencies { api project(':nostr-java-encryption') } -tasks.register('integrationTest', Test) { +tasks.register('apiIntegrationTest', Test) { description = 'api integration tests.' group = 'verification' systemProperty("spring.profiles.active", "test") @@ -20,11 +20,11 @@ tasks.register('integrationTest', Test) { } } -integrationTest { +apiIntegrationTest { retry { failOnPassedAfterRetry = false maxRetries = 1 } } -check.dependsOn integrationTest +check.dependsOn apiIntegrationTest From bf2aa7556810644ecb9c5d967df6d13c85356cc7 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Wed, 26 Mar 2025 23:42:28 -0700 Subject: [PATCH 15/37] gradle repo --- buildSrc/build.gradle | 1 - settings.gradle | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 53c70a4ad..1ee59078a 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -14,7 +14,6 @@ group = 'xyz.tcheeric' version = '0.6.5-SNAPSHOT' repositories { - gradlePluginPortal() mavenCentral() gradlePluginPortal { url = 'https://plugins.gradle.org/m2/' diff --git a/settings.gradle b/settings.gradle index 8fe1e2175..6f73d74f1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ buildscript { repositories { - maven { + gradlePluginPortal { url = 'https://plugins.gradle.org/m2/' } } From 37800669b5d1084c863bad5e3fbd2418b89ce46b Mon Sep 17 00:00:00 2001 From: nick avlo Date: Thu, 27 Mar 2025 12:11:47 -0700 Subject: [PATCH 16/37] determine use/need for GenericMessage --- .../java/nostr/event/impl/GenericMessage.java | 32 +++++++++---------- .../event/json/codec/BaseMessageDecoder.java | 16 ++++++++-- .../nostr/util/config/ExceptionConfig.java | 5 --- 3 files changed, 29 insertions(+), 24 deletions(-) delete mode 100644 nostr-java-util/src/main/java/nostr/util/config/ExceptionConfig.java diff --git a/nostr-java-event/src/main/java/nostr/event/impl/GenericMessage.java b/nostr-java-event/src/main/java/nostr/event/impl/GenericMessage.java index 79ba8469f..6e7fe8920 100644 --- a/nostr-java-event/src/main/java/nostr/event/impl/GenericMessage.java +++ b/nostr-java-event/src/main/java/nostr/event/impl/GenericMessage.java @@ -67,23 +67,23 @@ public String encode() throws JsonProcessingException { return ENCODER_MAPPED_AFTERBURNER.writeValueAsString(getArrayNode()); } - public static T decode(@NonNull String jsonString) { - try { - Object[] msgArr = I_DECODER_MAPPER_AFTERBURNER.readValue(jsonString, Object[].class); - GenericMessage gm = new GenericMessage(msgArr[0].toString()); - for (int i = 1; i < msgArr.length; i++) { -// TODO: does below ever resolve to String? because RxR stream says it'll always be false. check eric's tests and see what's happening there - if (msgArr[i] instanceof String) { - gm.addAttribute(ElementAttribute.builder().value(msgArr[i]).build()); - } - } - return (T) gm; - } catch (Exception e) { - throw new AssertionError(e); - } - } +// public static T decode(@NonNull String jsonString) { +// try { +// Object[] msgArr = I_DECODER_MAPPER_AFTERBURNER.readValue(jsonString, Object[].class); +// GenericMessage gm = new GenericMessage(msgArr[0].toString()); +// for (int i = 1; i < msgArr.length; i++) { +//// TODO: does below ever resolve to String? because RxR stream says it'll always be false. check eric's tests and see what's happening there +// if (msgArr[i] instanceof String) { +// gm.addAttribute(ElementAttribute.builder().value(msgArr[i]).build()); +// } +// } +// return (T) gm; +// } catch (Exception e) { +// throw new AssertionError(e); +// } +// } - public static T decodeRxR(@NonNull String json) { + public static T decode(@NonNull String json) { try { Object[] msgArr = I_DECODER_MAPPER_AFTERBURNER.readValue(json, Object[].class); GenericMessage gm = new GenericMessage( 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 6671eb260..ad5c5fe27 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 @@ -32,16 +32,26 @@ public T decode(@NonNull String jsonString) throws JsonProcessingException { Object subscriptionId = validNostrJsonStructure.getSubscriptionId(); return switch (command) { +// client <-> relay messages case "AUTH" -> subscriptionId instanceof Map map ? CanonicalAuthenticationMessage.decode(map) : RelayAuthenticationMessage.decode(subscriptionId); + case "EVENT" -> EventMessage.decode(jsonString); +// missing client <-> relay handlers +// case "COUNT" -> CountMessage.decode(subscriptionId); + +// client -> relay messages case "CLOSE" -> CloseMessage.decode(subscriptionId); + case "REQ" -> ReqMessage.decode(subscriptionId, jsonString); + +// relay -> client handlers case "EOSE" -> EoseMessage.decode(subscriptionId); - case "EVENT" -> EventMessage.decode(jsonString); case "NOTICE" -> NoticeMessage.decode(subscriptionId); case "OK" -> OkMessage.decode(jsonString); - case "REQ" -> ReqMessage.decode(subscriptionId, jsonString); - default -> GenericMessage.decode(jsonString); +// missing relay -> client handlers +// case "CLOSED" -> Closed.message.decode(subscriptionId); + + default -> throw new IllegalArgumentException(String.format("Invalid JSON command [%s] in JSON string [%s] ", command, jsonString)); }; } diff --git a/nostr-java-util/src/main/java/nostr/util/config/ExceptionConfig.java b/nostr-java-util/src/main/java/nostr/util/config/ExceptionConfig.java deleted file mode 100644 index 14612a46d..000000000 --- a/nostr-java-util/src/main/java/nostr/util/config/ExceptionConfig.java +++ /dev/null @@ -1,5 +0,0 @@ -//package nostr.util.config; - -//@Configuration -//public class ExceptionConfig { -//} From 32bd20d537ef507d58214d6529eb517032b70bd0 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Thu, 27 Mar 2025 16:35:17 -0700 Subject: [PATCH 17/37] renamed AddressableTagFilter to AddressTagFilter, added AddressTag tests --- .../java/nostr/api/unit/JsonParseTest.java | 6 +- .../src/main/java/nostr/event/BaseTag.java | 65 ++++++------- ...leTagFilter.java => AddressTagFilter.java} | 6 +- .../event/json/codec/FilterableProvider.java | 4 +- .../serializer/AbstractTagSerializer.java | 49 +++++----- .../main/java/nostr/event/tag/AddressTag.java | 17 ++++ .../main/java/nostr/event/tag/EventTag.java | 9 +- .../java/nostr/event/unit/AddressTagTest.java | 55 +++++++++++ .../java/nostr/event/unit/EventTagTest.java | 95 +++++++++---------- .../nostr/event/unit/FiltersDecoderTest.java | 8 +- .../nostr/event/unit/FiltersEncoderTest.java | 8 +- 11 files changed, 197 insertions(+), 125 deletions(-) rename nostr-java-event/src/main/java/nostr/event/filter/{AddressableTagFilter.java => AddressTagFilter.java} (92%) create mode 100644 nostr-java-event/src/test/java/nostr/event/unit/AddressTagTest.java diff --git a/nostr-java-api/src/test/java/nostr/api/unit/JsonParseTest.java b/nostr-java-api/src/test/java/nostr/api/unit/JsonParseTest.java index 76b6a3dbb..95d0ec323 100644 --- a/nostr-java-api/src/test/java/nostr/api/unit/JsonParseTest.java +++ b/nostr-java-api/src/test/java/nostr/api/unit/JsonParseTest.java @@ -14,7 +14,7 @@ import nostr.event.BaseTag; import nostr.event.Kind; import nostr.event.Marker; -import nostr.event.filter.AddressableTagFilter; +import nostr.event.filter.AddressTagFilter; import nostr.event.filter.AuthorFilter; import nostr.event.filter.EventFilter; import nostr.event.filter.Filterable; @@ -566,7 +566,7 @@ public void testReqMessagePopulatedListOfFiltersListDecoder() throws JsonProcess new AuthorFilter<>(new PublicKey(author)), new ReferencedEventFilter<>(new EventTag(referencedEventId)), new ReferencedPublicKeyFilter<>(new PubKeyTag(new PublicKey(author))), - new AddressableTagFilter<>(addressTag1))); + new AddressTagFilter<>(addressTag1))); assertEquals(expectedReqMessage.encode(), decodedReqMessage.encode()); assertEquals(expectedReqMessage, decodedReqMessage); @@ -674,7 +674,7 @@ public void testReqMessageAddressableTagDeserializer() throws JsonProcessingExce addressTag1.setPublicKey(new PublicKey(author)); addressTag1.setIdentifierTag(new IdentifierTag(uuidValue1)); - ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, new Filters(new AddressableTagFilter<>(addressTag1))); + ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, new Filters(new AddressTagFilter<>(addressTag1))); assertEquals(expectedReqMessage.encode(), decodedReqMessage.encode()); assertEquals(expectedReqMessage, decodedReqMessage); diff --git a/nostr-java-event/src/main/java/nostr/event/BaseTag.java b/nostr-java-event/src/main/java/nostr/event/BaseTag.java index cce67680c..a0a77012d 100644 --- a/nostr-java-event/src/main/java/nostr/event/BaseTag.java +++ b/nostr-java-event/src/main/java/nostr/event/BaseTag.java @@ -34,42 +34,43 @@ @JsonSerialize(using = BaseTagSerializer.class) public abstract class BaseTag implements ITag { - @JsonIgnore - private IEvent parent; + @JsonIgnore + private IEvent parent; - @Override - public void setParent(@NonNull IEvent event) { - this.parent = event; - } + @Override + public void setParent(@NonNull IEvent event) { + this.parent = event; + } - @Override - public String getCode() { - return this.getClass().getAnnotation(Tag.class).code(); - } + @Override + public String getCode() { + return this.getClass().getAnnotation(Tag.class).code(); + } - public String getFieldValue(Field field) throws NostrException { - try { - Object f = new PropertyDescriptor(field.getName(), this.getClass()).getReadMethod().invoke(this); - return f != null ? f.toString() : null; - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | IntrospectionException ex) { - throw new NostrException(ex); - } - } +// TODO: refactor into Optional + public String getFieldValue(Field field) throws NostrException { + try { + Object f = new PropertyDescriptor(field.getName(), this.getClass()).getReadMethod().invoke(this); + return f != null ? f.toString() : null; + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | IntrospectionException ex) { + throw new NostrException(ex); + } + } - public List getSupportedFields() throws NostrException { - return new Streams.FailableStream<>(Arrays.stream(this.getClass().getDeclaredFields())) - .filter(f -> - Objects.nonNull(f.getAnnotation(Key.class))) - .filter(f -> - Objects.nonNull(getFieldValue(f))) - .collect(Collectors.toList()); - } + public List getSupportedFields() { + return new Streams.FailableStream<>(Arrays.stream(this.getClass().getDeclaredFields())) + .filter(f -> + Objects.nonNull(f.getAnnotation(Key.class))) + .filter(f -> + Objects.nonNull(getFieldValue(f))) + .collect(Collectors.toList()); + } - protected static void setOptionalField(JsonNode node, BiConsumer con, T tag) { - Optional.ofNullable(node).ifPresent(n -> con.accept(n, tag)); - } + protected static void setOptionalField(JsonNode node, BiConsumer con, T tag) { + Optional.ofNullable(node).ifPresent(n -> con.accept(n, tag)); + } - protected static void setRequiredField(JsonNode node, BiConsumer con, T tag) { - con.accept(Optional.ofNullable(node).orElseThrow(), tag); - } + protected static void setRequiredField(JsonNode node, BiConsumer con, T tag) { + con.accept(Optional.ofNullable(node).orElseThrow(), tag); + } } diff --git a/nostr-java-event/src/main/java/nostr/event/filter/AddressableTagFilter.java b/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java similarity index 92% rename from nostr-java-event/src/main/java/nostr/event/filter/AddressableTagFilter.java rename to nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java index ae86a6ca3..5a2201899 100644 --- a/nostr-java-event/src/main/java/nostr/event/filter/AddressableTagFilter.java +++ b/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java @@ -16,10 +16,10 @@ import java.util.stream.Stream; @EqualsAndHashCode(callSuper = true) -public class AddressableTagFilter extends AbstractFilterable { +public class AddressTagFilter extends AbstractFilterable { public final static String FILTER_KEY = "#a"; - public AddressableTagFilter(T addressableTag) { + public AddressTagFilter(T addressableTag) { super(addressableTag, FILTER_KEY); } @@ -73,5 +73,5 @@ private T getAddressableTag() { return super.getFilterable(); } - public static Function fxn = node -> new AddressableTagFilter<>(AddressableTagFilter.createAddressTag(node)); + public static Function fxn = node -> new AddressTagFilter<>(AddressTagFilter.createAddressTag(node)); } diff --git a/nostr-java-event/src/main/java/nostr/event/json/codec/FilterableProvider.java b/nostr-java-event/src/main/java/nostr/event/json/codec/FilterableProvider.java index c614f8d02..3a839799e 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/codec/FilterableProvider.java +++ b/nostr-java-event/src/main/java/nostr/event/json/codec/FilterableProvider.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.databind.JsonNode; import lombok.NonNull; -import nostr.event.filter.AddressableTagFilter; +import nostr.event.filter.AddressTagFilter; import nostr.event.filter.AuthorFilter; import nostr.event.filter.EventFilter; import nostr.event.filter.Filterable; @@ -25,7 +25,7 @@ protected static List getFilterFunction(@NonNull JsonNode node, @Non return switch (type) { case ReferencedPublicKeyFilter.FILTER_KEY -> getFilterable(node, ReferencedPublicKeyFilter.fxn); case ReferencedEventFilter.FILTER_KEY -> getFilterable(node, ReferencedEventFilter.fxn); - case AddressableTagFilter.FILTER_KEY -> getFilterable(node, AddressableTagFilter.fxn); + case AddressTagFilter.FILTER_KEY -> getFilterable(node, AddressTagFilter.fxn); case IdentifierTagFilter.FILTER_KEY -> getFilterable(node, IdentifierTagFilter.fxn); case GeohashTagFilter.FILTER_KEY -> getFilterable(node, GeohashTagFilter.fxn); case HashtagTagFilter.FILTER_KEY -> getFilterable(node, HashtagTagFilter.fxn); diff --git a/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java b/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java index 8ca6697f6..410a46a89 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java +++ b/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java @@ -6,34 +6,33 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import nostr.event.BaseTag; -import nostr.util.NostrException; import org.apache.commons.lang3.stream.Streams; - import java.io.IOException; - import static nostr.event.json.codec.BaseTagEncoder.BASETAG_ENCODER_MAPPED_AFTERBURNER; abstract class AbstractTagSerializer extends StdSerializer { - protected AbstractTagSerializer(Class t) { - super(t); - } - - public void serialize(T value, JsonGenerator gen, SerializerProvider serializers) { - try { - final ObjectNode node = BASETAG_ENCODER_MAPPED_AFTERBURNER.getNodeFactory().objectNode(); - Streams.failableStream( - value.getSupportedFields().stream()).forEach(f -> - node.put(f.getName(), value.getFieldValue(f))); - - applyCustomAttributes(node, value); - - ArrayNode arrayNode = node.objectNode().putArray("values").add(value.getCode()); - node.fields().forEachRemaining(entry -> arrayNode.add(entry.getValue().asText())); - gen.writePOJO(arrayNode); - } catch (IOException | NostrException e) { - throw new RuntimeException(e); - } - } - - protected void applyCustomAttributes(ObjectNode node, T value) {} + protected AbstractTagSerializer(Class t) { + super(t); + } + + public void serialize(T value, JsonGenerator gen, SerializerProvider serializers) { + try { + final ObjectNode node = BASETAG_ENCODER_MAPPED_AFTERBURNER.getNodeFactory().objectNode(); + Streams.failableStream( + value.getSupportedFields().stream()) + .forEach(f -> + node.put(f.getName(), value.getFieldValue(f))); + + applyCustomAttributes(node, value); + + ArrayNode arrayNode = node.objectNode().putArray("values").add(value.getCode()); + node.fields().forEachRemaining(entry -> arrayNode.add(entry.getValue().asText())); + gen.writePOJO(arrayNode); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + protected void applyCustomAttributes(ObjectNode node, T value) { + } } diff --git a/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java b/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java index dd245b6e9..05c95abb4 100644 --- a/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java @@ -1,5 +1,8 @@ package nostr.event.tag; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.AllArgsConstructor; import lombok.Builder; @@ -8,6 +11,7 @@ import lombok.NoArgsConstructor; import nostr.base.PublicKey; import nostr.base.Relay; +import nostr.base.annotation.Key; import nostr.base.annotation.Tag; import nostr.event.BaseTag; import nostr.event.json.serializer.AddressTagSerializer; @@ -20,13 +24,26 @@ @Data @EqualsAndHashCode(callSuper = true) @Tag(code = "a", nip = 33) +@JsonPropertyOrder({"kind", "publicKey", "identifierTag", "relay"}) @NoArgsConstructor @AllArgsConstructor @JsonSerialize(using = AddressTagSerializer.class) public class AddressTag extends BaseTag { + @Key + @JsonProperty private Integer kind; + + @Key + @JsonProperty private PublicKey publicKey; + + @Key + @JsonProperty private IdentifierTag identifierTag; + + @Key + @JsonProperty + @JsonInclude(JsonInclude.Include.NON_NULL) private Relay relay; } diff --git a/nostr-java-event/src/main/java/nostr/event/tag/EventTag.java b/nostr-java-event/src/main/java/nostr/event/tag/EventTag.java index bbc55abe0..35d0df639 100644 --- a/nostr-java-event/src/main/java/nostr/event/tag/EventTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/EventTag.java @@ -21,23 +21,23 @@ @Builder @Data @EqualsAndHashCode(callSuper = true) -@AllArgsConstructor @Tag(code = "e", name = "event") @JsonPropertyOrder({"idEvent", "recommendedRelayUrl", "marker"}) @NoArgsConstructor +@AllArgsConstructor public class EventTag extends BaseTag { @Key - @JsonProperty("idEvent") + @JsonProperty private String idEvent; @Key - @JsonProperty("recommendedRelayUrl") + @JsonProperty @JsonInclude(JsonInclude.Include.NON_NULL) private String recommendedRelayUrl; @Key(nip = 10) - @JsonProperty("marker") + @JsonProperty @JsonInclude(JsonInclude.Include.NON_NULL) private Marker marker; @@ -57,3 +57,4 @@ public static T deserialize(@NonNull JsonNode node) { return (T) tag; } } + diff --git a/nostr-java-event/src/test/java/nostr/event/unit/AddressTagTest.java b/nostr-java-event/src/test/java/nostr/event/unit/AddressTagTest.java new file mode 100644 index 000000000..cd3a8c984 --- /dev/null +++ b/nostr-java-event/src/test/java/nostr/event/unit/AddressTagTest.java @@ -0,0 +1,55 @@ +package nostr.event.unit; + +import nostr.base.PublicKey; +import nostr.base.Relay; +import nostr.event.tag.AddressTag; +import nostr.event.tag.IdentifierTag; +import org.apache.commons.lang3.function.FailablePredicate; +import org.apache.commons.lang3.stream.Streams; +import org.junit.jupiter.api.Test; +import java.lang.reflect.Field; +import java.util.List; +import java.util.function.Predicate; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AddressTagTest { + + @Test + void getSupportedFields() { + Integer kind = 1; + String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + PublicKey publicKey = new PublicKey(author); + IdentifierTag identifierTag = new IdentifierTag("UUID-1"); + Relay relay = new Relay("ws://localhost:8080"); + + AddressTag addressTag = new AddressTag(); + addressTag.setKind(kind); + addressTag.setPublicKey(publicKey); + addressTag.setIdentifierTag(identifierTag); + addressTag.setRelay(relay); + +// TODO: refactor below to use baseTag.getFieldValue() instead (after refactoring ref'd method in BaseTag to Optional + List fields = addressTag.getSupportedFields(); + anyFieldNameMatch(fields, field -> field.getName().equals("kind")); + anyFieldNameMatch(fields, field -> field.getName().equals("publicKey")); + anyFieldNameMatch(fields, field -> field.getName().equals("identifierTag")); + anyFieldNameMatch(fields, field -> field.getName().equals("relay")); + + anyFieldValueMatch(fields, addressTag, fieldValue -> fieldValue.equals(kind.toString())); + anyFieldValueMatch(fields, addressTag, fieldValue -> fieldValue.equals(publicKey.toString())); + anyFieldValueMatch(fields, addressTag, fieldValue -> fieldValue.equals(identifierTag.toString())); + anyFieldValueMatch(fields, addressTag, fieldValue -> fieldValue.equals(relay.toString())); + + assertFalse(fields.stream().anyMatch(field -> field.getName().equals("idEventXXX"))); + assertFalse(Streams.failableStream(fields.stream()).map(addressTag::getFieldValue).anyMatch(fieldValue -> fieldValue.equals(identifierTag.toString() + "x"))); + } + + private static void anyFieldNameMatch(List fields, Predicate predicate) { + assertTrue(fields.stream().anyMatch(predicate)); + } + + private static void anyFieldValueMatch(List fields, AddressTag addressTag, FailablePredicate predicate) { + assertTrue(Streams.failableStream(fields.stream()).map(addressTag::getFieldValue).anyMatch(predicate)); + } +} diff --git a/nostr-java-event/src/test/java/nostr/event/unit/EventTagTest.java b/nostr-java-event/src/test/java/nostr/event/unit/EventTagTest.java index 3ba32d8b9..892d7abf3 100644 --- a/nostr-java-event/src/test/java/nostr/event/unit/EventTagTest.java +++ b/nostr-java-event/src/test/java/nostr/event/unit/EventTagTest.java @@ -1,48 +1,47 @@ -package nostr.event.unit; - -import nostr.event.Marker; -import nostr.event.tag.EventTag; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Field; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class EventTagTest { - - @Test - void getSupportedFields() { - String eventId = "fc7f200c5bed175702bd06c7ca5dba90d3497e827350b42fc99c3a4fa276a712"; - String recommendedRelayUrl = "ws://localhost:5555"; - - EventTag eventTag = new EventTag(eventId); - eventTag.setMarker(Marker.REPLY); - eventTag.setRecommendedRelayUrl(recommendedRelayUrl); - - assertDoesNotThrow(() -> { - List fields = eventTag.getSupportedFields(); - assertTrue(fields.stream().anyMatch(field -> field.getName().equals("idEvent"))); - assertTrue(fields.stream().anyMatch(field -> field.getName().equals("recommendedRelayUrl"))); - assertTrue(fields.stream().anyMatch(field -> field.getName().equals("marker"))); - - assertTrue(fields.stream().map(field -> getFieldValue(field, eventTag)).anyMatch(fieldValue -> fieldValue.equals(eventId))); - assertTrue(fields.stream().map(field -> getFieldValue(field, eventTag)).anyMatch(fieldValue -> fieldValue.equalsIgnoreCase(Marker.REPLY.getValue()))); - assertTrue(fields.stream().map(field -> getFieldValue(field, eventTag)).anyMatch(fieldValue -> fieldValue.equals(recommendedRelayUrl))); - - assertFalse(fields.stream().anyMatch(field -> field.getName().equals("idEventXXX"))); - assertFalse(fields.stream().map(field -> getFieldValue(field, eventTag)).anyMatch(fieldValue -> fieldValue.equals(eventId + "x"))); - }); - } - - private String getFieldValue(Field field, EventTag eventTag) { - final String[] returnVal = new String[1]; - assertDoesNotThrow(() -> { - returnVal[0] = eventTag.getFieldValue(field); - }); - return returnVal[0]; - } - -} +package nostr.event.unit; + +import nostr.event.Marker; +import nostr.event.tag.EventTag; +import org.apache.commons.lang3.function.FailablePredicate; +import org.apache.commons.lang3.stream.Streams; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.UUID; +import java.util.function.Predicate; +import static org.junit.jupiter.api.Assertions.*; + +class EventTagTest { + + @Test + void getSupportedFields() { + String eventId = UUID.randomUUID().toString().concat(UUID.randomUUID().toString()).substring(0, 64); + String recommendedRelayUrl = "ws://localhost:5555"; + + EventTag eventTag = new EventTag(eventId); + eventTag.setMarker(Marker.REPLY); + eventTag.setRecommendedRelayUrl(recommendedRelayUrl); + +// TODO: refactor below to use baseTag.getFieldValue() instead (after refactoring ref'd method in BaseTag to Optional + List fields = eventTag.getSupportedFields(); + anyFieldNameMatch(fields, field -> field.getName().equals("idEvent")); + anyFieldNameMatch(fields, field -> field.getName().equals("recommendedRelayUrl")); + anyFieldNameMatch(fields, field -> field.getName().equals("marker")); + + anyFieldValueMatch(fields, eventTag, fieldValue -> fieldValue.equals(eventId)); + anyFieldValueMatch(fields, eventTag, fieldValue -> fieldValue.equalsIgnoreCase(Marker.REPLY.getValue())); + anyFieldValueMatch(fields, eventTag, fieldValue -> fieldValue.equals(recommendedRelayUrl)); + + assertFalse(fields.stream().anyMatch(field -> field.getName().equals("idEventXXX"))); + assertFalse(Streams.failableStream(fields.stream()).map(eventTag::getFieldValue).anyMatch(fieldValue -> fieldValue.equals(eventId + "x"))); + } + + private static void anyFieldNameMatch(List fields, Predicate predicate) { + assertTrue(fields.stream().anyMatch(predicate)); + } + + private static void anyFieldValueMatch(List fields, EventTag eventTag, FailablePredicate predicate) { + assertTrue(Streams.failableStream(fields.stream()).map(eventTag::getFieldValue).anyMatch(predicate)); + } +} diff --git a/nostr-java-event/src/test/java/nostr/event/unit/FiltersDecoderTest.java b/nostr-java-event/src/test/java/nostr/event/unit/FiltersDecoderTest.java index ba46f522c..56bef8bae 100644 --- a/nostr-java-event/src/test/java/nostr/event/unit/FiltersDecoderTest.java +++ b/nostr-java-event/src/test/java/nostr/event/unit/FiltersDecoderTest.java @@ -4,7 +4,7 @@ import nostr.base.GenericTagQuery; import nostr.base.PublicKey; import nostr.event.Kind; -import nostr.event.filter.AddressableTagFilter; +import nostr.event.filter.AddressTagFilter; import nostr.event.filter.EventFilter; import nostr.event.filter.Filters; import nostr.event.filter.GenericTagQueryFilter; @@ -92,7 +92,7 @@ public void testAddressableTagFiltersDecoder() { assertEquals( new Filters( - new AddressableTagFilter<>(addressTag)), + new AddressTagFilter<>(addressTag)), decodedFilters); } @@ -128,8 +128,8 @@ public void testMultipleAddressableTagFiltersDecoder() { assertEquals( new Filters( - new AddressableTagFilter<>(addressTag1), - new AddressableTagFilter<>(addressTag2)), + new AddressTagFilter<>(addressTag1), + new AddressTagFilter<>(addressTag2)), decodedFilters); } diff --git a/nostr-java-event/src/test/java/nostr/event/unit/FiltersEncoderTest.java b/nostr-java-event/src/test/java/nostr/event/unit/FiltersEncoderTest.java index 6d5822d72..4097032f4 100644 --- a/nostr-java-event/src/test/java/nostr/event/unit/FiltersEncoderTest.java +++ b/nostr-java-event/src/test/java/nostr/event/unit/FiltersEncoderTest.java @@ -4,7 +4,7 @@ import nostr.base.GenericTagQuery; import nostr.base.PublicKey; import nostr.event.Kind; -import nostr.event.filter.AddressableTagFilter; +import nostr.event.filter.AddressTagFilter; import nostr.event.filter.AuthorFilter; import nostr.event.filter.EventFilter; import nostr.event.filter.Filters; @@ -137,7 +137,7 @@ public void testAddressableTagFilterEncoder() { addressTag.setPublicKey(new PublicKey(author)); addressTag.setIdentifierTag(new IdentifierTag(uuidValue1)); - FiltersEncoder encoder = new FiltersEncoder(new Filters(new AddressableTagFilter<>(addressTag))); + FiltersEncoder encoder = new FiltersEncoder(new Filters(new AddressTagFilter<>(addressTag))); String encodedFilters = encoder.encode(); String addressableTag = String.join(":", String.valueOf(kind), author, uuidValue1); @@ -337,8 +337,8 @@ public void testMultipleAddressableTagFilterEncoder() { addressTag2.setIdentifierTag(new IdentifierTag(uuidValue2)); FiltersEncoder encoder = new FiltersEncoder(new Filters( - new AddressableTagFilter<>(addressTag1), - new AddressableTagFilter<>(addressTag2))); + new AddressTagFilter<>(addressTag1), + new AddressTagFilter<>(addressTag2))); String encoded = encoder.encode(); String addressableTags = String.join("\",\"", addressableTag1, addressableTag2); From 1f5011c83474631df4298b1a585eb192f38c62f1 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Thu, 27 Mar 2025 16:39:20 -0700 Subject: [PATCH 18/37] renamed test to properly match class under test --- ...essageCommandMapperTest.java => BaseMessageDecoderTest.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename nostr-java-event/src/test/java/nostr/event/unit/{BaseMessageCommandMapperTest.java => BaseMessageDecoderTest.java} (98%) diff --git a/nostr-java-event/src/test/java/nostr/event/unit/BaseMessageCommandMapperTest.java b/nostr-java-event/src/test/java/nostr/event/unit/BaseMessageDecoderTest.java similarity index 98% rename from nostr-java-event/src/test/java/nostr/event/unit/BaseMessageCommandMapperTest.java rename to nostr-java-event/src/test/java/nostr/event/unit/BaseMessageDecoderTest.java index 4d421109f..f12bc2f35 100644 --- a/nostr-java-event/src/test/java/nostr/event/unit/BaseMessageCommandMapperTest.java +++ b/nostr-java-event/src/test/java/nostr/event/unit/BaseMessageDecoderTest.java @@ -10,7 +10,7 @@ import static org.junit.jupiter.api.Assertions.*; @Log -public class BaseMessageCommandMapperTest { +public class BaseMessageDecoderTest { // TODO: flesh out remaining commands public final static String REQ_JSON = "[\"REQ\", " + From b22d217b312b4627d25e3a5f9caab8db638f29ae Mon Sep 17 00:00:00 2001 From: nick avlo Date: Thu, 27 Mar 2025 21:32:19 -0700 Subject: [PATCH 19/37] BaseTag.getFieldValue() now returns Optional instead of throwing exception AbstractTagSerializer now uses above Optional in serialization stream AbstractTagSerializer removed failableStream since no longer needed to handle above stream exceptions test updates accordingly --- .../src/main/java/nostr/event/BaseTag.java | 22 ++-- .../serializer/AbstractTagSerializer.java | 9 +- .../java/nostr/event/unit/AddressTagTest.java | 111 +++++++++--------- .../java/nostr/event/unit/EventTagTest.java | 13 +- .../java/nostr/event/unit/PubkeyTagTest.java | 52 ++++---- 5 files changed, 102 insertions(+), 105 deletions(-) diff --git a/nostr-java-event/src/main/java/nostr/event/BaseTag.java b/nostr-java-event/src/main/java/nostr/event/BaseTag.java index a0a77012d..8987c0e65 100644 --- a/nostr-java-event/src/main/java/nostr/event/BaseTag.java +++ b/nostr-java-event/src/main/java/nostr/event/BaseTag.java @@ -14,7 +14,6 @@ import nostr.base.annotation.Tag; import nostr.event.json.deserializer.TagDeserializer; import nostr.event.json.serializer.BaseTagSerializer; -import nostr.util.NostrException; import org.apache.commons.lang3.stream.Streams; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; @@ -47,23 +46,24 @@ public String getCode() { return this.getClass().getAnnotation(Tag.class).code(); } -// TODO: refactor into Optional - public String getFieldValue(Field field) throws NostrException { + public Optional getFieldValue(Field field) { try { - Object f = new PropertyDescriptor(field.getName(), this.getClass()).getReadMethod().invoke(this); - return f != null ? f.toString() : null; + return Optional.ofNullable( + new PropertyDescriptor(field.getName(), this.getClass()) + .getReadMethod().invoke(this)) + .map(Object::toString); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | IntrospectionException ex) { - throw new NostrException(ex); + return Optional.empty(); } } public List getSupportedFields() { return new Streams.FailableStream<>(Arrays.stream(this.getClass().getDeclaredFields())) - .filter(f -> - Objects.nonNull(f.getAnnotation(Key.class))) - .filter(f -> - Objects.nonNull(getFieldValue(f))) - .collect(Collectors.toList()); + .filter(f -> + Objects.nonNull(f.getAnnotation(Key.class))) + .filter(f -> + getFieldValue(f).isPresent()) + .collect(Collectors.toList()); } protected static void setOptionalField(JsonNode node, BiConsumer con, T tag) { diff --git a/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java b/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java index 410a46a89..5ac64c450 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java +++ b/nostr-java-event/src/main/java/nostr/event/json/serializer/AbstractTagSerializer.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import nostr.event.BaseTag; -import org.apache.commons.lang3.stream.Streams; import java.io.IOException; import static nostr.event.json.codec.BaseTagEncoder.BASETAG_ENCODER_MAPPED_AFTERBURNER; @@ -18,10 +17,10 @@ protected AbstractTagSerializer(Class t) { public void serialize(T value, JsonGenerator gen, SerializerProvider serializers) { try { final ObjectNode node = BASETAG_ENCODER_MAPPED_AFTERBURNER.getNodeFactory().objectNode(); - Streams.failableStream( - value.getSupportedFields().stream()) - .forEach(f -> - node.put(f.getName(), value.getFieldValue(f))); + value.getSupportedFields().forEach(f -> + value.getFieldValue(f) + .ifPresent(s -> + node.put(f.getName(), s))); applyCustomAttributes(node, value); diff --git a/nostr-java-event/src/test/java/nostr/event/unit/AddressTagTest.java b/nostr-java-event/src/test/java/nostr/event/unit/AddressTagTest.java index cd3a8c984..bf98c17c8 100644 --- a/nostr-java-event/src/test/java/nostr/event/unit/AddressTagTest.java +++ b/nostr-java-event/src/test/java/nostr/event/unit/AddressTagTest.java @@ -1,55 +1,56 @@ -package nostr.event.unit; - -import nostr.base.PublicKey; -import nostr.base.Relay; -import nostr.event.tag.AddressTag; -import nostr.event.tag.IdentifierTag; -import org.apache.commons.lang3.function.FailablePredicate; -import org.apache.commons.lang3.stream.Streams; -import org.junit.jupiter.api.Test; -import java.lang.reflect.Field; -import java.util.List; -import java.util.function.Predicate; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class AddressTagTest { - - @Test - void getSupportedFields() { - Integer kind = 1; - String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - PublicKey publicKey = new PublicKey(author); - IdentifierTag identifierTag = new IdentifierTag("UUID-1"); - Relay relay = new Relay("ws://localhost:8080"); - - AddressTag addressTag = new AddressTag(); - addressTag.setKind(kind); - addressTag.setPublicKey(publicKey); - addressTag.setIdentifierTag(identifierTag); - addressTag.setRelay(relay); - -// TODO: refactor below to use baseTag.getFieldValue() instead (after refactoring ref'd method in BaseTag to Optional - List fields = addressTag.getSupportedFields(); - anyFieldNameMatch(fields, field -> field.getName().equals("kind")); - anyFieldNameMatch(fields, field -> field.getName().equals("publicKey")); - anyFieldNameMatch(fields, field -> field.getName().equals("identifierTag")); - anyFieldNameMatch(fields, field -> field.getName().equals("relay")); - - anyFieldValueMatch(fields, addressTag, fieldValue -> fieldValue.equals(kind.toString())); - anyFieldValueMatch(fields, addressTag, fieldValue -> fieldValue.equals(publicKey.toString())); - anyFieldValueMatch(fields, addressTag, fieldValue -> fieldValue.equals(identifierTag.toString())); - anyFieldValueMatch(fields, addressTag, fieldValue -> fieldValue.equals(relay.toString())); - - assertFalse(fields.stream().anyMatch(field -> field.getName().equals("idEventXXX"))); - assertFalse(Streams.failableStream(fields.stream()).map(addressTag::getFieldValue).anyMatch(fieldValue -> fieldValue.equals(identifierTag.toString() + "x"))); - } - - private static void anyFieldNameMatch(List fields, Predicate predicate) { - assertTrue(fields.stream().anyMatch(predicate)); - } - - private static void anyFieldValueMatch(List fields, AddressTag addressTag, FailablePredicate predicate) { - assertTrue(Streams.failableStream(fields.stream()).map(addressTag::getFieldValue).anyMatch(predicate)); - } -} +package nostr.event.unit; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.function.Predicate; +import nostr.base.PublicKey; +import nostr.base.Relay; +import nostr.event.tag.AddressTag; +import nostr.event.tag.IdentifierTag; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AddressTagTest { + + @Test + void getSupportedFields() { + Integer kind = 1; + String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + PublicKey publicKey = new PublicKey(author); + IdentifierTag identifierTag = new IdentifierTag("UUID-1"); + Relay relay = new Relay("ws://localhost:8080"); + + AddressTag addressTag = new AddressTag(); + addressTag.setKind(kind); + addressTag.setPublicKey(publicKey); + addressTag.setIdentifierTag(identifierTag); + addressTag.setRelay(relay); + + List fields = addressTag.getSupportedFields(); + anyFieldNameMatch(fields, field -> field.getName().equals("kind")); + anyFieldNameMatch(fields, field -> field.getName().equals("publicKey")); + anyFieldNameMatch(fields, field -> field.getName().equals("identifierTag")); + anyFieldNameMatch(fields, field -> field.getName().equals("relay")); + + anyFieldValueMatch(fields, addressTag, fieldValue -> fieldValue.equals(kind.toString())); + anyFieldValueMatch(fields, addressTag, fieldValue -> fieldValue.equals(publicKey.toString())); + anyFieldValueMatch(fields, addressTag, fieldValue -> fieldValue.equals(identifierTag.toString())); + anyFieldValueMatch(fields, addressTag, fieldValue -> fieldValue.equals(relay.toString())); + + assertFalse(fields.stream().anyMatch(field -> field.getName().equals("idEventXXX"))); + assertFalse( + fields.stream().flatMap(field -> + addressTag.getFieldValue(field).stream()) + .anyMatch(fieldValue -> + fieldValue.equals(identifierTag.toString() + "x"))); + } + + private static void anyFieldNameMatch(List fields, Predicate predicate) { + assertTrue(fields.stream().anyMatch(predicate)); + } + + private static void anyFieldValueMatch(List fields, AddressTag addressTag, Predicate predicate) { + assertTrue(fields.stream().flatMap(field -> addressTag.getFieldValue(field).stream()).anyMatch(predicate)); + } +} diff --git a/nostr-java-event/src/test/java/nostr/event/unit/EventTagTest.java b/nostr-java-event/src/test/java/nostr/event/unit/EventTagTest.java index 892d7abf3..963924c32 100644 --- a/nostr-java-event/src/test/java/nostr/event/unit/EventTagTest.java +++ b/nostr-java-event/src/test/java/nostr/event/unit/EventTagTest.java @@ -2,15 +2,13 @@ import nostr.event.Marker; import nostr.event.tag.EventTag; -import org.apache.commons.lang3.function.FailablePredicate; -import org.apache.commons.lang3.stream.Streams; import org.junit.jupiter.api.Test; - import java.lang.reflect.Field; import java.util.List; import java.util.UUID; import java.util.function.Predicate; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; class EventTagTest { @@ -23,7 +21,6 @@ void getSupportedFields() { eventTag.setMarker(Marker.REPLY); eventTag.setRecommendedRelayUrl(recommendedRelayUrl); -// TODO: refactor below to use baseTag.getFieldValue() instead (after refactoring ref'd method in BaseTag to Optional List fields = eventTag.getSupportedFields(); anyFieldNameMatch(fields, field -> field.getName().equals("idEvent")); anyFieldNameMatch(fields, field -> field.getName().equals("recommendedRelayUrl")); @@ -34,14 +31,14 @@ void getSupportedFields() { anyFieldValueMatch(fields, eventTag, fieldValue -> fieldValue.equals(recommendedRelayUrl)); assertFalse(fields.stream().anyMatch(field -> field.getName().equals("idEventXXX"))); - assertFalse(Streams.failableStream(fields.stream()).map(eventTag::getFieldValue).anyMatch(fieldValue -> fieldValue.equals(eventId + "x"))); + assertFalse(fields.stream().flatMap(field -> eventTag.getFieldValue(field).stream()).anyMatch(fieldValue -> fieldValue.equals(eventId + "x"))); } private static void anyFieldNameMatch(List fields, Predicate predicate) { assertTrue(fields.stream().anyMatch(predicate)); } - private static void anyFieldValueMatch(List fields, EventTag eventTag, FailablePredicate predicate) { - assertTrue(Streams.failableStream(fields.stream()).map(eventTag::getFieldValue).anyMatch(predicate)); + private static void anyFieldValueMatch(List fields, EventTag eventTag, Predicate predicate) { + assertTrue(fields.stream().flatMap(field -> eventTag.getFieldValue(field).stream()).anyMatch(predicate)); } } diff --git a/nostr-java-event/src/test/java/nostr/event/unit/PubkeyTagTest.java b/nostr-java-event/src/test/java/nostr/event/unit/PubkeyTagTest.java index 6536e4320..9173ebdc4 100644 --- a/nostr-java-event/src/test/java/nostr/event/unit/PubkeyTagTest.java +++ b/nostr-java-event/src/test/java/nostr/event/unit/PubkeyTagTest.java @@ -1,26 +1,26 @@ -package nostr.event.unit; - -import nostr.base.PublicKey; -import nostr.event.tag.PubKeyTag; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Field; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; - -class PubkeyTagTest { - - @Test - void getSupportedFields() { - String sha256 = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; - PubKeyTag pubKeyTag = new PubKeyTag(new PublicKey(sha256)); - assertDoesNotThrow(() -> { - Field field = pubKeyTag.getSupportedFields().stream().findFirst().orElseThrow(); - assertEquals("nostr.base.PublicKey", field.getAnnotatedType().toString()); - assertEquals("publicKey", field.getName()); - assertEquals(sha256, pubKeyTag.getFieldValue(field)); - }); - } - -} +package nostr.event.unit; + +import nostr.base.PublicKey; +import nostr.event.tag.PubKeyTag; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class PubkeyTagTest { + + @Test + void getSupportedFields() { + String sha256 = "56adf01ca1aa9d6f1c35953833bbe6d99a0c85b73af222e6bd305b51f2749f6f"; + PubKeyTag pubKeyTag = new PubKeyTag(new PublicKey(sha256)); + assertDoesNotThrow(() -> { + Field field = pubKeyTag.getSupportedFields().stream().findFirst().orElseThrow(); + assertEquals("nostr.base.PublicKey", field.getAnnotatedType().toString()); + assertEquals("publicKey", field.getName()); + assertEquals(sha256, pubKeyTag.getFieldValue(field).orElseThrow()); + }); + } + +} From eb38322e4e4453cc5990581c2f91a814e1a01d9c Mon Sep 17 00:00:00 2001 From: nick avlo Date: Thu, 27 Mar 2025 21:36:57 -0700 Subject: [PATCH 20/37] cleanup --- nostr-java-event/src/main/java/nostr/event/BaseTag.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nostr-java-event/src/main/java/nostr/event/BaseTag.java b/nostr-java-event/src/main/java/nostr/event/BaseTag.java index 8987c0e65..a88852475 100644 --- a/nostr-java-event/src/main/java/nostr/event/BaseTag.java +++ b/nostr-java-event/src/main/java/nostr/event/BaseTag.java @@ -58,7 +58,7 @@ public Optional getFieldValue(Field field) { } public List getSupportedFields() { - return new Streams.FailableStream<>(Arrays.stream(this.getClass().getDeclaredFields())) + return Streams.failableStream(Arrays.stream(this.getClass().getDeclaredFields())) .filter(f -> Objects.nonNull(f.getAnnotation(Key.class))) .filter(f -> From 4110b77fbf44ac3def59885b4fe102e53a4b31e8 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Thu, 27 Mar 2025 23:45:26 -0700 Subject: [PATCH 21/37] resolve stash diffs --- .../nostr/event/filter/AddressTagFilter.java | 35 +++++++++---------- .../json/deserializer/TagDeserializer.java | 2 ++ .../main/java/nostr/event/tag/AddressTag.java | 11 ++++++ 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java b/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java index 5a2201899..13314c1a9 100644 --- a/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java +++ b/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java @@ -1,19 +1,18 @@ package nostr.event.filter; import com.fasterxml.jackson.databind.JsonNode; -import lombok.EqualsAndHashCode; -import lombok.NonNull; -import nostr.base.PublicKey; -import nostr.event.impl.GenericEvent; -import nostr.event.tag.AddressTag; -import nostr.event.tag.IdentifierTag; - import java.util.Arrays; import java.util.List; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; +import lombok.EqualsAndHashCode; +import lombok.NonNull; +import nostr.base.PublicKey; +import nostr.event.impl.GenericEvent; +import nostr.event.tag.AddressTag; +import nostr.event.tag.IdentifierTag; @EqualsAndHashCode(callSuper = true) public class AddressTagFilter extends AbstractFilterable { @@ -40,7 +39,7 @@ public static AddressTag createAddressTag(@NonNull JsonNode addressableTag) thro return addressTag; } catch (NumberFormatException e) { throw new IllegalArgumentException( - String.format("Malformed JsonNode addressable tag: [%s]", addressableTag.asText()), e); + String.format("Malformed JsonNode addressable tag: [%s]", addressableTag.asText()), e); } } @@ -52,21 +51,21 @@ public String getFilterableValue() { String id = addressableTag.getIdentifierTag().getId(); return Stream.of(kind, hexString, id) - .map(Object::toString) - .collect(Collectors.joining(":")); + .map(Object::toString) + .collect(Collectors.joining(":")); } private boolean compare(@NonNull GenericEvent genericEvent) { T addressableTag = getAddressableTag(); return - !genericEvent.getPubKey().toHexString().equals( - addressableTag.getPublicKey().toHexString()) || - !genericEvent.getKind().equals( - addressableTag.getKind()) || - Filterable.getTypeSpecificTags(IdentifierTag.class, genericEvent).stream() - .anyMatch(identifierTag -> - identifierTag.getId().equals( - addressableTag.getIdentifierTag().getId())); + !genericEvent.getPubKey().toHexString().equals( + addressableTag.getPublicKey().toHexString()) || + !genericEvent.getKind().equals( + addressableTag.getKind()) || + Filterable.getTypeSpecificTags(IdentifierTag.class, genericEvent).stream() + .anyMatch(identifierTag -> + identifierTag.getId().equals( + addressableTag.getIdentifierTag().getId())); } private T getAddressableTag() { diff --git a/nostr-java-event/src/main/java/nostr/event/json/deserializer/TagDeserializer.java b/nostr-java-event/src/main/java/nostr/event/json/deserializer/TagDeserializer.java index 3eb44c106..28bbd099d 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/deserializer/TagDeserializer.java +++ b/nostr-java-event/src/main/java/nostr/event/json/deserializer/TagDeserializer.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.JsonNode; import nostr.event.BaseTag; import nostr.event.json.codec.GenericTagDecoder; +import nostr.event.tag.AddressTag; import nostr.event.tag.EventTag; import nostr.event.tag.GeohashTag; import nostr.event.tag.HashtagTag; @@ -32,6 +33,7 @@ public T deserialize(JsonParser jsonParser, DeserializationContext deserializati } else // Perform custom deserialization logic based on the concrete class { return switch (code) { + case "a" -> AddressTag.deserialize(node); case "d" -> IdentifierTag.deserialize(node); case "e" -> EventTag.deserialize(node); case "g" -> GeohashTag.deserialize(node); diff --git a/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java b/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java index 05c95abb4..c059b1ab5 100644 --- a/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java @@ -3,12 +3,14 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import lombok.NonNull; import nostr.base.PublicKey; import nostr.base.Relay; import nostr.base.annotation.Key; @@ -46,4 +48,13 @@ public class AddressTag extends BaseTag { @JsonProperty @JsonInclude(JsonInclude.Include.NON_NULL) private Relay relay; + + public static T deserialize(@NonNull JsonNode node) { + AddressTag tag = new AddressTag(); + setRequiredField(node.get(1), (n, a) -> tag.setKind(n.asInt()), tag); + setRequiredField(node.get(2), (n, a) -> tag.setPublicKey(new PublicKey(n.asText())), tag); + setRequiredField(node.get(3), (n, a) -> tag.setIdentifierTag(new IdentifierTag(n.asText())), tag); + setOptionalField(node.get(4), (n, a) -> tag.setRelay(new Relay(n.asText())), tag); + return (T) tag; + } } From d8a381400dd5c56e6ea3c6a38956d53df0e446cf Mon Sep 17 00:00:00 2001 From: nick avlo Date: Fri, 28 Mar 2025 13:26:46 -0700 Subject: [PATCH 22/37] AddressTag variant, breaks TagDeserializer in superconductor --- .../src/main/java/nostr/event/tag/AddressTag.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java b/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java index c059b1ab5..1ab0e8315 100644 --- a/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java @@ -33,19 +33,19 @@ public class AddressTag extends BaseTag { @Key - @JsonProperty + @JsonProperty("kind") private Integer kind; @Key - @JsonProperty + @JsonProperty("publicKey") private PublicKey publicKey; @Key - @JsonProperty + @JsonProperty("identifierTag") private IdentifierTag identifierTag; @Key - @JsonProperty + @JsonProperty("relay") @JsonInclude(JsonInclude.Include.NON_NULL) private Relay relay; From 9c5b8e652b689061d07ee984a46feb22967aef5d Mon Sep 17 00:00:00 2001 From: nick avlo Date: Tue, 1 Apr 2025 17:59:29 -0700 Subject: [PATCH 23/37] add vote tag --- .../nostr/event/filter/VoteTagFilter.java | 36 ++++++++++++++ .../event/json/codec/FilterableProvider.java | 47 ++++++++++--------- .../json/deserializer/TagDeserializer.java | 2 + .../main/java/nostr/event/tag/VoteTag.java | 32 +++++++++++++ 4 files changed, 94 insertions(+), 23 deletions(-) create mode 100644 nostr-java-event/src/main/java/nostr/event/filter/VoteTagFilter.java create mode 100644 nostr-java-event/src/main/java/nostr/event/tag/VoteTag.java diff --git a/nostr-java-event/src/main/java/nostr/event/filter/VoteTagFilter.java b/nostr-java-event/src/main/java/nostr/event/filter/VoteTagFilter.java new file mode 100644 index 000000000..3d0f180db --- /dev/null +++ b/nostr-java-event/src/main/java/nostr/event/filter/VoteTagFilter.java @@ -0,0 +1,36 @@ +package nostr.event.filter; + +import com.fasterxml.jackson.databind.JsonNode; +import java.util.function.Function; +import java.util.function.Predicate; +import lombok.EqualsAndHashCode; +import nostr.event.impl.GenericEvent; +import nostr.event.tag.VoteTag; + +@EqualsAndHashCode(callSuper = true) +public class VoteTagFilter extends AbstractFilterable { + public final static String FILTER_KEY = "#v"; + + public VoteTagFilter(T voteTag) { + super(voteTag, FILTER_KEY); + } + + @Override + public Predicate getPredicate() { + return (genericEvent) -> + Filterable.getTypeSpecificTags(VoteTag.class, genericEvent).stream() + .anyMatch(voteTag -> + voteTag.getVote().equals(getFilterableValue())); + } + + @Override + public Integer getFilterableValue() { + return getVoteTag().getVote(); + } + + private T getVoteTag() { + return super.getFilterable(); + } + + public static Function fxn = node -> new VoteTagFilter<>(new VoteTag(node.asInt())); +} diff --git a/nostr-java-event/src/main/java/nostr/event/json/codec/FilterableProvider.java b/nostr-java-event/src/main/java/nostr/event/json/codec/FilterableProvider.java index 3a839799e..c28782489 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/codec/FilterableProvider.java +++ b/nostr-java-event/src/main/java/nostr/event/json/codec/FilterableProvider.java @@ -1,6 +1,9 @@ package nostr.event.json.codec; import com.fasterxml.jackson.databind.JsonNode; +import java.util.List; +import java.util.function.Function; +import java.util.stream.StreamSupport; import lombok.NonNull; import nostr.event.filter.AddressTagFilter; import nostr.event.filter.AuthorFilter; @@ -15,30 +18,28 @@ import nostr.event.filter.ReferencedPublicKeyFilter; import nostr.event.filter.SinceFilter; import nostr.event.filter.UntilFilter; - -import java.util.List; -import java.util.function.Function; -import java.util.stream.StreamSupport; +import nostr.event.filter.VoteTagFilter; public class FilterableProvider { - protected static List getFilterFunction(@NonNull JsonNode node, @NonNull String type) { - return switch (type) { - case ReferencedPublicKeyFilter.FILTER_KEY -> getFilterable(node, ReferencedPublicKeyFilter.fxn); - case ReferencedEventFilter.FILTER_KEY -> getFilterable(node, ReferencedEventFilter.fxn); - case AddressTagFilter.FILTER_KEY -> getFilterable(node, AddressTagFilter.fxn); - case IdentifierTagFilter.FILTER_KEY -> getFilterable(node, IdentifierTagFilter.fxn); - case GeohashTagFilter.FILTER_KEY -> getFilterable(node, GeohashTagFilter.fxn); - case HashtagTagFilter.FILTER_KEY -> getFilterable(node, HashtagTagFilter.fxn); - case AuthorFilter.FILTER_KEY -> getFilterable(node, AuthorFilter.fxn); - case EventFilter.FILTER_KEY -> getFilterable(node, EventFilter.fxn); - case KindFilter.FILTER_KEY -> getFilterable(node, KindFilter.fxn); - case SinceFilter.FILTER_KEY -> SinceFilter.fxn.apply(node); - case UntilFilter.FILTER_KEY -> UntilFilter.fxn.apply(node); - default -> getFilterable(node, GenericTagQueryFilter.fxn(type)); - }; - } + protected static List getFilterFunction(@NonNull JsonNode node, @NonNull String type) { + return switch (type) { + case ReferencedPublicKeyFilter.FILTER_KEY -> getFilterable(node, ReferencedPublicKeyFilter.fxn); + case ReferencedEventFilter.FILTER_KEY -> getFilterable(node, ReferencedEventFilter.fxn); + case AddressTagFilter.FILTER_KEY -> getFilterable(node, AddressTagFilter.fxn); + case IdentifierTagFilter.FILTER_KEY -> getFilterable(node, IdentifierTagFilter.fxn); + case GeohashTagFilter.FILTER_KEY -> getFilterable(node, GeohashTagFilter.fxn); + case HashtagTagFilter.FILTER_KEY -> getFilterable(node, HashtagTagFilter.fxn); + case VoteTagFilter.FILTER_KEY -> getFilterable(node, VoteTagFilter.fxn); + case AuthorFilter.FILTER_KEY -> getFilterable(node, AuthorFilter.fxn); + case EventFilter.FILTER_KEY -> getFilterable(node, EventFilter.fxn); + case KindFilter.FILTER_KEY -> getFilterable(node, KindFilter.fxn); + case SinceFilter.FILTER_KEY -> SinceFilter.fxn.apply(node); + case UntilFilter.FILTER_KEY -> UntilFilter.fxn.apply(node); + default -> getFilterable(node, GenericTagQueryFilter.fxn(type)); + }; + } - private static List getFilterable(JsonNode jsonNode, Function filterFunction) { - return StreamSupport.stream(jsonNode.spliterator(), false).map(filterFunction).toList(); - } + private static List getFilterable(JsonNode jsonNode, Function filterFunction) { + return StreamSupport.stream(jsonNode.spliterator(), false).map(filterFunction).toList(); + } } diff --git a/nostr-java-event/src/main/java/nostr/event/json/deserializer/TagDeserializer.java b/nostr-java-event/src/main/java/nostr/event/json/deserializer/TagDeserializer.java index 3eb44c106..efbe5b95c 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/deserializer/TagDeserializer.java +++ b/nostr-java-event/src/main/java/nostr/event/json/deserializer/TagDeserializer.java @@ -17,6 +17,7 @@ import nostr.event.tag.SubjectTag; import java.io.IOException; +import nostr.event.tag.VoteTag; public class TagDeserializer extends JsonDeserializer { @@ -37,6 +38,7 @@ public T deserialize(JsonParser jsonParser, DeserializationContext deserializati case "g" -> GeohashTag.deserialize(node); case "p" -> PubKeyTag.deserialize(node); case "t" -> HashtagTag.deserialize(node); + case "v" -> VoteTag.deserialize(node); case "nonce" -> NonceTag.deserialize(node); case "price" -> PriceTag.deserialize(node); diff --git a/nostr-java-event/src/main/java/nostr/event/tag/VoteTag.java b/nostr-java-event/src/main/java/nostr/event/tag/VoteTag.java new file mode 100644 index 000000000..4943a30c5 --- /dev/null +++ b/nostr-java-event/src/main/java/nostr/event/tag/VoteTag.java @@ -0,0 +1,32 @@ +package nostr.event.tag; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import nostr.base.annotation.Key; +import nostr.base.annotation.Tag; +import nostr.event.BaseTag; + +@Builder +@Data +@EqualsAndHashCode(callSuper = true) +@Tag(code = "v", nip = 2112) +@NoArgsConstructor +@AllArgsConstructor +public class VoteTag extends BaseTag { + + @Key + @JsonProperty("v") + private Integer vote; + + public static T deserialize(@NonNull JsonNode node) { + VoteTag tag = new VoteTag(); + setRequiredField(node.get(1), (n, t) -> tag.setVote(n.asInt()), tag); + return (T) tag; + } +} From ca379aab85dc7178a5c505d6c31a12676d871814 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Tue, 1 Apr 2025 20:33:58 -0700 Subject: [PATCH 24/37] updated IT tests --- .../api/integration/ApiNIP52RequestIT.java | 307 +++++++++--------- .../api/util/CommonTestObjectsFactory.java | 152 +++++++++ 2 files changed, 305 insertions(+), 154 deletions(-) create mode 100644 nostr-java-api/src/test/java/nostr/api/util/CommonTestObjectsFactory.java diff --git a/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP52RequestIT.java b/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP52RequestIT.java index 6775956f3..430159dd0 100644 --- a/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP52RequestIT.java +++ b/nostr-java-api/src/test/java/nostr/api/integration/ApiNIP52RequestIT.java @@ -1,15 +1,22 @@ package nostr.api.integration; +import java.io.IOException; +import java.net.URI; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; import nostr.api.NIP52; +import nostr.api.util.CommonTestObjectsFactory; import nostr.api.util.JsonComparator; import nostr.base.PublicKey; import nostr.client.springwebsocket.SpringWebSocketClient; import nostr.event.BaseTag; import nostr.event.impl.CalendarContent; import nostr.event.impl.GenericEvent; -import nostr.event.tag.GenericTag; import nostr.event.message.EventMessage; import nostr.event.tag.EventTag; +import nostr.event.tag.GenericTag; import nostr.event.tag.GeohashTag; import nostr.event.tag.HashtagTag; import nostr.event.tag.IdentifierTag; @@ -18,115 +25,107 @@ import nostr.id.Identity; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; - -import java.io.IOException; -import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - import static nostr.base.IEvent.MAPPER_AFTERBURNER; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @ActiveProfiles("test") class ApiNIP52RequestIT { - private static final String PRV_KEY_VALUE = "23c011c4c02de9aa98d48c3646c70bb0e7ae30bdae1dfed4d251cbceadaeeb7b"; - private static final String RELAY_URI = "ws://localhost:5555"; - private static final String UUID_CALENDAR_TIME_BASED_EVENT_TEST = "UUID-CalendarTimeBasedEventTest"; - - public static final String ID = "299ab85049a7923e9cd82329c0fa489ca6fd6d21feeeac33543b1237e14a9e07"; - public static final String KIND = "31923"; - public static final String CALENDAR_CONTENT = "calendar content"; - public static final String PUB_KEY = "cccd79f81439ff794cf5ac5f7bff9121e257f399829e472c7a14d3e86fe76984"; - public static final String CREATED_AT = "1726114798510"; - public static final String START = "1726114798610"; - public static final String END = "1726114798710"; - - public static final String START_TZID = "America/Costa_Rica"; - public static final String END_TZID = "America/Costa_Rica"; - - public static final String E_TAG_HEX = "494001ac0c8af2a10f60f23538e5b35d3cdacb8e1cc956fe7a16dfa5cbfc4346"; - public static final String G_TAG_VALUE = "calendar geo-tag-1"; - public static final String T_TAG_VALUE = "calendar hash-tag-1111"; - public static final String P1_TAG_HEX = "444d79f81439ff794cf5ac5f7bff9121e257f399829e472c7a14d3e86fe76984"; - public static final String P1_ROLE = "PAYER"; - public static final String P2_ROLE = "PAYEE"; - - public static final String P2_TAG_HEX = "555d79f81439ff794cf5ac5f7bff9121e257f399829e472c7a14d3e86fe76984"; - public static final String TITLE = "calendar title"; - public static final String SUMMARY = "calendar summary"; - public static final String LOCATION = "calendar location"; - - public static final EventTag E_TAG = new EventTag(E_TAG_HEX); - public static final PubKeyTag P1_TAG = new PubKeyTag(new PublicKey(P1_TAG_HEX), RELAY_URI, P1_ROLE); - public static final PubKeyTag P2_TAG = new PubKeyTag(new PublicKey(P2_TAG_HEX), RELAY_URI, P2_ROLE); - public static final GeohashTag G_TAG = new GeohashTag(G_TAG_VALUE); - public static final HashtagTag T_TAG = new HashtagTag(T_TAG_VALUE); - public static final ReferenceTag R_TAG = new ReferenceTag(URI.create(RELAY_URI)); - - public static final String LABEL_1 = "calendar label 1 of 2"; - public static final String LABEL_2 = "calendar label 2 of 2"; - - public static final String START_TZID_CODE = "start_tzid"; - public static final String END_TZID_CODE = "end_tzid"; - public static final String SUMMARY_CODE = "summary"; - public static final String LABEL_CODE = "l"; - public static final String LOCATION_CODE = "location"; - public static final String END_CODE = "end"; - - public String eventId; - public String eventPubKey; - public String signature; - - @Test - void testNIP99CalendarContentPreRequest() throws IOException { - System.out.println("testNIP52CalendarContentEvent"); - - List tags = new ArrayList<>(); - tags.add(E_TAG); - tags.add(P1_TAG); - tags.add(P2_TAG); - tags.add(GenericTag.create(START_TZID_CODE, START_TZID)); - tags.add(GenericTag.create(END_TZID_CODE, END_TZID)); - tags.add(GenericTag.create(SUMMARY_CODE, SUMMARY)); - tags.add(GenericTag.create(LABEL_CODE, LABEL_1)); - tags.add(GenericTag.create(LABEL_CODE, LABEL_2)); - tags.add(GenericTag.create(LOCATION_CODE, LOCATION)); - tags.add(GenericTag.create(END_CODE, END)); - tags.add(G_TAG); - tags.add(T_TAG); - tags.add(R_TAG); - - CalendarContent calendarContent = CalendarContent.builder( - new IdentifierTag(UUID_CALENDAR_TIME_BASED_EVENT_TEST), - TITLE, - Long.valueOf(START)) - .build(); - - var nip52 = new NIP52<>(Identity.create(PRV_KEY_VALUE)); - - GenericEvent event = nip52.createCalendarTimeBasedEvent(tags, CALENDAR_CONTENT, calendarContent).sign().getEvent(); - event.setCreatedAt(Long.valueOf(CREATED_AT)); - eventId = event.getId(); - signature = event.getSignature().toString(); - eventPubKey = event.getPubKey().toString(); - EventMessage eventMessage = new EventMessage(event); - - SpringWebSocketClient springWebSocketEventClient = new SpringWebSocketClient(RELAY_URI); - String eventResponse = springWebSocketEventClient.send(eventMessage).stream().findFirst().orElseThrow(); - - // Extract and compare only first 3 elements of the JSON array - var expectedArray = MAPPER_AFTERBURNER.readTree(expectedEventResponseJson(event.getId())).get(0).asText(); - var expectedSubscriptionId = MAPPER_AFTERBURNER.readTree(expectedEventResponseJson(event.getId())).get(1).asText(); - var expectedSuccess = MAPPER_AFTERBURNER.readTree(expectedEventResponseJson(event.getId())).get(2).asBoolean(); - - var actualArray = MAPPER_AFTERBURNER.readTree(eventResponse).get(0).asText(); - var actualSubscriptionId = MAPPER_AFTERBURNER.readTree(eventResponse).get(1).asText(); - var actualSuccess = MAPPER_AFTERBURNER.readTree(eventResponse).get(2).asBoolean(); - - assertTrue(expectedArray.equals(actualArray), "First element should match"); - assertTrue(expectedSubscriptionId.equals(actualSubscriptionId), "Subscription ID should match"); - //assertTrue(expectedSuccess == actualSuccess, "Success flag should match"); -- This test is not required. The relay will always return false because we resending the same event, causing duplicates. + private static final String RELAY_URI = "ws://localhost:5555"; + private static final String UUID_CALENDAR_TIME_BASED_EVENT_TEST = CommonTestObjectsFactory.generateRandomHex64String(); + + public static final String KIND = "31923"; + public static final String CALENDAR_CONTENT = CommonTestObjectsFactory.lorumIpsum(ApiNIP52RequestIT.class); + + public static final String CREATED_AT = String.valueOf(Instant.now().getEpochSecond()); + public static final String START = String.valueOf(Instant.now().plus(Duration.ofSeconds(10)).getEpochSecond()); + public static final String END = String.valueOf(Instant.now().plus(Duration.ofSeconds(100)).getEpochSecond()); + + public static final String START_TZID = "America/Costa_Rica"; + public static final String END_TZID = "America/Costa_Rica"; + + public static final String E_TAG_HEX = CommonTestObjectsFactory.generateRandomHex64String(); + public static final String G_TAG_VALUE = CommonTestObjectsFactory.generateRandomHex64String(); + public static final String T_TAG_VALUE = CommonTestObjectsFactory.generateRandomHex64String(); + public static final PublicKey P1_TAG_HEX = CommonTestObjectsFactory.createNewIdentity().getPublicKey(); + public static final String P1_ROLE = "PAYER"; + public static final String P2_ROLE = "PAYEE"; + + public static final PublicKey P2_TAG_HEX = CommonTestObjectsFactory.createNewIdentity().getPublicKey(); + public static final String TITLE = CommonTestObjectsFactory.lorumIpsum(); + public static final String SUMMARY = CommonTestObjectsFactory.lorumIpsum(); + public static final String LOCATION = CommonTestObjectsFactory.lorumIpsum(); + + public static final EventTag E_TAG = new EventTag(E_TAG_HEX); + public static final PubKeyTag P1_TAG = new PubKeyTag(P1_TAG_HEX, RELAY_URI, P1_ROLE); + public static final PubKeyTag P2_TAG = new PubKeyTag(P2_TAG_HEX, RELAY_URI, P2_ROLE); + public static final GeohashTag G_TAG = new GeohashTag(G_TAG_VALUE); + public static final HashtagTag T_TAG = new HashtagTag(T_TAG_VALUE); + public static final ReferenceTag R_TAG = new ReferenceTag(URI.create(RELAY_URI)); + + public static final String LABEL_1 = CommonTestObjectsFactory.lorumIpsum(); + public static final String LABEL_2 = CommonTestObjectsFactory.lorumIpsum(); + + public static final String START_TZID_CODE = "start_tzid"; + public static final String END_TZID_CODE = "end_tzid"; + public static final String SUMMARY_CODE = "summary"; + public static final String LABEL_CODE = "l"; + public static final String LOCATION_CODE = "location"; + public static final String END_CODE = "end"; + + public String eventId; + public String eventPubKey; + public String signature; + + @Test + void testNIP99CalendarContentPreRequest() throws IOException { + System.out.println("testNIP52CalendarContentEvent"); + + List tags = new ArrayList<>(); + tags.add(E_TAG); + tags.add(P1_TAG); + tags.add(P2_TAG); + tags.add(GenericTag.create(START_TZID_CODE, START_TZID)); + tags.add(GenericTag.create(END_TZID_CODE, END_TZID)); + tags.add(GenericTag.create(SUMMARY_CODE, SUMMARY)); + tags.add(GenericTag.create(LABEL_CODE, LABEL_1)); + tags.add(GenericTag.create(LABEL_CODE, LABEL_2)); + tags.add(GenericTag.create(LOCATION_CODE, LOCATION)); + tags.add(GenericTag.create(END_CODE, END)); + tags.add(G_TAG); + tags.add(T_TAG); + tags.add(R_TAG); + + CalendarContent calendarContent = CalendarContent.builder( + new IdentifierTag(UUID_CALENDAR_TIME_BASED_EVENT_TEST), + TITLE, + Long.valueOf(START)) + .build(); + + var nip52 = new NIP52<>(Identity.generateRandomIdentity()); + + GenericEvent event = nip52.createCalendarTimeBasedEvent(tags, CALENDAR_CONTENT, calendarContent).sign().getEvent(); + event.setCreatedAt(Long.valueOf(CREATED_AT)); + eventId = event.getId(); + signature = event.getSignature().toString(); + eventPubKey = event.getPubKey().toString(); + EventMessage eventMessage = new EventMessage(event); + + SpringWebSocketClient springWebSocketEventClient = new SpringWebSocketClient(RELAY_URI); + String eventResponse = springWebSocketEventClient.send(eventMessage).stream().findFirst().orElseThrow(); + + // Extract and compare only first 3 elements of the JSON array + var expectedArray = MAPPER_AFTERBURNER.readTree(expectedEventResponseJson(event.getId())).get(0).asText(); + var expectedSubscriptionId = MAPPER_AFTERBURNER.readTree(expectedEventResponseJson(event.getId())).get(1).asText(); + var expectedSuccess = MAPPER_AFTERBURNER.readTree(expectedEventResponseJson(event.getId())).get(2).asBoolean(); + + var actualArray = MAPPER_AFTERBURNER.readTree(eventResponse).get(0).asText(); + var actualSubscriptionId = MAPPER_AFTERBURNER.readTree(eventResponse).get(1).asText(); + var actualSuccess = MAPPER_AFTERBURNER.readTree(eventResponse).get(2).asBoolean(); + + assertEquals(expectedArray, actualArray, "First element should match"); + assertEquals(expectedSubscriptionId, actualSubscriptionId, "Subscription ID should match"); + //assertTrue(expectedSuccess == actualSuccess, "Success flag should match"); -- This test is not required. The relay will always return false because we resending the same event, causing duplicates. // springWebSocketEventClient.closeSocket(); @@ -136,55 +135,55 @@ void testNIP99CalendarContentPreRequest() throws IOException { integration testing successful against superconductor. integration testing against nostr-rs-relay still pending */ - - SpringWebSocketClient springWebSocketRequestClient = new SpringWebSocketClient(RELAY_URI); - String subscriberId = UUID.randomUUID().toString(); - String reqJson = createReqJson(subscriberId, eventId); - String reqResponse = springWebSocketRequestClient.send(reqJson).stream().findFirst().orElseThrow(); - - String expected = expectedRequestResponseJson(subscriberId); - assertTrue( - JsonComparator.isEquivalentJson( - MAPPER_AFTERBURNER.readTree(expected), - MAPPER_AFTERBURNER.readTree(reqResponse))); + + SpringWebSocketClient springWebSocketRequestClient = new SpringWebSocketClient(RELAY_URI); + String subscriberId = CommonTestObjectsFactory.generateRandomHex64String(); + String reqJson = createReqJson(subscriberId, eventId); + String reqResponse = springWebSocketRequestClient.send(reqJson).stream().findFirst().orElseThrow(); + + String expected = expectedRequestResponseJson(subscriberId); + assertTrue( + JsonComparator.isEquivalentJson( + MAPPER_AFTERBURNER.readTree(expected), + MAPPER_AFTERBURNER.readTree(reqResponse))); // springWebSocketRequestClient.closeSocket(); - } - - private String expectedEventResponseJson(String subscriptionId) { - return "[\"OK\",\"" + subscriptionId + "\",true,\"success: request processed\"]"; - } - - private String createReqJson(String subscriberId, String id) { - return "[\"REQ\",\"" + subscriberId + "\",{\"ids\":[\"" + id + "\"]}]"; - } - - private String expectedRequestResponseJson(String subscriberId) { - return " [\"EVENT\",\"" + subscriberId + "\",\n" + - " {\"id\": \"" + eventId + "\",\n" + - " \"kind\": " + KIND + ",\n" + - " \"content\": \"" + CALENDAR_CONTENT + "\",\n" + - " \"pubkey\": \"" + eventPubKey + "\",\n" + - " \"created_at\": " + CREATED_AT + ",\n" + - " \"tags\": [\n" + - " [ \"e\", \"" + E_TAG.getIdEvent() + "\" ],\n" + - " [ \"g\", \"" + G_TAG.getLocation() + "\" ],\n" + - " [ \"t\", \"" + T_TAG.getHashTag() + "\" ],\n" + - " [ \"d\", \"" + UUID_CALENDAR_TIME_BASED_EVENT_TEST + "\" ],\n" + - " [ \"p\", \"" + P1_TAG.getPublicKey() + "\", \"" + RELAY_URI + "\", \"" + P1_ROLE + "\" ],\n" + - " [ \"p\", \"" + P2_TAG.getPublicKey() + "\", \"" + RELAY_URI + "\", \"" + P2_ROLE + "\" ],\n" + - " [ \"start_tzid\", \"" + START_TZID + "\" ],\n" + - " [ \"end_tzid\", \"" + END_TZID + "\" ],\n" + - " [ \"summary\", \"" + SUMMARY + "\" ],\n" + - " [ \"l\", \"" + LABEL_1 + "\" ],\n" + - " [ \"l\", \"" + LABEL_2 + "\" ],\n" + - " [ \"location\", \"" + LOCATION + "\" ],\n" + - " [ \"r\", \"" + URI.create(RELAY_URI) + "\" ],\n" + - " [ \"title\", \"" + TITLE + "\" ],\n" + - " [ \"start\", \"" + START + "\" ],\n" + - " [ \"end\", \"" + END + "\" ]\n" + - " ],\n" + - " \"sig\": \"" + signature + "\"\n" + - " }]"; - } + } + + private String expectedEventResponseJson(String subscriptionId) { + return "[\"OK\",\"" + subscriptionId + "\",true,\"success: request processed\"]"; + } + + private String createReqJson(String subscriberId, String id) { + return "[\"REQ\",\"" + subscriberId + "\",{\"ids\":[\"" + id + "\"]}]"; + } + + private String expectedRequestResponseJson(String subscriberId) { + return " [\"EVENT\",\"" + subscriberId + "\",\n" + + " {\"id\": \"" + eventId + "\",\n" + + " \"kind\": " + KIND + ",\n" + + " \"content\": \"" + CALENDAR_CONTENT + "\",\n" + + " \"pubkey\": \"" + eventPubKey + "\",\n" + + " \"created_at\": " + CREATED_AT + ",\n" + + " \"tags\": [\n" + + " [ \"e\", \"" + E_TAG.getIdEvent() + "\" ],\n" + + " [ \"g\", \"" + G_TAG.getLocation() + "\" ],\n" + + " [ \"t\", \"" + T_TAG.getHashTag() + "\" ],\n" + + " [ \"d\", \"" + UUID_CALENDAR_TIME_BASED_EVENT_TEST + "\" ],\n" + + " [ \"p\", \"" + P1_TAG.getPublicKey() + "\", \"" + RELAY_URI + "\", \"" + P1_ROLE + "\" ],\n" + + " [ \"p\", \"" + P2_TAG.getPublicKey() + "\", \"" + RELAY_URI + "\", \"" + P2_ROLE + "\" ],\n" + + " [ \"start_tzid\", \"" + START_TZID + "\" ],\n" + + " [ \"end_tzid\", \"" + END_TZID + "\" ],\n" + + " [ \"summary\", \"" + SUMMARY + "\" ],\n" + + " [ \"l\", \"" + LABEL_1 + "\" ],\n" + + " [ \"l\", \"" + LABEL_2 + "\" ],\n" + + " [ \"location\", \"" + LOCATION + "\" ],\n" + + " [ \"r\", \"" + URI.create(RELAY_URI) + "\" ],\n" + + " [ \"title\", \"" + TITLE + "\" ],\n" + + " [ \"start\", \"" + START + "\" ],\n" + + " [ \"end\", \"" + END + "\" ]\n" + + " ],\n" + + " \"sig\": \"" + signature + "\"\n" + + " }]"; + } } diff --git a/nostr-java-api/src/test/java/nostr/api/util/CommonTestObjectsFactory.java b/nostr-java-api/src/test/java/nostr/api/util/CommonTestObjectsFactory.java new file mode 100644 index 000000000..3c07ce2d4 --- /dev/null +++ b/nostr-java-api/src/test/java/nostr/api/util/CommonTestObjectsFactory.java @@ -0,0 +1,152 @@ +package nostr.api.util; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Random; +import java.util.UUID; +import lombok.Getter; +import nostr.api.factory.impl.NIP01Impl; +import nostr.api.factory.impl.NIP99Impl; +import nostr.event.BaseTag; +import nostr.event.impl.ClassifiedListing; +import nostr.event.impl.GenericEvent; +import nostr.event.impl.TextNoteEvent; +import nostr.event.tag.EventTag; +import nostr.event.tag.GeohashTag; +import nostr.event.tag.HashtagTag; +import nostr.event.tag.PriceTag; +import nostr.event.tag.PubKeyTag; +import nostr.event.tag.SubjectTag; +import nostr.id.Identity; +import org.apache.commons.lang3.RandomStringUtils; + +public class CommonTestObjectsFactory { + + public static Identity createNewIdentity() { + return Identity.generateRandomIdentity(); + } + + public static T createTextNoteEvent(Identity identity, List tags, String content) { + TextNoteEvent textNoteEvent = new NIP01Impl.TextNoteEventFactory(identity, tags, content).create(); +// NIP01 nip01_1 = new NIP01<>(identity); +// EventNostr sign = nip01_1.createTextNoteEvent(tags, content).sign(); +// return sign; + return (T) textNoteEvent; + } + + public static T createClassifiedListingEvent( + Identity identity, + List tags, + String content, + ClassifiedListing cl) { + + return (T) new NIP99Impl.ClassifiedListingEventFactory(identity, tags, content, cl).create(); + } + + public static GenericEvent createGenericEvent() { + String concat = generateRandomHex64String(); + return new GenericEvent(concat.substring(0, 64)); + } + + public static SubjectTag createSubjectTag(Class clazz) { + return new SubjectTag(clazz.getName() + " Subject Tag"); + } + + public static PubKeyTag createPubKeyTag(Identity identity) { + return new PubKeyTag(identity.getPublicKey()); + } + + public static GeohashTag createGeohashTag(Class clazz) { + return new GeohashTag(clazz.getName() + " Geohash Tag"); + } + + public static HashtagTag createHashtagTag(Class clazz) { + return new HashtagTag(clazz.getName() + " Hashtag Tag"); + } + + public static EventTag createEventTag(Class clazz) { + return new EventTag(createGenericEvent().getId()); + } + + public static PriceTag createPriceTag() { + PriceComposite pc = new PriceComposite(); + BigDecimal NUMBER = pc.getPrice(); + String CURRENCY = pc.getCurrency(); + String FREQUENCY = pc.getFrequency(); + return new PriceTag(NUMBER, CURRENCY, FREQUENCY); + } + + public static ClassifiedListing createClassifiedListing(String title, String summary) { + return new ClassifiedListingComposite(title, summary, createPriceTag()).getClassifiedListing(); + } + + public static String lorumIpsum() { + return lorumIpsum(CommonTestObjectsFactory.class); + } + + public static String lorumIpsum(Class clazz) { + return lorumIpsum(clazz, 64); + } + + public static String lorumIpsum(Class clazz, int length) { + return lorumIpsum(clazz.getSimpleName(), length); + } + + public static String lorumIpsum(String s, int length) { + boolean useLetters = false; + boolean useNumbers = true; + return cullStringLength( + String.join("-", s, generateRandomAlphaNumericString(length, useLetters, useNumbers)) + , 64); + } + + public static String lnUrl() { +// lnurl1dp68gurn8ghj7um5v93kketj9ehx2amn9uh8wetvdskkkmn0wahz7mrww4excup0dajx2mrv92x9xp +// match lnUrl string length of 84 + return cullStringLength("lnurl" + generateRandomHex64String(), 84); + } + + private static String cullStringLength(String s, int x) { + return s.length() > x ? s.substring(0, x) : s; + } + + private static String generateRandomAlphaNumericString(int length, boolean useLetters, boolean useNumbers) { + return RandomStringUtils.random(length, useLetters, useNumbers); + } + + public static String generateRandomHex64String() { + return UUID.randomUUID().toString().concat(UUID.randomUUID().toString()).replaceAll("[^A-Za-z0-9]", ""); + } + + public static BigDecimal createRandomBigDecimal() { + Random rand = new Random(); + int max = 100, min = 50; + int i = rand.nextInt(max - min + 1) + min; + int j = (rand.nextInt(max - min + 1) + min); + return new BigDecimal(String.valueOf(i) + '.' + j); + } + + @Getter + public static class PriceComposite { + private final String currency = "BTC"; + private final String frequency = "nanosecond"; + private final BigDecimal price; + + private PriceComposite() { + price = createRandomBigDecimal(); + } + } + + @Getter + public static class ClassifiedListingComposite { + private final ClassifiedListing classifiedListing; + + private ClassifiedListingComposite(String title, String summary, PriceTag priceTag) { + this.classifiedListing = ClassifiedListing.builder( + title, + summary, + priceTag) + .build(); + } + } +} From 0115366e9717ed46be24575430145532021ddd91 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Wed, 2 Apr 2025 19:28:13 -0700 Subject: [PATCH 25/37] addresstag updates --- .../src/main/java/nostr/event/tag/AddressTag.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java b/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java index 1ab0e8315..5a551c9e6 100644 --- a/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java @@ -19,7 +19,6 @@ import nostr.event.json.serializer.AddressTagSerializer; /** - * * @author eric */ @Builder @@ -33,19 +32,23 @@ public class AddressTag extends BaseTag { @Key - @JsonProperty("kind") +// @JsonProperty("kind") + @JsonProperty private Integer kind; @Key - @JsonProperty("publicKey") +// @JsonProperty("publicKey") + @JsonProperty private PublicKey publicKey; @Key - @JsonProperty("identifierTag") +// @JsonProperty("identifierTag") + @JsonProperty private IdentifierTag identifierTag; @Key - @JsonProperty("relay") +// @JsonProperty("relay") + @JsonProperty @JsonInclude(JsonInclude.Include.NON_NULL) private Relay relay; From 7cc9f345e62259b9543ec2290ee871e5656b83c1 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Thu, 10 Apr 2025 16:47:44 -0700 Subject: [PATCH 26/37] addressTag filter updates --- .../src/main/java/nostr/api/NIP09.java | 2 +- .../integration/ZDoLastApiNIP09EventIT.java | 2 +- .../test/java/nostr/api/unit/NIP60Test.java | 6 +- .../nostr/event/filter/AddressTagFilter.java | 66 ++++---- .../event/filter/IdentifierTagFilter.java | 4 +- .../event/json/codec/FilterableProvider.java | 2 +- .../json/serializer/AddressTagSerializer.java | 23 +-- .../main/java/nostr/event/tag/AddressTag.java | 25 +-- .../java/nostr/event/tag/IdentifierTag.java | 6 +- .../event/unit/EventWithAddressTagTest.java | 147 ++++++++++++++++++ .../java/nostr/id/ZapReceiptEventTest.java | 2 +- 11 files changed, 213 insertions(+), 72 deletions(-) create mode 100644 nostr-java-event/src/test/java/nostr/event/unit/EventWithAddressTagTest.java diff --git a/nostr-java-api/src/main/java/nostr/api/NIP09.java b/nostr-java-api/src/main/java/nostr/api/NIP09.java index 98470710b..b839f6bf9 100644 --- a/nostr-java-api/src/main/java/nostr/api/NIP09.java +++ b/nostr-java-api/src/main/java/nostr/api/NIP09.java @@ -75,7 +75,7 @@ public NIP09 createDeletionEvent(@NonNull List deleteables) { private AddressTag toAddressTag(@NonNull GenericTag genericTag) { IdentifierTag identifierTag = new IdentifierTag(); - identifierTag.setId(genericTag.getAttributes().get(1).getValue().toString()); + identifierTag.setUuid(genericTag.getAttributes().get(1).getValue().toString()); AddressTag addressTag = new AddressTag(); addressTag.setIdentifierTag(identifierTag); diff --git a/nostr-java-api/src/test/java/nostr/api/integration/ZDoLastApiNIP09EventIT.java b/nostr-java-api/src/test/java/nostr/api/integration/ZDoLastApiNIP09EventIT.java index 50ea7b6d9..76cb12953 100644 --- a/nostr-java-api/src/test/java/nostr/api/integration/ZDoLastApiNIP09EventIT.java +++ b/nostr-java-api/src/test/java/nostr/api/integration/ZDoLastApiNIP09EventIT.java @@ -118,7 +118,7 @@ public void deleteEventWithRef() throws IOException { AddressTag addressTag = (AddressTag) addressTags.get(0); assertEquals(10_001, addressTag.getKind()); - assertEquals(replaceableEvent.getId(), addressTag.getIdentifierTag().getId()); + assertEquals(replaceableEvent.getId(), addressTag.getIdentifierTag().getUuid()); assertEquals(identity.getPublicKey(), addressTag.getPublicKey()); List kindTags = deletedEvent.getTags() diff --git a/nostr-java-api/src/test/java/nostr/api/unit/NIP60Test.java b/nostr-java-api/src/test/java/nostr/api/unit/NIP60Test.java index 47f99b0fb..fdef8e9b2 100644 --- a/nostr-java-api/src/test/java/nostr/api/unit/NIP60Test.java +++ b/nostr-java-api/src/test/java/nostr/api/unit/NIP60Test.java @@ -138,7 +138,7 @@ public void createTokenEvent() throws JsonProcessingException { AddressTag aTag = (AddressTag) tags.get(0); Assertions.assertEquals("a", aTag.getCode()); // Assertions.assertEquals("", aTag.getPublicKey()); - Assertions.assertEquals("my-wallet", aTag.getIdentifierTag().getId()); + Assertions.assertEquals("my-wallet", aTag.getIdentifierTag().getUuid()); Assertions.assertEquals(37375, aTag.getKind().intValue()); // Decrypt and verify content @@ -190,7 +190,7 @@ public void createSpendingHistoryEvent() throws JsonProcessingException { // Assert a-tag AddressTag aTag = (AddressTag) tags.get(0); - Assertions.assertEquals("my-wallet", aTag.getIdentifierTag().getId()); + Assertions.assertEquals("my-wallet", aTag.getIdentifierTag().getUuid()); Assertions.assertEquals(37375, aTag.getKind().intValue()); // Decrypt and verify content @@ -258,7 +258,7 @@ public void createRedemptionQuoteEvent() { // Assert a-tag AddressTag aTag = (AddressTag) tags.get(2); - Assertions.assertEquals("my-wallet", aTag.getIdentifierTag().getId()); + Assertions.assertEquals("my-wallet", aTag.getIdentifierTag().getUuid()); Assertions.assertEquals(37375, aTag.getKind().intValue()); } diff --git a/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java b/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java index 13314c1a9..65edf2819 100644 --- a/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java +++ b/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.JsonNode; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -10,6 +11,8 @@ import lombok.EqualsAndHashCode; import lombok.NonNull; import nostr.base.PublicKey; +import nostr.base.Relay; +import nostr.event.BaseTag; import nostr.event.impl.GenericEvent; import nostr.event.tag.AddressTag; import nostr.event.tag.IdentifierTag; @@ -24,53 +27,38 @@ public AddressTagFilter(T addressableTag) { @Override public Predicate getPredicate() { - return this::compare; - } - - public static AddressTag createAddressTag(@NonNull JsonNode addressableTag) throws IllegalArgumentException { - try { - List list = Arrays.stream(addressableTag.asText().split(":")).toList(); - - AddressTag addressTag = new AddressTag(); - addressTag.setKind(Integer.valueOf(list.getFirst())); - addressTag.setPublicKey(new PublicKey(list.get(1))); - addressTag.setIdentifierTag(new IdentifierTag(list.get(2))); - - return addressTag; - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - String.format("Malformed JsonNode addressable tag: [%s]", addressableTag.asText()), e); - } + return (genericEvent) -> + Filterable.getTypeSpecificTags(AddressTag.class, genericEvent).stream() + .anyMatch(addressTag -> + addressTag.equals(getAddressableTag())); } @Override public String getFilterableValue() { - T addressableTag = getAddressableTag(); - Integer kind = addressableTag.getKind(); - String hexString = addressableTag.getPublicKey().toHexString(); - String id = addressableTag.getIdentifierTag().getId(); - - return Stream.of(kind, hexString, id) - .map(Object::toString) - .collect(Collectors.joining(":")); - } - - private boolean compare(@NonNull GenericEvent genericEvent) { - T addressableTag = getAddressableTag(); - return - !genericEvent.getPubKey().toHexString().equals( - addressableTag.getPublicKey().toHexString()) || - !genericEvent.getKind().equals( - addressableTag.getKind()) || - Filterable.getTypeSpecificTags(IdentifierTag.class, genericEvent).stream() - .anyMatch(identifierTag -> - identifierTag.getId().equals( - addressableTag.getIdentifierTag().getId())); + return Stream.of( + getAddressableTag().getKind(), + getAddressableTag().getPublicKey().toHexString(), + getAddressableTag().getIdentifierTag().getUuid()) + .map(Object::toString).collect(Collectors.joining(":")); } private T getAddressableTag() { return super.getFilterable(); } - public static Function fxn = node -> new AddressTagFilter<>(AddressTagFilter.createAddressTag(node)); + public static Function fxn = node -> + new AddressTagFilter<>(createAddressTag(node)); + + protected static T createAddressTag(@NonNull JsonNode node) { + List list = Arrays.stream(node.asText().split(":")).toList(); + + final AddressTag addressTag = new AddressTag(); + addressTag.setKind(Integer.valueOf(list.get(0))); + addressTag.setPublicKey(new PublicKey(list.get(1))); + addressTag.setIdentifierTag(new IdentifierTag(list.get(2))); + + Optional.ofNullable(node.get(2)).ifPresent(relay -> addressTag.setRelay(new Relay(relay.asText()))); + + return (T) addressTag; + } } diff --git a/nostr-java-event/src/main/java/nostr/event/filter/IdentifierTagFilter.java b/nostr-java-event/src/main/java/nostr/event/filter/IdentifierTagFilter.java index 8eb7b55b9..0b5bec5fa 100644 --- a/nostr-java-event/src/main/java/nostr/event/filter/IdentifierTagFilter.java +++ b/nostr-java-event/src/main/java/nostr/event/filter/IdentifierTagFilter.java @@ -21,12 +21,12 @@ public Predicate getPredicate() { return (genericEvent) -> Filterable.getTypeSpecificTags(IdentifierTag.class, genericEvent).stream() .anyMatch(genericEventIdentifierTag -> - genericEventIdentifierTag.getId().equals(getFilterableValue())); + genericEventIdentifierTag.getUuid().equals(getFilterableValue())); } @Override public String getFilterableValue() { - return getIdentifierTag().getId(); + return getIdentifierTag().getUuid(); } private T getIdentifierTag() { diff --git a/nostr-java-event/src/main/java/nostr/event/json/codec/FilterableProvider.java b/nostr-java-event/src/main/java/nostr/event/json/codec/FilterableProvider.java index c28782489..80d257d1f 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/codec/FilterableProvider.java +++ b/nostr-java-event/src/main/java/nostr/event/json/codec/FilterableProvider.java @@ -25,8 +25,8 @@ protected static List getFilterFunction(@NonNull JsonNode node, @Non return switch (type) { case ReferencedPublicKeyFilter.FILTER_KEY -> getFilterable(node, ReferencedPublicKeyFilter.fxn); case ReferencedEventFilter.FILTER_KEY -> getFilterable(node, ReferencedEventFilter.fxn); - case AddressTagFilter.FILTER_KEY -> getFilterable(node, AddressTagFilter.fxn); case IdentifierTagFilter.FILTER_KEY -> getFilterable(node, IdentifierTagFilter.fxn); + case AddressTagFilter.FILTER_KEY -> getFilterable(node, AddressTagFilter.fxn); case GeohashTagFilter.FILTER_KEY -> getFilterable(node, GeohashTagFilter.fxn); case HashtagTagFilter.FILTER_KEY -> getFilterable(node, HashtagTagFilter.fxn); case VoteTagFilter.FILTER_KEY -> getFilterable(node, VoteTagFilter.fxn); diff --git a/nostr-java-event/src/main/java/nostr/event/json/serializer/AddressTagSerializer.java b/nostr-java-event/src/main/java/nostr/event/json/serializer/AddressTagSerializer.java index f927c47c8..15b6f86d8 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/serializer/AddressTagSerializer.java +++ b/nostr-java-event/src/main/java/nostr/event/json/serializer/AddressTagSerializer.java @@ -3,9 +3,10 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; -import nostr.event.tag.AddressTag; - import java.io.IOException; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import nostr.event.tag.AddressTag; /** * @@ -14,17 +15,19 @@ public class AddressTagSerializer extends JsonSerializer { @Override - public void serialize(AddressTag value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException { + public void serialize(AddressTag addressTag, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException { jsonGenerator.writeStartArray(); jsonGenerator.writeString("a"); - jsonGenerator.writeString(value.getKind() + ":" + value.getPublicKey().toString() + ":"); - - if(value.getIdentifierTag() != null) { - jsonGenerator.writeString(value.getIdentifierTag().getId()); - } + + jsonGenerator.writeString( + Stream.of( + addressTag.getKind(), + addressTag.getPublicKey().toHexString(), + addressTag.getIdentifierTag().getUuid()) + .map(Object::toString).collect(Collectors.joining(":"))); - if (value.getRelay() != null) { - jsonGenerator.writeString("," + value.getRelay().getUri()); + if (addressTag.getRelay() != null) { + jsonGenerator.writeString("," + addressTag.getRelay().getUri()); } jsonGenerator.writeEndArray(); } diff --git a/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java b/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java index 5a551c9e6..312d9d958 100644 --- a/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/AddressTag.java @@ -5,6 +5,9 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -23,7 +26,7 @@ */ @Builder @Data -@EqualsAndHashCode(callSuper = true) +@EqualsAndHashCode(callSuper = false) @Tag(code = "a", nip = 33) @JsonPropertyOrder({"kind", "publicKey", "identifierTag", "relay"}) @NoArgsConstructor @@ -32,32 +35,32 @@ public class AddressTag extends BaseTag { @Key -// @JsonProperty("kind") @JsonProperty private Integer kind; @Key -// @JsonProperty("publicKey") @JsonProperty private PublicKey publicKey; @Key -// @JsonProperty("identifierTag") @JsonProperty private IdentifierTag identifierTag; @Key -// @JsonProperty("relay") @JsonProperty @JsonInclude(JsonInclude.Include.NON_NULL) private Relay relay; public static T deserialize(@NonNull JsonNode node) { - AddressTag tag = new AddressTag(); - setRequiredField(node.get(1), (n, a) -> tag.setKind(n.asInt()), tag); - setRequiredField(node.get(2), (n, a) -> tag.setPublicKey(new PublicKey(n.asText())), tag); - setRequiredField(node.get(3), (n, a) -> tag.setIdentifierTag(new IdentifierTag(n.asText())), tag); - setOptionalField(node.get(4), (n, a) -> tag.setRelay(new Relay(n.asText())), tag); - return (T) tag; + List list = Arrays.stream(node.get(1).asText().split(":")).toList(); + + final AddressTag addressTag = new AddressTag(); + addressTag.setKind(Integer.valueOf(list.get(0))); + addressTag.setPublicKey(new PublicKey(list.get(1))); + addressTag.setIdentifierTag(new IdentifierTag(list.get(2))); + + Optional.ofNullable(node.get(2)).ifPresent(relay -> addressTag.setRelay(new Relay(relay.asText()))); + + return (T) addressTag; } } diff --git a/nostr-java-event/src/main/java/nostr/event/tag/IdentifierTag.java b/nostr-java-event/src/main/java/nostr/event/tag/IdentifierTag.java index 8f1d98cf9..6f2a97b60 100644 --- a/nostr-java-event/src/main/java/nostr/event/tag/IdentifierTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/IdentifierTag.java @@ -24,12 +24,12 @@ public class IdentifierTag extends BaseTag { @Key - @JsonProperty("d") - private String id; + @JsonProperty + private String uuid; public static T deserialize(@NonNull JsonNode node) { IdentifierTag tag = new IdentifierTag(); - setRequiredField(node.get(1), (n, t) -> tag.setId(n.asText()), tag); + setRequiredField(node.get(1), (n, t) -> tag.setUuid(n.asText()), tag); return (T) tag; } } diff --git a/nostr-java-event/src/test/java/nostr/event/unit/EventWithAddressTagTest.java b/nostr-java-event/src/test/java/nostr/event/unit/EventWithAddressTagTest.java new file mode 100644 index 000000000..cbe735520 --- /dev/null +++ b/nostr-java-event/src/test/java/nostr/event/unit/EventWithAddressTagTest.java @@ -0,0 +1,147 @@ +package nostr.event.unit; + +import com.fasterxml.jackson.core.JsonProcessingException; +import java.util.ArrayList; +import java.util.List; +import nostr.base.PublicKey; +import nostr.base.Relay; +import nostr.event.BaseMessage; +import nostr.event.BaseTag; +import nostr.event.impl.GenericEvent; +import nostr.event.json.codec.BaseMessageDecoder; +import nostr.event.message.EventMessage; +import nostr.event.tag.AddressTag; +import nostr.event.tag.IdentifierTag; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.fail; + +public class EventWithAddressTagTest { + @Test + public void decodeTestWithRelay() throws JsonProcessingException { + + String json = "[" + + "\"EVENT\"," + + "{" + + "\"id\":\"28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a\"," + + "\"kind\":1," + + "\"pubkey\":\"2bed79f81439ff794cf5ac5f7bff9121e257f399829e472c7a14d3e86fe76984\"," + + "\"created_at\":1687765220," + + "\"content\":\"手順書が間違ってたら作業者は無理だな\"," + + "\"tags\":[" + + "[\"a\",\"1:f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75:UUID-1\",\"ws://localhost:8080\"]" + + "]," + + "\"sig\":\"86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546\"" + + "}]"; + + BaseMessage message = new BaseMessageDecoder<>().decode(json); + + assertEquals("EVENT", message.getCommand()); + assertInstanceOf(EventMessage.class, message); + + EventMessage eventMessage = (EventMessage) message; + + GenericEvent eventImpl = (GenericEvent) eventMessage.getEvent(); + + assertEquals("28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a", eventImpl.getId()); + assertEquals(1, eventImpl.getKind()); + assertEquals("2bed79f81439ff794cf5ac5f7bff9121e257f399829e472c7a14d3e86fe76984", eventImpl.getPubKey().toString()); + assertEquals(1687765220, eventImpl.getCreatedAt()); + assertEquals("手順書が間違ってたら作業者は無理だな", eventImpl.getContent()); + assertEquals("86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546", + eventImpl.getSignature().toString()); + + List expectedTags = new ArrayList<>(); + + Integer kind = 1; + String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + PublicKey publicKey = new PublicKey(author); + IdentifierTag identifierTag = new IdentifierTag("UUID-1"); + Relay relay = new Relay("ws://localhost:8080"); + + AddressTag addressTag = new AddressTag(); + addressTag.setKind(kind); + addressTag.setPublicKey(publicKey); + addressTag.setIdentifierTag(identifierTag); + addressTag.setRelay(relay); + expectedTags.add(addressTag); + + List actualTags = eventImpl.getTags(); + + for (int i = 0; i < expectedTags.size(); i++) { + BaseTag expected = expectedTags.get(i); + if (expected instanceof AddressTag expectedAddressTag) { + AddressTag actualAddressTag = (AddressTag) actualTags.get(i); + assertEquals(expectedAddressTag.getKind(), actualAddressTag.getKind()); + assertEquals(expectedAddressTag.getPublicKey(), actualAddressTag.getPublicKey()); + assertEquals(expectedAddressTag.getIdentifierTag(), actualAddressTag.getIdentifierTag()); + assertEquals(expectedAddressTag.getRelay(), actualAddressTag.getRelay()); + } else { + fail(); + } + } + } + + @Test + public void decodeTestWithoutRelay() throws JsonProcessingException { + + String json = "[" + + "\"EVENT\"," + + "{" + + "\"id\":\"28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a\"," + + "\"kind\":1," + + "\"pubkey\":\"2bed79f81439ff794cf5ac5f7bff9121e257f399829e472c7a14d3e86fe76984\"," + + "\"created_at\":1687765220," + + "\"content\":\"手順書が間違ってたら作業者は無理だな\"," + + "\"tags\":[" + + "[\"a\",\"1:f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75:UUID-1\"]" + + "]," + + "\"sig\":\"86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546\"" + + "}]"; + + BaseMessage message = new BaseMessageDecoder<>().decode(json); + + assertEquals("EVENT", message.getCommand()); + assertInstanceOf(EventMessage.class, message); + + EventMessage eventMessage = (EventMessage) message; + + GenericEvent eventImpl = (GenericEvent) eventMessage.getEvent(); + + assertEquals("28f2fc030e335d061f0b9d03ce0e2c7d1253e6fadb15d89bd47379a96b2c861a", eventImpl.getId()); + assertEquals(1, eventImpl.getKind()); + assertEquals("2bed79f81439ff794cf5ac5f7bff9121e257f399829e472c7a14d3e86fe76984", eventImpl.getPubKey().toString()); + assertEquals(1687765220, eventImpl.getCreatedAt()); + assertEquals("手順書が間違ってたら作業者は無理だな", eventImpl.getContent()); + assertEquals("86f25c161fec51b9e441bdb2c09095d5f8b92fdce66cb80d9ef09fad6ce53eaa14c5e16787c42f5404905536e43ebec0e463aee819378a4acbe412c533e60546", + eventImpl.getSignature().toString()); + + List expectedTags = new ArrayList<>(); + + Integer kind = 1; + String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + PublicKey publicKey = new PublicKey(author); + IdentifierTag identifierTag = new IdentifierTag("UUID-1"); + + AddressTag addressTag = new AddressTag(); + addressTag.setKind(kind); + addressTag.setPublicKey(publicKey); + addressTag.setIdentifierTag(identifierTag); + expectedTags.add(addressTag); + + List actualTags = eventImpl.getTags(); + + for (int i = 0; i < expectedTags.size(); i++) { + BaseTag expected = expectedTags.get(i); + if (expected instanceof AddressTag expectedAddressTag) { + AddressTag actualAddressTag = (AddressTag) actualTags.get(i); + assertEquals(expectedAddressTag.getKind(), actualAddressTag.getKind()); + assertEquals(expectedAddressTag.getPublicKey(), actualAddressTag.getPublicKey()); + assertEquals(expectedAddressTag.getIdentifierTag(), actualAddressTag.getIdentifierTag()); + } else { + fail(); + } + } + } +} diff --git a/nostr-java-id/src/test/java/nostr/id/ZapReceiptEventTest.java b/nostr-java-id/src/test/java/nostr/id/ZapReceiptEventTest.java index dd1d94da0..7cc17b3fd 100644 --- a/nostr-java-id/src/test/java/nostr/id/ZapReceiptEventTest.java +++ b/nostr-java-id/src/test/java/nostr/id/ZapReceiptEventTest.java @@ -39,7 +39,7 @@ void testConstructZapReceiptEvent() { assertTrue(instance.getTags().stream().filter(AddressTag.class::isInstance).map(AddressTag.class::cast).map(addressTag -> addressTag.getRelay().getUri()).anyMatch(ZAP_RECEIPT_RELAY_URI::equals)); - assertTrue(instance.getTags().stream().filter(AddressTag.class::isInstance).map(AddressTag.class::cast).map(addressTag -> addressTag.getIdentifierTag().getId()).anyMatch(ZAP_RECEIPT_IDENTIFIER::equals)); + assertTrue(instance.getTags().stream().filter(AddressTag.class::isInstance).map(AddressTag.class::cast).map(addressTag -> addressTag.getIdentifierTag().getUuid()).anyMatch(ZAP_RECEIPT_IDENTIFIER::equals)); assertEquals(BOLT_11, instance.getZapReceipt().getBolt11()); assertEquals(DESCRIPTION_SHA256, instance.getZapReceipt().getDescriptionSha256()); From 9539ff3f00c46374e8853180b6c0565bcfe90d37 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Thu, 10 Apr 2025 17:41:17 -0700 Subject: [PATCH 27/37] remove stream from serializer --- .../json/serializer/AddressTagSerializer.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/nostr-java-event/src/main/java/nostr/event/json/serializer/AddressTagSerializer.java b/nostr-java-event/src/main/java/nostr/event/json/serializer/AddressTagSerializer.java index 15b6f86d8..f8d26b857 100644 --- a/nostr-java-event/src/main/java/nostr/event/json/serializer/AddressTagSerializer.java +++ b/nostr-java-event/src/main/java/nostr/event/json/serializer/AddressTagSerializer.java @@ -3,11 +3,10 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; -import java.io.IOException; -import java.util.stream.Collectors; -import java.util.stream.Stream; import nostr.event.tag.AddressTag; +import java.io.IOException; + /** * * @author eric @@ -15,19 +14,16 @@ public class AddressTagSerializer extends JsonSerializer { @Override - public void serialize(AddressTag addressTag, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException { + public void serialize(AddressTag value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException { jsonGenerator.writeStartArray(); jsonGenerator.writeString("a"); - jsonGenerator.writeString( - Stream.of( - addressTag.getKind(), - addressTag.getPublicKey().toHexString(), - addressTag.getIdentifierTag().getUuid()) - .map(Object::toString).collect(Collectors.joining(":"))); + value.getKind() + ":" + + value.getPublicKey().toString() + ":" + + value.getIdentifierTag().getUuid()); - if (addressTag.getRelay() != null) { - jsonGenerator.writeString("," + addressTag.getRelay().getUri()); + if (value.getRelay() != null) { + jsonGenerator.writeString("," + value.getRelay().getUri()); } jsonGenerator.writeEndArray(); } From 896367abc4642a1d7ba4d8e8d3ca5548016951db Mon Sep 17 00:00:00 2001 From: nick avlo Date: Mon, 14 Apr 2025 13:16:15 -0700 Subject: [PATCH 28/37] explicit identifiertag serializer --- .../serializer/IdentifierTagSerializer.java | 20 +++++++++++++++++++ .../java/nostr/event/tag/IdentifierTag.java | 5 ++++- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 nostr-java-event/src/main/java/nostr/event/json/serializer/IdentifierTagSerializer.java diff --git a/nostr-java-event/src/main/java/nostr/event/json/serializer/IdentifierTagSerializer.java b/nostr-java-event/src/main/java/nostr/event/json/serializer/IdentifierTagSerializer.java new file mode 100644 index 000000000..b44019d1f --- /dev/null +++ b/nostr-java-event/src/main/java/nostr/event/json/serializer/IdentifierTagSerializer.java @@ -0,0 +1,20 @@ +package nostr.event.json.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import nostr.event.tag.IdentifierTag; + +import java.io.IOException; + +public class IdentifierTagSerializer extends JsonSerializer { + + @Override + public void serialize(IdentifierTag value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException { + jsonGenerator.writeStartArray(); + jsonGenerator.writeString("d"); + jsonGenerator.writeString(value.getUuid()); + jsonGenerator.writeEndArray(); + } + +} diff --git a/nostr-java-event/src/main/java/nostr/event/tag/IdentifierTag.java b/nostr-java-event/src/main/java/nostr/event/tag/IdentifierTag.java index 6f2a97b60..90f5dca7f 100644 --- a/nostr-java-event/src/main/java/nostr/event/tag/IdentifierTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/IdentifierTag.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -11,16 +12,18 @@ import nostr.base.annotation.Key; import nostr.base.annotation.Tag; import nostr.event.BaseTag; +import nostr.event.json.serializer.IdentifierTagSerializer; /** * @author eric */ @Builder @Data -@EqualsAndHashCode(callSuper = true) +@EqualsAndHashCode(callSuper = false) @Tag(code = "d", nip = 33) @NoArgsConstructor @AllArgsConstructor +@JsonSerialize(using = IdentifierTagSerializer.class) public class IdentifierTag extends BaseTag { @Key From 5e8e70477dac40422f66f7667b6badb4dcf40819 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Mon, 14 Apr 2025 19:44:01 -0700 Subject: [PATCH 29/37] additional addressTag Encode/Decode tests --- .../nostr/event/filter/AddressTagFilter.java | 77 +++++++++++-------- .../nostr/event/unit/FiltersDecoderTest.java | 33 +++++++- .../nostr/event/unit/FiltersEncoderTest.java | 29 ++++++- 3 files changed, 100 insertions(+), 39 deletions(-) diff --git a/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java b/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java index 65edf2819..653bf1743 100644 --- a/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java +++ b/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java @@ -1,8 +1,10 @@ package nostr.event.filter; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.base.CharMatcher; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; @@ -16,49 +18,58 @@ import nostr.event.impl.GenericEvent; import nostr.event.tag.AddressTag; import nostr.event.tag.IdentifierTag; +import org.apache.commons.lang3.stream.Streams; @EqualsAndHashCode(callSuper = true) public class AddressTagFilter extends AbstractFilterable { - public final static String FILTER_KEY = "#a"; + public final static String FILTER_KEY = "#a"; - public AddressTagFilter(T addressableTag) { - super(addressableTag, FILTER_KEY); - } - - @Override - public Predicate getPredicate() { - return (genericEvent) -> - Filterable.getTypeSpecificTags(AddressTag.class, genericEvent).stream() - .anyMatch(addressTag -> - addressTag.equals(getAddressableTag())); - } + public AddressTagFilter(T addressableTag) { + super(addressableTag, FILTER_KEY); + } - @Override - public String getFilterableValue() { - return Stream.of( - getAddressableTag().getKind(), - getAddressableTag().getPublicKey().toHexString(), - getAddressableTag().getIdentifierTag().getUuid()) - .map(Object::toString).collect(Collectors.joining(":")); - } + @Override + public Predicate getPredicate() { + return (genericEvent) -> + Filterable.getTypeSpecificTags(AddressTag.class, genericEvent).stream() + .anyMatch(addressTag -> + addressTag.equals(getAddressableTag())); + } - private T getAddressableTag() { - return super.getFilterable(); - } + @Override + public Object getFilterableValue() { + String collect = Stream.of( + getAddressableTag().getKind(), + getAddressableTag().getPublicKey().toHexString(), + getAddressableTag().getIdentifierTag().getUuid()) + .map(Object::toString).collect(Collectors.joining(":")); + return Optional.ofNullable(getAddressableTag().getRelay()).map(relay -> + String.join("\",\"", collect, relay.getUri())).orElse(collect); + } - public static Function fxn = node -> - new AddressTagFilter<>(createAddressTag(node)); + private T getAddressableTag() { + return super.getFilterable(); + } - protected static T createAddressTag(@NonNull JsonNode node) { - List list = Arrays.stream(node.asText().split(":")).toList(); + public static Function fxn = node -> + new AddressTagFilter<>(createAddressTag(node)); - final AddressTag addressTag = new AddressTag(); - addressTag.setKind(Integer.valueOf(list.get(0))); - addressTag.setPublicKey(new PublicKey(list.get(1))); - addressTag.setIdentifierTag(new IdentifierTag(list.get(2))); + protected static T createAddressTag(@NonNull JsonNode node) { + String[] nodes = node.asText().split(","); + List list = Arrays.stream(nodes[0].split(":")).toList(); - Optional.ofNullable(node.get(2)).ifPresent(relay -> addressTag.setRelay(new Relay(relay.asText()))); + final AddressTag addressTag = new AddressTag(); + addressTag.setKind(Integer.valueOf(list.get(0))); + addressTag.setPublicKey(new PublicKey(list.get(1))); + addressTag.setIdentifierTag(new IdentifierTag(list.get(2))); - return (T) addressTag; + if (Objects.equals(2, nodes.length)) { + String identifierString = CharMatcher.is('"').trimTrailingFrom(list.get(2)); + addressTag.setIdentifierTag(new IdentifierTag(identifierString)); + String relayString = CharMatcher.is('"').trimLeadingFrom(nodes[1]); + addressTag.setRelay(new Relay(relayString)); } + + return (T) addressTag; + } } diff --git a/nostr-java-event/src/test/java/nostr/event/unit/FiltersDecoderTest.java b/nostr-java-event/src/test/java/nostr/event/unit/FiltersDecoderTest.java index 56bef8bae..4d3a7961b 100644 --- a/nostr-java-event/src/test/java/nostr/event/unit/FiltersDecoderTest.java +++ b/nostr-java-event/src/test/java/nostr/event/unit/FiltersDecoderTest.java @@ -3,6 +3,7 @@ import lombok.extern.java.Log; import nostr.base.GenericTagQuery; import nostr.base.PublicKey; +import nostr.base.Relay; import nostr.event.Kind; import nostr.event.filter.AddressTagFilter; import nostr.event.filter.EventFilter; @@ -71,10 +72,9 @@ public void testMultipleEventFiltersDecoder() { decodedFilters); } - @Test - public void testAddressableTagFiltersDecoder() { - log.info("testAddressableTagFiltersDecoder"); + public void testAddressableTagFiltersWithoutRelayDecoder() { + log.info("testAddressableTagFiltersWithoutRelayDecoder"); Integer kind = 1; String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; @@ -96,6 +96,31 @@ public void testAddressableTagFiltersDecoder() { decodedFilters); } + @Test + public void testAddressableTagFiltersWithRelayDecoder() { + log.info("testAddressableTagFiltersWithRelayDecoder"); + + Integer kind = 1; + String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String uuidValue1 = "UUID-1"; + Relay relay = new Relay("ws://localhost:5555"); + + String joined = String.join(":", String.valueOf(kind), author, uuidValue1); + + AddressTag addressTag = new AddressTag(); + addressTag.setKind(kind); + addressTag.setPublicKey(new PublicKey(author)); + addressTag.setIdentifierTag(new IdentifierTag(uuidValue1)); + addressTag.setRelay(relay); + + String expected = String.join("\\\",\\\"", joined, relay.getUri()); + String addressableTag = "{\"#a\":[\"" + expected + "\"]}"; + Filters decodedFilters = new FiltersDecoder().decode(addressableTag); + + Filters expected1 = new Filters(new AddressTagFilter<>(addressTag)); + assertEquals(expected1, decodedFilters); + } + @Test public void testMultipleAddressableTagFiltersDecoder() { log.info("testMultipleAddressableTagFiltersDecoder"); @@ -386,6 +411,6 @@ public void testFailedAddressableTagMalformedSeparator() { String malformedJoin = String.join(",", String.valueOf(kind), author, uuidValue1); String expected = "{\"#a\":[\"" + malformedJoin + "\"]}"; - assertThrows(IllegalArgumentException.class, () -> new FiltersDecoder().decode(expected)); + assertThrows(ArrayIndexOutOfBoundsException.class, () -> new FiltersDecoder().decode(expected)); } } diff --git a/nostr-java-event/src/test/java/nostr/event/unit/FiltersEncoderTest.java b/nostr-java-event/src/test/java/nostr/event/unit/FiltersEncoderTest.java index 4097032f4..855ac1620 100644 --- a/nostr-java-event/src/test/java/nostr/event/unit/FiltersEncoderTest.java +++ b/nostr-java-event/src/test/java/nostr/event/unit/FiltersEncoderTest.java @@ -3,6 +3,7 @@ import lombok.extern.java.Log; import nostr.base.GenericTagQuery; import nostr.base.PublicKey; +import nostr.base.Relay; import nostr.event.Kind; import nostr.event.filter.AddressTagFilter; import nostr.event.filter.AuthorFilter; @@ -125,8 +126,8 @@ public void testMultipleKindFiltersEncoder() { } @Test - public void testAddressableTagFilterEncoder() { - log.info("testAddressableTagFilterEncoder"); + public void testAddressableTagFilterWithoutRelayEncoder() { + log.info("testAddressableTagFilterWithoutRelayEncoder"); Integer kind = 1; String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; @@ -144,6 +145,30 @@ public void testAddressableTagFilterEncoder() { assertEquals("{\"#a\":[\"" + addressableTag + "\"]}", encodedFilters); } + @Test + public void testAddressableTagWithRelayFilterEncoder() { + log.info("testAddressableTagWithRelayFilterEncoder"); + + Integer kind = 1; + String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String uuidValue1 = "UUID-1"; + Relay relay = new Relay("ws://localhost:5555"); + + AddressTag addressTag = new AddressTag(); + addressTag.setKind(kind); + addressTag.setPublicKey(new PublicKey(author)); + addressTag.setIdentifierTag(new IdentifierTag(uuidValue1)); + addressTag.setRelay(relay); + + FiltersEncoder encoder = new FiltersEncoder(new Filters(new AddressTagFilter<>(addressTag))); + String encodedFilters = encoder.encode(); + String addressableTag = String.join(":", String.valueOf(kind), author, uuidValue1); + String joined = String.join("\\\",\\\"", addressableTag, relay.getUri()); + + String expected = "{\"#a\":[\"" + joined + "\"]}"; + assertEquals(expected, encodedFilters); + } + @Test public void testIdentifierTagFilterEncoder() { log.info("testIdentifierTagFilterEncoder"); From 2bf8c626f581864ffa332cf8d07cdcdb9a951daa Mon Sep 17 00:00:00 2001 From: nick avlo Date: Tue, 15 Apr 2025 00:21:04 -0700 Subject: [PATCH 30/37] cleanup --- .../nostr/event/filter/AddressTagFilter.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java b/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java index 653bf1743..f6fcf9ea9 100644 --- a/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java +++ b/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java @@ -18,7 +18,6 @@ import nostr.event.impl.GenericEvent; import nostr.event.tag.AddressTag; import nostr.event.tag.IdentifierTag; -import org.apache.commons.lang3.stream.Streams; @EqualsAndHashCode(callSuper = true) public class AddressTagFilter extends AbstractFilterable { @@ -38,13 +37,13 @@ public Predicate getPredicate() { @Override public Object getFilterableValue() { - String collect = Stream.of( + String requiredAttributes = Stream.of( getAddressableTag().getKind(), getAddressableTag().getPublicKey().toHexString(), getAddressableTag().getIdentifierTag().getUuid()) .map(Object::toString).collect(Collectors.joining(":")); return Optional.ofNullable(getAddressableTag().getRelay()).map(relay -> - String.join("\",\"", collect, relay.getUri())).orElse(collect); + String.join("\",\"", requiredAttributes, relay.getUri())).orElse(requiredAttributes); } private T getAddressableTag() { @@ -63,12 +62,13 @@ protected static T createAddressTag(@NonNull JsonNode node) addressTag.setPublicKey(new PublicKey(list.get(1))); addressTag.setIdentifierTag(new IdentifierTag(list.get(2))); - if (Objects.equals(2, nodes.length)) { - String identifierString = CharMatcher.is('"').trimTrailingFrom(list.get(2)); - addressTag.setIdentifierTag(new IdentifierTag(identifierString)); - String relayString = CharMatcher.is('"').trimLeadingFrom(nodes[1]); - addressTag.setRelay(new Relay(relayString)); - } + if (!Objects.equals(2, nodes.length)) + return (T) addressTag; + + String identifierString = CharMatcher.is('"').trimTrailingFrom(list.get(2)); + addressTag.setIdentifierTag(new IdentifierTag(identifierString)); + String relayString = CharMatcher.is('"').trimLeadingFrom(nodes[1]); + addressTag.setRelay(new Relay(relayString)); return (T) addressTag; } From 1578fe9f74dddb3cd62e7667f58c90859ec454e4 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Mon, 21 Apr 2025 16:58:13 -0700 Subject: [PATCH 31/37] guava replaced with regex --- .../src/main/groovy/nostr-java.conventions.gradle | 2 +- .../java/nostr/event/filter/AddressTagFilter.java | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/buildSrc/src/main/groovy/nostr-java.conventions.gradle b/buildSrc/src/main/groovy/nostr-java.conventions.gradle index 526ea811d..eb4e2f3bd 100644 --- a/buildSrc/src/main/groovy/nostr-java.conventions.gradle +++ b/buildSrc/src/main/groovy/nostr-java.conventions.gradle @@ -60,7 +60,6 @@ dependencies { implementation 'org.bouncycastle:bcprov-jdk18on:' + bouncyCastle implementation 'org.apache.commons:commons-lang3:' + apacheCommonsLang3 - implementation 'com.google.guava:guava:' + googleGuava implementation 'org.awaitility:awaitility:' + awaitility implementation 'org.projectlombok:lombok:' + lombok annotationProcessor 'org.awaitility:awaitility:' + awaitility @@ -70,6 +69,7 @@ dependencies { testImplementation 'org.projectlombok:lombok:' + lombok testImplementation 'org.awaitility:awaitility:' + awaitility testImplementation 'com.adarshr:gradle-test-logger-plugin:' + logger + testImplementation 'com.google.guava:guava:' + googleGuava testAnnotationProcessor 'org.projectlombok:lombok:' + lombok } diff --git a/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java b/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java index f6fcf9ea9..bfbe969ba 100644 --- a/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java +++ b/nostr-java-event/src/main/java/nostr/event/filter/AddressTagFilter.java @@ -1,7 +1,6 @@ package nostr.event.filter; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.base.CharMatcher; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -64,11 +63,13 @@ protected static T createAddressTag(@NonNull JsonNode node) if (!Objects.equals(2, nodes.length)) return (T) addressTag; - - String identifierString = CharMatcher.is('"').trimTrailingFrom(list.get(2)); - addressTag.setIdentifierTag(new IdentifierTag(identifierString)); - String relayString = CharMatcher.is('"').trimLeadingFrom(nodes[1]); - addressTag.setRelay(new Relay(relayString)); + + addressTag.setIdentifierTag( + new IdentifierTag( + list.get(2).replaceAll("\"$", ""))); + addressTag.setRelay( + new Relay( + nodes[1].replaceAll("^\"", ""))); return (T) addressTag; } From ac645b9a0d341b97aa246315e87be18e8a09825b Mon Sep 17 00:00:00 2001 From: nick avlo Date: Wed, 23 Apr 2025 20:43:20 -0700 Subject: [PATCH 32/37] default json vote property --- nostr-java-event/src/main/java/nostr/event/tag/VoteTag.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nostr-java-event/src/main/java/nostr/event/tag/VoteTag.java b/nostr-java-event/src/main/java/nostr/event/tag/VoteTag.java index 4943a30c5..fd1124d07 100644 --- a/nostr-java-event/src/main/java/nostr/event/tag/VoteTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/VoteTag.java @@ -21,7 +21,7 @@ public class VoteTag extends BaseTag { @Key - @JsonProperty("v") + @JsonProperty private Integer vote; public static T deserialize(@NonNull JsonNode node) { From 8c16496b6c1edc9773d1c209e320bc9b1d0263a4 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Fri, 25 Apr 2025 16:50:08 -0700 Subject: [PATCH 33/37] add VoteTag tests --- .../nostr/api/integration/ApiEventIT.java | 24 + .../java/nostr/api/unit/JsonParseTest.java | 23 + .../src/main/java/nostr/event/Kind.java | 1 + .../main/java/nostr/event/tag/NonceTag.java | 4 +- .../java/nostr/event/tag/ReferenceTag.java | 2 +- .../main/java/nostr/event/tag/VoteTag.java | 2 +- .../nostr/event/unit/FiltersDecoderTest.java | 571 +++++++++--------- .../nostr/event/unit/FiltersEncoderTest.java | 15 + 8 files changed, 359 insertions(+), 283 deletions(-) diff --git a/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java b/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java index 03af0b732..867ddd30f 100644 --- a/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java +++ b/nostr-java-api/src/test/java/nostr/api/integration/ApiEventIT.java @@ -21,6 +21,7 @@ import nostr.event.filter.GenericTagQueryFilter; import nostr.event.filter.GeohashTagFilter; import nostr.event.filter.HashtagTagFilter; +import nostr.event.filter.VoteTagFilter; import nostr.event.impl.CalendarContent; import nostr.event.impl.CreateOrUpdateStallEvent; import nostr.event.impl.CreateOrUpdateStallEvent.Stall; @@ -37,6 +38,7 @@ import nostr.event.tag.HashtagTag; import nostr.event.tag.IdentifierTag; import nostr.event.tag.PubKeyTag; +import nostr.event.tag.VoteTag; import nostr.id.Identity; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -643,4 +645,26 @@ public static NostrMarketplaceEvent.Product createProduct(Stall stall) { return product; } + + @Test + public void testNIP01SendTextNoteEventVoteTag() { + System.out.println("testNIP01SendTextNoteEventVoteTag"); + + Integer targetVote = 1; + VoteTag voteTag = new VoteTag(targetVote); + + NIP01 nip01 = new NIP01<>(Identity.generateRandomIdentity()); + nip01.createTextNoteEvent(List.of(voteTag), "Vote Tag Test value testNIP01SendTextNoteEventVoteTag").signAndSend(relays); + + Filters filters = new Filters( + new VoteTagFilter<>(new VoteTag(targetVote))); + + List result = nip01.sendRequest(filters, UUID.randomUUID().toString()); + + assertFalse(result.isEmpty()); + assertEquals(2, result.size()); + assertTrue(result.stream().anyMatch(s -> s.contains(targetVote.toString()))); + +// nip01.close(); + } } diff --git a/nostr-java-api/src/test/java/nostr/api/unit/JsonParseTest.java b/nostr-java-api/src/test/java/nostr/api/unit/JsonParseTest.java index 95d0ec323..7257e4f45 100644 --- a/nostr-java-api/src/test/java/nostr/api/unit/JsonParseTest.java +++ b/nostr-java-api/src/test/java/nostr/api/unit/JsonParseTest.java @@ -26,6 +26,7 @@ import nostr.event.filter.KindFilter; import nostr.event.filter.ReferencedEventFilter; import nostr.event.filter.ReferencedPublicKeyFilter; +import nostr.event.filter.VoteTagFilter; import nostr.event.impl.GenericEvent; import nostr.event.tag.GenericTag; import nostr.event.json.codec.BaseEventEncoder; @@ -42,6 +43,7 @@ import nostr.event.tag.IdentifierTag; import nostr.event.tag.PriceTag; import nostr.event.tag.PubKeyTag; +import nostr.event.tag.VoteTag; import nostr.id.Identity; import org.junit.jupiter.api.Test; @@ -749,4 +751,25 @@ public void testBaseEventMessageDecoderMultipleFiltersJson() throws JsonProcessi assertEquals(subscriptionId, ((ReqMessage) message).getSubscriptionId()); assertEquals(2, ((ReqMessage) message).getFiltersList().size()); } + + @Test + public void testReqMessageVoteTagFilterDecoder() { + log.info("testReqMessageVoteTagFilterDecoder"); + + String subscriptionId = "npub333k6vc9xhjp8q5cws262wuf2eh4zuvwupft03hy4ttqqnm7e0jrq3upup9"; + String voteTagKey = "#v"; + Integer voteTagValue = 1; + String reqJsonWithVoteTagFilterToDecode = "[\"REQ\",\"" + subscriptionId + "\",{\"" + voteTagKey + "\":[\"" + voteTagValue + "\"]}]"; + + assertDoesNotThrow(() -> { + ReqMessage decodedReqMessage = new BaseMessageDecoder().decode(reqJsonWithVoteTagFilterToDecode); + + ReqMessage expectedReqMessage = new ReqMessage(subscriptionId, + new Filters( + new VoteTagFilter<>(new VoteTag(voteTagValue)))); + + assertEquals(reqJsonWithVoteTagFilterToDecode, decodedReqMessage.encode()); + assertEquals(expectedReqMessage, decodedReqMessage); + }); + } } diff --git a/nostr-java-event/src/main/java/nostr/event/Kind.java b/nostr-java-event/src/main/java/nostr/event/Kind.java index 0f34b990b..686f87bd6 100644 --- a/nostr-java-event/src/main/java/nostr/event/Kind.java +++ b/nostr-java-event/src/main/java/nostr/event/Kind.java @@ -29,6 +29,7 @@ public enum Kind { MUTE_USER(44, "mute_user"), ENCRYPTED_PAYLOADS(44, "encrypted_payloads"), OTS_EVENT(1040, "ots_event"), + VOTE(2112, "vote"), RESERVED_CASHU_WALLET_TOKENS(7_374, "reserved_cashu_wallet_tokens"), WALLET_UNSPENT_PROOF(7_375, "wallet_unspent_proof"), WALLET_TX_HISTORY(7_376, "wallet_tx_history"), diff --git a/nostr-java-event/src/main/java/nostr/event/tag/NonceTag.java b/nostr-java-event/src/main/java/nostr/event/tag/NonceTag.java index 86f91e0de..413ae7c33 100644 --- a/nostr-java-event/src/main/java/nostr/event/tag/NonceTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/NonceTag.java @@ -38,8 +38,8 @@ public NonceTag(@NonNull Integer nonce, @NonNull Integer difficulty) { public static T deserialize(@NonNull JsonNode node) { NonceTag tag = new NonceTag(); - setRequiredField(node.get(1), (n, t) -> tag.setNonce(Integer.valueOf(n.asText())), tag); - setRequiredField(node.get(2), (n, t) -> tag.setDifficulty(Integer.valueOf(n.asText())), tag); + setRequiredField(node.get(1), (n, t) -> tag.setNonce(n.asInt()), tag); + setRequiredField(node.get(2), (n, t) -> tag.setDifficulty(n.asInt()), tag); return (T) tag; } } diff --git a/nostr-java-event/src/main/java/nostr/event/tag/ReferenceTag.java b/nostr-java-event/src/main/java/nostr/event/tag/ReferenceTag.java index 098e73076..e9516f354 100644 --- a/nostr-java-event/src/main/java/nostr/event/tag/ReferenceTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/ReferenceTag.java @@ -28,7 +28,7 @@ public class ReferenceTag extends BaseTag { @Key - @JsonProperty("r") + @JsonProperty("uri") private URI uri; } diff --git a/nostr-java-event/src/main/java/nostr/event/tag/VoteTag.java b/nostr-java-event/src/main/java/nostr/event/tag/VoteTag.java index fd1124d07..6616b471e 100644 --- a/nostr-java-event/src/main/java/nostr/event/tag/VoteTag.java +++ b/nostr-java-event/src/main/java/nostr/event/tag/VoteTag.java @@ -14,7 +14,7 @@ @Builder @Data -@EqualsAndHashCode(callSuper = true) +@EqualsAndHashCode(callSuper = false) @Tag(code = "v", nip = 2112) @NoArgsConstructor @AllArgsConstructor diff --git a/nostr-java-event/src/test/java/nostr/event/unit/FiltersDecoderTest.java b/nostr-java-event/src/test/java/nostr/event/unit/FiltersDecoderTest.java index 4d3a7961b..74cac3545 100644 --- a/nostr-java-event/src/test/java/nostr/event/unit/FiltersDecoderTest.java +++ b/nostr-java-event/src/test/java/nostr/event/unit/FiltersDecoderTest.java @@ -1,5 +1,7 @@ package nostr.event.unit; +import java.time.Instant; +import java.util.Date; import lombok.extern.java.Log; import nostr.base.GenericTagQuery; import nostr.base.PublicKey; @@ -17,6 +19,7 @@ import nostr.event.filter.ReferencedPublicKeyFilter; import nostr.event.filter.SinceFilter; import nostr.event.filter.UntilFilter; +import nostr.event.filter.VoteTagFilter; import nostr.event.impl.GenericEvent; import nostr.event.json.codec.FiltersDecoder; import nostr.event.tag.AddressTag; @@ -25,392 +28,402 @@ import nostr.event.tag.HashtagTag; import nostr.event.tag.IdentifierTag; import nostr.event.tag.PubKeyTag; +import nostr.event.tag.VoteTag; import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.Date; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @Log public class FiltersDecoderTest { - @Test - public void testEventFiltersDecoder() { - log.info("testEventFiltersDecoder"); + @Test + public void testEventFiltersDecoder() { + log.info("testEventFiltersDecoder"); + + String filterKey = "ids"; + String eventId = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + + String expected = "{\"" + filterKey + "\":[\"" + eventId + "\"]}"; + Filters decodedFilters = new FiltersDecoder().decode(expected); + + assertEquals( + new Filters( + new EventFilter<>(new GenericEvent(eventId))), + decodedFilters); + } + + @Test + public void testMultipleEventFiltersDecoder() { + log.info("testMultipleEventFiltersDecoder"); - String filterKey = "ids"; - String eventId = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String filterKey = "ids"; + String eventId1 = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String eventId2 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - String expected = "{\"" + filterKey + "\":[\"" + eventId + "\"]}"; - Filters decodedFilters = new FiltersDecoder().decode(expected); + String joined = String.join("\",\"", eventId1, eventId2); - assertEquals( - new Filters( - new EventFilter<>(new GenericEvent(eventId))), - decodedFilters); - } + String expected = "{\"" + filterKey + "\":[\"" + joined + "\"]}"; + Filters decodedFilters = new FiltersDecoder().decode(expected); - @Test - public void testMultipleEventFiltersDecoder() { - log.info("testMultipleEventFiltersDecoder"); + assertEquals( + new Filters( + new EventFilter<>(new GenericEvent(eventId1)), + new EventFilter<>(new GenericEvent(eventId2))), + decodedFilters); + } - String filterKey = "ids"; - String eventId1 = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - String eventId2 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + @Test + public void testAddressableTagFiltersWithoutRelayDecoder() { + log.info("testAddressableTagFiltersWithoutRelayDecoder"); - String joined = String.join("\",\"", eventId1, eventId2); + Integer kind = 1; + String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String uuidValue1 = "UUID-1"; - String expected = "{\"" + filterKey + "\":[\"" + joined + "\"]}"; - Filters decodedFilters = new FiltersDecoder().decode(expected); + String joined = String.join(":", String.valueOf(kind), author, uuidValue1); - assertEquals( - new Filters( - new EventFilter<>(new GenericEvent(eventId1)), - new EventFilter<>(new GenericEvent(eventId2))), - decodedFilters); - } + AddressTag addressTag = new AddressTag(); + addressTag.setKind(kind); + addressTag.setPublicKey(new PublicKey(author)); + addressTag.setIdentifierTag(new IdentifierTag(uuidValue1)); - @Test - public void testAddressableTagFiltersWithoutRelayDecoder() { - log.info("testAddressableTagFiltersWithoutRelayDecoder"); + String expected = "{\"#a\":[\"" + joined + "\"]}"; + Filters decodedFilters = new FiltersDecoder().decode(expected); - Integer kind = 1; - String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - String uuidValue1 = "UUID-1"; + assertEquals( + new Filters( + new AddressTagFilter<>(addressTag)), + decodedFilters); + } - String joined = String.join(":", String.valueOf(kind), author, uuidValue1); + @Test + public void testAddressableTagFiltersWithRelayDecoder() { + log.info("testAddressableTagFiltersWithRelayDecoder"); - AddressTag addressTag = new AddressTag(); - addressTag.setKind(kind); - addressTag.setPublicKey(new PublicKey(author)); - addressTag.setIdentifierTag(new IdentifierTag(uuidValue1)); + Integer kind = 1; + String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String uuidValue1 = "UUID-1"; + Relay relay = new Relay("ws://localhost:5555"); - String expected = "{\"#a\":[\"" + joined + "\"]}"; - Filters decodedFilters = new FiltersDecoder().decode(expected); + String joined = String.join(":", String.valueOf(kind), author, uuidValue1); - assertEquals( - new Filters( - new AddressTagFilter<>(addressTag)), - decodedFilters); - } + AddressTag addressTag = new AddressTag(); + addressTag.setKind(kind); + addressTag.setPublicKey(new PublicKey(author)); + addressTag.setIdentifierTag(new IdentifierTag(uuidValue1)); + addressTag.setRelay(relay); - @Test - public void testAddressableTagFiltersWithRelayDecoder() { - log.info("testAddressableTagFiltersWithRelayDecoder"); + String expected = String.join("\\\",\\\"", joined, relay.getUri()); + String addressableTag = "{\"#a\":[\"" + expected + "\"]}"; + Filters decodedFilters = new FiltersDecoder().decode(addressableTag); - Integer kind = 1; - String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - String uuidValue1 = "UUID-1"; - Relay relay = new Relay("ws://localhost:5555"); + Filters expected1 = new Filters(new AddressTagFilter<>(addressTag)); + assertEquals(expected1, decodedFilters); + } - String joined = String.join(":", String.valueOf(kind), author, uuidValue1); + @Test + public void testMultipleAddressableTagFiltersDecoder() { + log.info("testMultipleAddressableTagFiltersDecoder"); - AddressTag addressTag = new AddressTag(); - addressTag.setKind(kind); - addressTag.setPublicKey(new PublicKey(author)); - addressTag.setIdentifierTag(new IdentifierTag(uuidValue1)); - addressTag.setRelay(relay); + Integer kind1 = 1; + String author1 = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String uuidValue1 = "UUID-1"; - String expected = String.join("\\\",\\\"", joined, relay.getUri()); - String addressableTag = "{\"#a\":[\"" + expected + "\"]}"; - Filters decodedFilters = new FiltersDecoder().decode(addressableTag); + Integer kind2 = 1; + String author2 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + String uuidValue2 = "UUID-2"; - Filters expected1 = new Filters(new AddressTagFilter<>(addressTag)); - assertEquals(expected1, decodedFilters); - } + AddressTag addressTag1 = new AddressTag(); + addressTag1.setKind(kind1); + addressTag1.setPublicKey(new PublicKey(author1)); + addressTag1.setIdentifierTag(new IdentifierTag(uuidValue1)); - @Test - public void testMultipleAddressableTagFiltersDecoder() { - log.info("testMultipleAddressableTagFiltersDecoder"); + AddressTag addressTag2 = new AddressTag(); + addressTag2.setKind(kind2); + addressTag2.setPublicKey(new PublicKey(author2)); + addressTag2.setIdentifierTag(new IdentifierTag(uuidValue2)); - Integer kind1 = 1; - String author1 = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - String uuidValue1 = "UUID-1"; + String joined1 = String.join(":", String.valueOf(kind1), author1, uuidValue1); + String joined2 = String.join(":", String.valueOf(kind2), author2, uuidValue2); - Integer kind2 = 1; - String author2 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - String uuidValue2 = "UUID-2"; + String joined3 = String.join("\",\"", joined1, joined2); - AddressTag addressTag1 = new AddressTag(); - addressTag1.setKind(kind1); - addressTag1.setPublicKey(new PublicKey(author1)); - addressTag1.setIdentifierTag(new IdentifierTag(uuidValue1)); + String expected = "{\"#a\":[\"" + joined3 + "\"]}"; + Filters decodedFilters = new FiltersDecoder().decode(expected); - AddressTag addressTag2 = new AddressTag(); - addressTag2.setKind(kind2); - addressTag2.setPublicKey(new PublicKey(author2)); - addressTag2.setIdentifierTag(new IdentifierTag(uuidValue2)); + assertEquals( + new Filters( + new AddressTagFilter<>(addressTag1), + new AddressTagFilter<>(addressTag2)), + decodedFilters); + } - String joined1 = String.join(":", String.valueOf(kind1), author1, uuidValue1); - String joined2 = String.join(":", String.valueOf(kind2), author2, uuidValue2); + @Test + public void testKindFiltersDecoder() { + log.info("testKindFiltersDecoder"); - String joined3 = String.join("\",\"", joined1, joined2); + String filterKey = KindFilter.FILTER_KEY; + Kind kind = Kind.valueOf(1); - String expected = "{\"#a\":[\"" + joined3 + "\"]}"; - Filters decodedFilters = new FiltersDecoder().decode(expected); + String expected = "{\"" + filterKey + "\":[" + kind.toString() + "]}"; + Filters decodedFilters = new FiltersDecoder().decode(expected); - assertEquals( - new Filters( - new AddressTagFilter<>(addressTag1), - new AddressTagFilter<>(addressTag2)), - decodedFilters); - } + assertEquals(new Filters(new KindFilter<>(kind)), decodedFilters); + } - @Test - public void testKindFiltersDecoder() { - log.info("testKindFiltersDecoder"); + @Test + public void testMultipleKindFiltersDecoder() { + log.info("testMultipleKindFiltersDecoder"); - String filterKey = KindFilter.FILTER_KEY; - Kind kind = Kind.valueOf(1); + String filterKey = KindFilter.FILTER_KEY; + Kind kind1 = Kind.valueOf(1); + Kind kind2 = Kind.valueOf(2); - String expected = "{\"" + filterKey + "\":[" + kind.toString() + "]}"; - Filters decodedFilters = new FiltersDecoder().decode(expected); + String join = String.join(",", kind1.toString(), kind2.toString()); - assertEquals(new Filters(new KindFilter<>(kind)), decodedFilters); - } + String expected = "{\"" + filterKey + "\":[" + join + "]}"; + Filters decodedFilters = new FiltersDecoder().decode(expected); - @Test - public void testMultipleKindFiltersDecoder() { - log.info("testMultipleKindFiltersDecoder"); + assertEquals( + new Filters( + new KindFilter<>(kind1), + new KindFilter<>(kind2)), + decodedFilters); + } - String filterKey = KindFilter.FILTER_KEY; - Kind kind1 = Kind.valueOf(1); - Kind kind2 = Kind.valueOf(2); + @Test + public void testIdentifierTagFilterDecoder() { + log.info("testIdentifierTagFilterDecoder"); - String join = String.join(",", kind1.toString(), kind2.toString()); + String uuidValue1 = "UUID-1"; - String expected = "{\"" + filterKey + "\":[" + join + "]}"; - Filters decodedFilters = new FiltersDecoder().decode(expected); + String expected = "{\"#d\":[\"" + uuidValue1 + "\"]}"; + Filters decodedFilters = new FiltersDecoder().decode(expected); - assertEquals( - new Filters( - new KindFilter<>(kind1), - new KindFilter<>(kind2)), - decodedFilters); - } - @Test - public void testIdentifierTagFilterDecoder() { - log.info("testIdentifierTagFilterDecoder"); + assertEquals(new Filters(new IdentifierTagFilter<>(new IdentifierTag(uuidValue1))), decodedFilters); + } - String uuidValue1 = "UUID-1"; + @Test + public void testMultipleIdentifierTagFilterDecoder() { + log.info("testMultipleIdentifierTagFilterDecoder"); - String expected = "{\"#d\":[\"" + uuidValue1 + "\"]}"; - Filters decodedFilters = new FiltersDecoder().decode(expected); + String uuidValue1 = "UUID-1"; + String uuidValue2 = "UUID-2"; + String joined = String.join("\",\"", uuidValue1, uuidValue2); + String expected = "{\"#d\":[\"" + joined + "\"]}"; - assertEquals(new Filters(new IdentifierTagFilter<>(new IdentifierTag(uuidValue1))), decodedFilters); - } + Filters decodedFilters = new FiltersDecoder().decode(expected); - @Test - public void testMultipleIdentifierTagFilterDecoder() { - log.info("testMultipleIdentifierTagFilterDecoder"); + assertEquals( + new Filters( + new IdentifierTagFilter<>(new IdentifierTag(uuidValue1)), + new IdentifierTagFilter<>(new IdentifierTag(uuidValue2))), + decodedFilters); + } - String uuidValue1 = "UUID-1"; - String uuidValue2 = "UUID-2"; + @Test + public void testReferencedEventFilterDecoder() { + log.info("testReferencedEventFilterDecoder"); - String joined = String.join("\",\"", uuidValue1, uuidValue2); - String expected = "{\"#d\":[\"" + joined + "\"]}"; + String eventId = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - Filters decodedFilters = new FiltersDecoder().decode(expected); + String expected = "{\"#e\":[\"" + eventId + "\"]}"; + Filters decodedFilters = new FiltersDecoder().decode(expected); - assertEquals( - new Filters( - new IdentifierTagFilter<>(new IdentifierTag(uuidValue1)), - new IdentifierTagFilter<>(new IdentifierTag(uuidValue2))), - decodedFilters); - } + assertEquals(new Filters(new ReferencedEventFilter<>(new EventTag(eventId))), decodedFilters); + } - @Test - public void testReferencedEventFilterDecoder() { - log.info("testReferencedEventFilterDecoder"); + @Test + public void testMultipleReferencedEventFilterDecoder() { + log.info("testMultipleReferencedEventFilterDecoder"); - String eventId = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String eventId1 = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String eventId2 = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - String expected = "{\"#e\":[\"" + eventId + "\"]}"; - Filters decodedFilters = new FiltersDecoder().decode(expected); + String joined = String.join("\",\"", eventId1, eventId2); + String expected = "{\"#e\":[\"" + joined + "\"]}"; + Filters decodedFilters = new FiltersDecoder().decode(expected); - assertEquals(new Filters(new ReferencedEventFilter<>(new EventTag(eventId))), decodedFilters); - } + assertEquals( + new Filters( + new ReferencedEventFilter<>(new EventTag(eventId1)), + new ReferencedEventFilter<>(new EventTag(eventId2))), + decodedFilters); + } - @Test - public void testMultipleReferencedEventFilterDecoder() { - log.info("testMultipleReferencedEventFilterDecoder"); + @Test + public void testReferencedPublicKeyFilterDecofder() { + log.info("testReferencedPublicKeyFilterDecoder"); - String eventId1 = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - String eventId2 = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String pubkeyString = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - String joined = String.join("\",\"", eventId1, eventId2); - String expected = "{\"#e\":[\"" + joined + "\"]}"; - Filters decodedFilters = new FiltersDecoder().decode(expected); + String expected = "{\"#p\":[\"" + pubkeyString + "\"]}"; + Filters decodedFilters = new FiltersDecoder().decode(expected); - assertEquals( - new Filters( - new ReferencedEventFilter<>(new EventTag(eventId1)), - new ReferencedEventFilter<>(new EventTag(eventId2))), - decodedFilters); - } + assertEquals(new Filters(new ReferencedPublicKeyFilter<>(new PubKeyTag(new PublicKey(pubkeyString)))), decodedFilters); + } - @Test - public void testReferencedPublicKeyFilterDecofder() { - log.info("testReferencedPublicKeyFilterDecoder"); + @Test + public void testMultipleReferencedPublicKeyFilterDecoder() { + log.info("testMultipleReferencedPublicKeyFilterDecoder"); - String pubkeyString = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String pubkeyString1 = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String pubkeyString2 = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - String expected = "{\"#p\":[\"" + pubkeyString + "\"]}"; - Filters decodedFilters = new FiltersDecoder().decode(expected); + String joined = String.join("\",\"", pubkeyString1, pubkeyString2); + String expected = "{\"#p\":[\"" + joined + "\"]}"; - assertEquals(new Filters(new ReferencedPublicKeyFilter<>(new PubKeyTag(new PublicKey(pubkeyString)))), decodedFilters); - } + Filters decodedFilters = new FiltersDecoder().decode(expected); - @Test - public void testMultipleReferencedPublicKeyFilterDecoder() { - log.info("testMultipleReferencedPublicKeyFilterDecoder"); + assertEquals( + new Filters( + new ReferencedPublicKeyFilter<>(new PubKeyTag(new PublicKey(pubkeyString1))), + new ReferencedPublicKeyFilter<>(new PubKeyTag(new PublicKey(pubkeyString2)))), + decodedFilters); + } - String pubkeyString1 = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - String pubkeyString2 = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + @Test + public void testGeohashTagFiltersDecoder() { + log.info("testGeohashTagFiltersDecoder"); - String joined = String.join("\",\"", pubkeyString1, pubkeyString2); - String expected = "{\"#p\":[\"" + joined + "\"]}"; + String geohashKey = "#g"; + String geohashValue = "2vghde"; + String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + geohashKey + "\":[\"" + geohashValue + "\"]}"; - Filters decodedFilters = new FiltersDecoder().decode(expected); + Filters decodedFilters = new FiltersDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); - assertEquals( - new Filters( - new ReferencedPublicKeyFilter<>(new PubKeyTag(new PublicKey(pubkeyString1))), - new ReferencedPublicKeyFilter<>(new PubKeyTag(new PublicKey(pubkeyString2)))), - decodedFilters); - } + assertEquals(new Filters(new GeohashTagFilter<>(new GeohashTag(geohashValue))), decodedFilters); + } - @Test - public void testGeohashTagFiltersDecoder() { - log.info("testGeohashTagFiltersDecoder"); + @Test + public void testMultipleGeohashTagFiltersDecoder() { + log.info("testMultipleGeohashTagFiltersDecoder"); - String geohashKey = "#g"; - String geohashValue = "2vghde"; - String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + geohashKey + "\":[\"" + geohashValue + "\"]}"; + String geohashKey = "#g"; + String geohashValue1 = "2vghde"; + String geohashValue2 = "3abcde"; + String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + geohashKey + "\":[\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]}"; - Filters decodedFilters = new FiltersDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); + Filters decodedFilters = new FiltersDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); - assertEquals(new Filters(new GeohashTagFilter<>(new GeohashTag(geohashValue))), decodedFilters); - } + assertEquals(new Filters( + new GeohashTagFilter<>(new GeohashTag(geohashValue1)), + new GeohashTagFilter<>(new GeohashTag(geohashValue2))), + decodedFilters); + } - @Test - public void testMultipleGeohashTagFiltersDecoder() { - log.info("testMultipleGeohashTagFiltersDecoder"); + @Test + public void testHashtagTagFiltersDecoder() { + log.info("testHashtagTagFiltersDecoder"); - String geohashKey = "#g"; - String geohashValue1 = "2vghde"; - String geohashValue2 = "3abcde"; - String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + geohashKey + "\":[\"" + geohashValue1 + "\",\"" + geohashValue2 + "\"]}"; + String hashtagKey = "#t"; + String hashtagValue = "2vghde"; + String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + hashtagKey + "\":[\"" + hashtagValue + "\"]}"; - Filters decodedFilters = new FiltersDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); + Filters decodedFilters = new FiltersDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); - assertEquals(new Filters( - new GeohashTagFilter<>(new GeohashTag(geohashValue1)), - new GeohashTagFilter<>(new GeohashTag(geohashValue2))), - decodedFilters); - } + assertEquals(new Filters(new HashtagTagFilter<>(new HashtagTag(hashtagValue))), decodedFilters); + } - @Test - public void testHashtagTagFiltersDecoder() { - log.info("testHashtagTagFiltersDecoder"); + @Test + public void testMultipleHashtagTagFiltersDecoder() { + log.info("testMultipleHashtagTagFiltersDecoder"); - String hashtagKey = "#t"; - String hashtagValue = "2vghde"; - String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + hashtagKey + "\":[\"" + hashtagValue + "\"]}"; + String hashtagKey = "#t"; + String hashtagValue1 = "2vghde"; + String hashtagValue2 = "3abcde"; + String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + hashtagKey + "\":[\"" + hashtagValue1 + "\",\"" + hashtagValue2 + "\"]}"; - Filters decodedFilters = new FiltersDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); + Filters decodedFilters = new FiltersDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); - assertEquals(new Filters(new HashtagTagFilter<>(new HashtagTag(hashtagValue))), decodedFilters); - } + assertEquals(new Filters( + new HashtagTagFilter<>(new HashtagTag(hashtagValue1)), + new HashtagTagFilter<>(new HashtagTag(hashtagValue2))), + decodedFilters); + } - @Test - public void testMultipleHashtagTagFiltersDecoder() { - log.info("testMultipleHashtagTagFiltersDecoder"); + @Test + public void testGenericTagFiltersDecoder() { + log.info("testGenericTagFiltersDecoder"); - String hashtagKey = "#t"; - String hashtagValue1 = "2vghde"; - String hashtagValue2 = "3abcde"; - String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + hashtagKey + "\":[\"" + hashtagValue1 + "\",\"" + hashtagValue2 + "\"]}"; + String customTagKey = "#b"; + String customTagValue = "2vghde"; + String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + customTagKey + "\":[\"" + customTagValue + "\"]}"; - Filters decodedFilters = new FiltersDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); + Filters decodedFilters = new FiltersDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); - assertEquals(new Filters( - new HashtagTagFilter<>(new HashtagTag(hashtagValue1)), - new HashtagTagFilter<>(new HashtagTag(hashtagValue2))), - decodedFilters); - } + assertEquals(new Filters(new GenericTagQueryFilter<>(new GenericTagQuery(customTagKey, customTagValue))), decodedFilters); + } - @Test - public void testGenericTagFiltersDecoder() { - log.info("testGenericTagFiltersDecoder"); + @Test + public void testMultipleGenericTagFiltersDecoder() { + log.info("testMultipleGenericTagFiltersDecoder"); - String customTagKey = "#b"; - String customTagValue = "2vghde"; - String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + customTagKey + "\":[\"" + customTagValue + "\"]}"; + String customTagKey = "#b"; + String customTagValue1 = "2vghde"; + String customTagValue2 = "3abcde"; - Filters decodedFilters = new FiltersDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); + String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + customTagKey + "\":[\"" + customTagValue1 + "\",\"" + customTagValue2 + "\"]}"; - assertEquals(new Filters(new GenericTagQueryFilter<>(new GenericTagQuery(customTagKey, customTagValue))), decodedFilters); - } + Filters decodedFilters = new FiltersDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); - @Test - public void testMultipleGenericTagFiltersDecoder() { - log.info("testMultipleGenericTagFiltersDecoder"); + assertEquals( + new Filters( + new GenericTagQueryFilter<>(new GenericTagQuery(customTagKey, customTagValue1)), + new GenericTagQueryFilter<>(new GenericTagQuery(customTagKey, customTagValue2))), + decodedFilters); + } - String customTagKey = "#b"; - String customTagValue1 = "2vghde"; - String customTagValue2 = "3abcde"; + @Test + public void testVoteTagFiltersDecoder() { + log.info("testVoteTagFiltersDecoder"); - String reqJsonWithCustomTagQueryFilterToDecode = "{\"" + customTagKey + "\":[\"" + customTagValue1 + "\",\"" + customTagValue2 + "\"]}"; + String voteTagKey = "#v"; + Integer voteTagValue = 1; + String reqJsonWithVoteTagFilterToDecode = "{\"" + voteTagKey + "\":[\"" + voteTagValue + "\"]}"; - Filters decodedFilters = new FiltersDecoder().decode(reqJsonWithCustomTagQueryFilterToDecode); + Filters decodedFilters = new FiltersDecoder().decode(reqJsonWithVoteTagFilterToDecode); - assertEquals( - new Filters( - new GenericTagQueryFilter<>(new GenericTagQuery(customTagKey, customTagValue1)), - new GenericTagQueryFilter<>(new GenericTagQuery(customTagKey, customTagValue2))), - decodedFilters); - } + assertEquals(new Filters(new VoteTagFilter<>(new VoteTag(voteTagValue))), decodedFilters); + } - @Test - public void testSinceFiltersDecoder() { - log.info("testSinceFiltersDecoder"); + @Test + public void testSinceFiltersDecoder() { + log.info("testSinceFiltersDecoder"); - Long since = Date.from(Instant.now()).getTime(); + Long since = Date.from(Instant.now()).getTime(); - String expected = "{\"since\":" + since + "}"; - Filters decodedFilters = new FiltersDecoder().decode(expected); + String expected = "{\"since\":" + since + "}"; + Filters decodedFilters = new FiltersDecoder().decode(expected); - assertEquals(new Filters(new SinceFilter(since)), decodedFilters); - } + assertEquals(new Filters(new SinceFilter(since)), decodedFilters); + } - @Test - public void testUntilFiltersDecoder() { - log.info("testUntilFiltersDecoder"); + @Test + public void testUntilFiltersDecoder() { + log.info("testUntilFiltersDecoder"); - Long until = Date.from(Instant.now()).getTime(); + Long until = Date.from(Instant.now()).getTime(); - String expected = "{\"until\":" + until + "}"; - Filters decodedFilters = new FiltersDecoder().decode(expected); + String expected = "{\"until\":" + until + "}"; + Filters decodedFilters = new FiltersDecoder().decode(expected); - assertEquals(new Filters(new UntilFilter(until)), decodedFilters); - } + assertEquals(new Filters(new UntilFilter(until)), decodedFilters); + } - @Test - public void testFailedAddressableTagMalformedSeparator() { - log.info("testFailedAddressableTagMalformedSeparator"); + @Test + public void testFailedAddressableTagMalformedSeparator() { + log.info("testFailedAddressableTagMalformedSeparator"); - Integer kind = 1; - String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; - String uuidValue1 = "UUID-1"; + Integer kind = 1; + String author = "f1b419a95cb0233a11d431423b41a42734e7165fcab16081cd08ef1c90e0be75"; + String uuidValue1 = "UUID-1"; - String malformedJoin = String.join(",", String.valueOf(kind), author, uuidValue1); - String expected = "{\"#a\":[\"" + malformedJoin + "\"]}"; + String malformedJoin = String.join(",", String.valueOf(kind), author, uuidValue1); + String expected = "{\"#a\":[\"" + malformedJoin + "\"]}"; - assertThrows(ArrayIndexOutOfBoundsException.class, () -> new FiltersDecoder().decode(expected)); - } + assertThrows(ArrayIndexOutOfBoundsException.class, () -> new FiltersDecoder().decode(expected)); + } } diff --git a/nostr-java-event/src/test/java/nostr/event/unit/FiltersEncoderTest.java b/nostr-java-event/src/test/java/nostr/event/unit/FiltersEncoderTest.java index 855ac1620..5686f7eb5 100644 --- a/nostr-java-event/src/test/java/nostr/event/unit/FiltersEncoderTest.java +++ b/nostr-java-event/src/test/java/nostr/event/unit/FiltersEncoderTest.java @@ -18,6 +18,7 @@ import nostr.event.filter.ReferencedPublicKeyFilter; import nostr.event.filter.SinceFilter; import nostr.event.filter.UntilFilter; +import nostr.event.filter.VoteTagFilter; import nostr.event.impl.GenericEvent; import nostr.event.json.codec.FiltersEncoder; import nostr.event.message.ReqMessage; @@ -27,6 +28,7 @@ import nostr.event.tag.HashtagTag; import nostr.event.tag.IdentifierTag; import nostr.event.tag.PubKeyTag; +import nostr.event.tag.VoteTag; import org.junit.jupiter.api.Test; import java.time.Instant; @@ -370,6 +372,19 @@ public void testMultipleAddressableTagFilterEncoder() { assertEquals("{\"#a\":[\"" + addressableTags + "\"]}", encoded); } + @Test + public void testVoteTagFiltersEncoder() { + log.info("testVoteTagFiltersEncoder"); + + Integer vote = 1; + + FiltersEncoder encoder = new FiltersEncoder( + new Filters(new VoteTagFilter<>(new VoteTag(vote)))); + + String encodedFilters = encoder.encode(); + assertEquals("{\"#v\":[\"1\"]}", encodedFilters); + } + @Test public void testSinceFiltersEncoder() { log.info("testSinceFiltersEncoder"); From 2019053024708a478c8e779232f5ddaecfdb131f Mon Sep 17 00:00:00 2001 From: nick avlo Date: Mon, 28 Apr 2025 20:01:15 -0700 Subject: [PATCH 34/37] cleanup --- .../nostr/event/message/EventMessage.java | 6 ++--- .../java/nostr/event/message/ReqMessage.java | 22 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/nostr-java-event/src/main/java/nostr/event/message/EventMessage.java b/nostr-java-event/src/main/java/nostr/event/message/EventMessage.java index 7853ad2d0..3aee54acf 100644 --- a/nostr-java-event/src/main/java/nostr/event/message/EventMessage.java +++ b/nostr-java-event/src/main/java/nostr/event/message/EventMessage.java @@ -61,15 +61,15 @@ public static T decode(@NonNull String jsonString) { } } - public static T processEvent(Object o) { + private static T processEvent(Object o) { return (T) new EventMessage(convertValue((Map) o)); } - public static T processEvent(Object[] msgArr) { + private static T processEvent(Object[] msgArr) { return (T) new EventMessage(convertValue((Map) msgArr[2]), msgArr[1].toString()); } private static GenericEvent convertValue(Map map) { - return nostr.base.IDecoder.I_DECODER_MAPPER_AFTERBURNER.convertValue(map, new TypeReference<>() {}); + return I_DECODER_MAPPER_AFTERBURNER.convertValue(map, new TypeReference<>() {}); } } diff --git a/nostr-java-event/src/main/java/nostr/event/message/ReqMessage.java b/nostr-java-event/src/main/java/nostr/event/message/ReqMessage.java index 735a12557..ed0c84464 100644 --- a/nostr-java-event/src/main/java/nostr/event/message/ReqMessage.java +++ b/nostr-java-event/src/main/java/nostr/event/message/ReqMessage.java @@ -34,11 +34,11 @@ public class ReqMessage extends BaseMessage { @JsonProperty private final List filtersList; - public ReqMessage(@NonNull String subscriptionId, Filters... filtersList) { + public ReqMessage(@NonNull String subscriptionId, @NonNull Filters... filtersList) { this(subscriptionId, List.of(filtersList)); } - public ReqMessage(@NonNull String subscriptionId, List filtersList) { + public ReqMessage(@NonNull String subscriptionId, @NonNull List filtersList) { super(Command.REQ.name()); validateSubscriptionId(subscriptionId); this.subscriptionId = subscriptionId; @@ -60,14 +60,6 @@ public String encode() throws JsonProcessingException { return ENCODER_MAPPED_AFTERBURNER.writeValueAsString(getArrayNode()); } - private static JsonNode createJsonNode(String jsonNode) { - try { - return ENCODER_MAPPED_AFTERBURNER.readTree(jsonNode); - } catch (JsonProcessingException e) { - throw new IllegalArgumentException(String.format("Malformed encoding ReqMessage json: [%s]", jsonNode), e); - } - } - public static T decode(@NonNull Object subscriptionId, @NonNull String jsonString) throws JsonProcessingException { validateSubscriptionId(subscriptionId.toString()); return (T) new ReqMessage( @@ -76,13 +68,21 @@ public static T decode(@NonNull Object subscriptionId, @ new FiltersDecoder().decode(filtersList)).toList()); } + private static JsonNode createJsonNode(String jsonNode) { + try { + return ENCODER_MAPPED_AFTERBURNER.readTree(jsonNode); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException(String.format("Malformed encoding ReqMessage json: [%s]", jsonNode), e); + } + } + private static void validateSubscriptionId(String subscriptionId) { if (!ValueRange.of(1, 64).isValidIntValue(subscriptionId.length())) { throw new IllegalArgumentException(String.format("SubscriptionId length must be between 1 and 64 characters but was [%d]", subscriptionId.length())); } } - private static List getJsonFiltersList(@NonNull String jsonString) throws JsonProcessingException { + private static List getJsonFiltersList(String jsonString) throws JsonProcessingException { return IntStream.range(FILTERS_START_INDEX, I_DECODER_MAPPER_AFTERBURNER.readTree(jsonString).size()) .mapToObj(idx -> readTree(jsonString, idx)).toList(); } From 989ad21464963b438f35575165851e0a02fc1e9b Mon Sep 17 00:00:00 2001 From: nick avlo Date: Mon, 28 Apr 2025 23:01:16 -0700 Subject: [PATCH 35/37] version up --- buildSrc/build.gradle | 2 +- gradle.properties | 2 +- metrics.md | 232 ---------------------------------- nostr-java-api/pom.xml | 2 +- nostr-java-base/pom.xml | 2 +- nostr-java-client/pom.xml | 2 +- nostr-java-crypto/pom.xml | 2 +- nostr-java-encryption/pom.xml | 2 +- nostr-java-event/pom.xml | 2 +- nostr-java-examples/pom.xml | 2 +- nostr-java-id/pom.xml | 2 +- nostr-java-util/pom.xml | 2 +- pom.xml | 4 +- 13 files changed, 13 insertions(+), 245 deletions(-) delete mode 100644 metrics.md diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 1ee59078a..24807fb22 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -11,7 +11,7 @@ plugins { } group = 'xyz.tcheeric' -version = '0.6.5-SNAPSHOT' +version = '0.6.6-SNAPSHOT' repositories { mavenCentral() diff --git a/gradle.properties b/gradle.properties index 40e91dec1..2bd5042ba 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ org.gradle.parallel=true org.gradle.warning.mode=none nostr-java.group=xyz.tcheeric -nostr-java.version=0.6.5-SNAPSHOT +nostr-java.version=0.6.6-SNAPSHOT nostr-java.description=nostr-java nostr-java.java-version=21 diff --git a/metrics.md b/metrics.md deleted file mode 100644 index 49ccce034..000000000 --- a/metrics.md +++ /dev/null @@ -1,232 +0,0 @@ -### nostr-java & superconductor: gradle -vs- maven metrics - - -[useful gradle commands (w/ maven equivalents) @ bottom of page](https://github.com/avlo/nostr-java-avlo-fork/blob/79d3f521e177ebd5e741490e1cdc0f1001e07c49/metrics.md#useful-gradle-commands-w-maven-equivalents) - - ----- -##### nostr-java clean build - -###### maven: ~19 seconds - -```java -$ time mvn clean install -Dmaven.test.skip=true - - [INFO] BUILD SUCCESS - [INFO] ------------------------------------------------------------------------ - [INFO] Total time: 17.592 s - [INFO] Finished at: 2025-03-14T22:31:52-07:00 - [INFO] ------------------------------------------------------------------------ - - real 0m19.156s <----------------------------------------------------------- ~19 sec - user 0m58.042s - sys 0m1.616s -``` - -###### gradle: ~9 seconds -``` java -$ time gradle clean build -x test - -BUILD SUCCESSFUL in 9s -38 actionable tasks: 38 executed -Configuration cache entry reused. - - real 0m9.618s <------------------------------------------------------------- ~9 sec - user 0m2.383s - sys 0m0.116s -``` ----- - -#### nostr-java, subsequent build - -###### maven: ~21 seconds - -```java -$ time mvn install -Dmaven.test.skip=true - [INFO] BUILD SUCCESS - [INFO] ------------------------------------------------------------------------ - [INFO] Total time: 19.680 s - [INFO] Finished at: 2025-03-14T22:39:35-07:00 - [INFO] ------------------------------------------------------------------------ - - real 0m21.324s <------------------------------------------------------------- ~21sec - user 1m3.300s - sys 0m1.771s -``` - -###### gradle: ~1 second - -```java -$ time gradle build -x test - Reusing configuration cache. - - BUILD SUCCESSFUL in 1s - 27 actionable tasks: 27 up-to-date - Configuration cache entry reused. - - real 0m1.178s <------------------------------------------------------------- ~1sec - user 0m2.082s - sys 0m0.176s -``` - ----- -#### superconductor clean build: - -###### maven: ~11 seconds - -```java -$ time mvn clean install -Dmaven.test.skip=true - [INFO] BUILD SUCCESS - [INFO] ------------------------------------------------------------------------ - [INFO] Total time: 10.437 s - [INFO] Finished at: 2025-03-14T22:46:58-07:00 - [INFO] ------------------------------------------------------------------------ - - real 0m11.989s <------------------------------------------------------------- ~11sec - user 0m36.291s - sys 0m1.351s -``` - -###### gradle: ~7 seconds - -```java -$ time gradle clean build -x test - - BUILD SUCCESSFUL in 7s - 7 actionable tasks: 4 executed, 2 from cache, 1 up-to-date - - real 0m7.655s <------------------------------------------------------------ ~7sec - user 0m2.800s - sys 0m0.164s -``` - ----- -#### superconductor clean build integration- test: - -###### maven: ~1 minute, 15 seconds -``` java -$ mvn clean install - - [INFO] Results: - [INFO] - [INFO] Tests run: 52, Failures: 0, Errors: 0, Skipped: 0 - [INFO] ------------------------------------------------------------------------ - [INFO] BUILD SUCCESS - [INFO] ------------------------------------------------------------------------ - [INFO] Total time: 01:15 min <--------------------------------------------------- ~1min 15sec* - [INFO] Finished at: 2025-03-14T23:26:59-07:00 - [INFO] ------------------------------------------------------------------------ - - real 1m16.800s <----------------------------------------------------------------- ~1min 16sec* - user 2m58.945s - sys 0m4.427s -``` -_*one-second time diff re: maven publish to local repo (`~/.m2/xyz/tcheeric/...`)_ - -###### gradle: ~49 seconds -``` java -$ time gradle build - - SUCCESS: Executed 61 tests in 41.1s - - BUILD SUCCESSFUL in 49s - 10 actionable tasks: 6 executed, 4 from cache - - real 0m49.269s <------------------------------------------------------------- ~49sec - user 0m2.854s - sys 0m0.201s -``` - ----- - -#### superconductor subsequent build test -_historical note: significant down/wait-time using maven occurs here, thus motivating gradle build/test option for superconductor, initially, with nostr-java subsequently profiting from same feature/option. **from** **~1 minute** **down to** **~4 seconds** significantly improves workflow continuity. furthermore, multiple iterative test builds occurring as per typical development results in significant cumulative time saved._ - -###### maven: ~1 minute, 3 seconds - -```java -$ time mvn install - - [INFO] Results: - [INFO] - [INFO] Tests run: 52, Failures: 0, Errors: 0, Skipped: 0 - [INFO] ------------------------------------------------------------------------ - [INFO] BUILD SUCCESS - [INFO] ------------------------------------------------------------------------ - [INFO] Total time: 01:03 min <--------------------------------------------------------- ~1min 3sec** - [INFO] Finished at: 2025-03-14T23:35:18-07:00 - [INFO] ------------------------------------------------------------------------ - - real 1m6.132s <------------------------------------------------------------------------ ~1min 6sec** - user 2m32.305s - sys 0m3.797s -``` -_** three-second time diff re: maven publish to local repo (`~/.m2/xyz/tcheeric/...`)_ - -###### gradle: ~4 seconds -```java -$ time gradle build - -BUILD SUCCESSFUL in 3s -10 actionable tasks: 10 up-to-date - -real 0m4.020s <------------------------------------------------------------------------ ~4sec -user 0m2.913s -``` - ----- - -#### nostr-java integration tests against running SC - -###### maven: ~29 seconds - -```java -$ time mvn test - - [INFO] Results: - [INFO] - [INFO] Tests run: 159, Failures: 0, Errors: 0, Skipped: 0 - [INFO] - [INFO] ------------------------------------------------------------------------ - [INFO] Reactor Summary for nostr-java 0.6.5-SNAPSHOT: - [INFO] - [INFO] nostr-java ......................................... SUCCESS [ 0.012 s] - [INFO] nostr-java-util .................................... SUCCESS [ 0.900 s] - [INFO] nostr-java-crypto .................................. SUCCESS [ 0.353 s] - [INFO] nostr-java-base .................................... SUCCESS [ 0.246 s] - [INFO] nostr-java-event ................................... SUCCESS [ 0.142 s] - [INFO] nostr-java-id ...................................... SUCCESS [ 0.164 s] - [INFO] nostr-java-client .................................. SUCCESS [ 0.438 s] - [INFO] nostr-java-encryption .............................. SUCCESS [ 0.050 s] - [INFO] nostr-java-encryption-nip04 ........................ SUCCESS [ 0.107 s] - [INFO] nostr-java-encryption-nip44 ........................ SUCCESS [ 0.114 s] - [INFO] nostr-java-api ..................................... SUCCESS [ 0.190 s] - [INFO] nostr-java-examples ................................ SUCCESS [ 0.212 s] - [INFO] nostr-java-test .................................... SUCCESS [ 25.933 s] - [INFO] ------------------------------------------------------------------------ - [INFO] BUILD SUCCESS - [INFO] ------------------------------------------------------------------------ - [INFO] Total time: 29.084 s <------------------------------------------------------------------------ ~29sec*** - [INFO] Finished at: 2025-03-15T12:41:13-07:00 - [INFO] ------------------------------------------------------------------------ - - real 0m30.669s <-------------------------------------------------------------------------------------- ~30sec*** - user 1m2.185s - sys 0m1.698s -``` -_*** one-second time diff re: maven publish to local repo (`~/.m2/xyz/tcheeric/...`)_ - ----- - -##### useful gradle commands (w/ maven equivalents) - -```java -alias gc='gradle clean' // 'maven clean' -alias gb='gradle build' // 'maven build' -alias gcb='gradle clean build' // 'maven clean build' - -alias gbnotest='gradle build -x test' // 'maven build -Dmaven.test.skip=true' -alias gcbnotest='gradle clean build -x test' // 'maven clean build -Dmaven.test.skip=true' - -alias gpub='gradle publishToMavenLocal' // 'maven install' -``` diff --git a/nostr-java-api/pom.xml b/nostr-java-api/pom.xml index b60f18204..c96a05475 100644 --- a/nostr-java-api/pom.xml +++ b/nostr-java-api/pom.xml @@ -4,7 +4,7 @@ xyz.tcheeric nostr-java - 0.6.5-SNAPSHOT + 0.6.6-SNAPSHOT ../pom.xml diff --git a/nostr-java-base/pom.xml b/nostr-java-base/pom.xml index 103219fe7..5e31c3a23 100644 --- a/nostr-java-base/pom.xml +++ b/nostr-java-base/pom.xml @@ -4,7 +4,7 @@ xyz.tcheeric nostr-java - 0.6.5-SNAPSHOT + 0.6.6-SNAPSHOT ../pom.xml diff --git a/nostr-java-client/pom.xml b/nostr-java-client/pom.xml index 405a337fe..f31ee23c9 100644 --- a/nostr-java-client/pom.xml +++ b/nostr-java-client/pom.xml @@ -4,7 +4,7 @@ xyz.tcheeric nostr-java - 0.6.5-SNAPSHOT + 0.6.6-SNAPSHOT ../pom.xml diff --git a/nostr-java-crypto/pom.xml b/nostr-java-crypto/pom.xml index b60d82e4e..9113f4be4 100644 --- a/nostr-java-crypto/pom.xml +++ b/nostr-java-crypto/pom.xml @@ -4,7 +4,7 @@ xyz.tcheeric nostr-java - 0.6.5-SNAPSHOT + 0.6.6-SNAPSHOT ../pom.xml diff --git a/nostr-java-encryption/pom.xml b/nostr-java-encryption/pom.xml index 5a7734cf5..7520a3d2e 100644 --- a/nostr-java-encryption/pom.xml +++ b/nostr-java-encryption/pom.xml @@ -4,7 +4,7 @@ xyz.tcheeric nostr-java - 0.6.5-SNAPSHOT + 0.6.6-SNAPSHOT ../pom.xml diff --git a/nostr-java-event/pom.xml b/nostr-java-event/pom.xml index 00c84a67c..afde0d680 100644 --- a/nostr-java-event/pom.xml +++ b/nostr-java-event/pom.xml @@ -4,7 +4,7 @@ xyz.tcheeric nostr-java - 0.6.5-SNAPSHOT + 0.6.6-SNAPSHOT ../pom.xml diff --git a/nostr-java-examples/pom.xml b/nostr-java-examples/pom.xml index 6ebdb695c..03bff57e8 100644 --- a/nostr-java-examples/pom.xml +++ b/nostr-java-examples/pom.xml @@ -4,7 +4,7 @@ xyz.tcheeric nostr-java - 0.6.5-SNAPSHOT + 0.6.6-SNAPSHOT ../pom.xml diff --git a/nostr-java-id/pom.xml b/nostr-java-id/pom.xml index 884eade2e..011a81b35 100644 --- a/nostr-java-id/pom.xml +++ b/nostr-java-id/pom.xml @@ -4,7 +4,7 @@ xyz.tcheeric nostr-java - 0.6.5-SNAPSHOT + 0.6.6-SNAPSHOT ../pom.xml diff --git a/nostr-java-util/pom.xml b/nostr-java-util/pom.xml index 2a63680da..85749f7b4 100644 --- a/nostr-java-util/pom.xml +++ b/nostr-java-util/pom.xml @@ -4,7 +4,7 @@ xyz.tcheeric nostr-java - 0.6.5-SNAPSHOT + 0.6.6-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 5de38fe04..9874e9a02 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ xyz.tcheeric nostr-java - 0.6.5-SNAPSHOT + 0.6.6-SNAPSHOT pom @@ -77,7 +77,7 @@ - 0.6.5-SNAPSHOT + 0.6.6-SNAPSHOT 21 3.4.3 From ea2f7c70e3d130a28a4ca60a83bb4e303b30aac4 Mon Sep 17 00:00:00 2001 From: nick avlo Date: Tue, 29 Apr 2025 00:37:17 -0700 Subject: [PATCH 36/37] readme up --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e1c354428..b6a4f8a72 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,8 @@ To use it in your project, add the following to your pom.xml file. ## Examples I recommend having a look at these repositories/module for examples: - [nostr-example](https://github.com/tcheeric/nostr-java/tree/main/nostr-java-examples) module - - [nostr-client](https://github.com/tcheeric/nostr-client) github repository - - [SuperConductor](https://github.com/avlo/superconductor) nostr relay + - [subdivisions](https://github.com/avlo/subdivisions/tree/master) nostr web-socket client & client-complementary utilities + - [superconductor](https://github.com/avlo/superconductor) nostr relay ## Supported NIPs @@ -68,4 +68,4 @@ The following NIPs are supported by the API out-of-the-box: - [NIP-57](https://github.com/nostr-protocol/nips/blob/master/57.md) - [NIP-60](https://github.com/nostr-protocol/nips/blob/master/60.md) - [NIP-61](https://github.com/nostr-protocol/nips/blob/master/61.md) -- [NIP-99](https://github.com/nostr-protocol/nips/blob/master/99.md) \ No newline at end of file +- [NIP-99](https://github.com/nostr-protocol/nips/blob/master/99.md) From 2869749c02452286536d6e5ecbc1ae99157286ff Mon Sep 17 00:00:00 2001 From: nick avlo Date: Wed, 30 Apr 2025 13:20:18 -0700 Subject: [PATCH 37/37] build instructions --- README.md | 102 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 81 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index b6a4f8a72..2dfbf1283 100644 --- a/README.md +++ b/README.md @@ -8,39 +8,99 @@ Nostr-java is a library for generating, signing, and publishing nostr events to - Java 21+ ## Usage -To use it in your project, add the following to your pom.xml file. +### To use nostr-java in your project, two options: + +#### Option 1 - add release version and jitpack.io repository to your pom.xml file ```xml - - v0.007.1-alpha - UTF-8 - + + v0.007.1-alpha + UTF-8 + ``` ```xml - - - jitpack.io - https://jitpack.io - - + + + jitpack.io + https://jitpack.io + + +``` +#### Option 2 - Check out and build project directly from source + +```bash +$ cd +$ git clone git@github.com:tcheeric/nostr-java.git +$ cd nostr-java +$ git checkout ``` +
+ unit-tested build (does not require a nostr-relay for testing) + +###### maven + (unix) + $ ./mvnw clean test + $ ./mvnw install -Dmaven.test.skip=true + + (windows) + $ ./mvnw.cmd clean test + $ ./mvnw.cmd install -Dmaven.test.skip=true + + +###### gradle + + (unix) + $ ./gradlew clean test + $ ./gradlew publishToMavenLocal + + (windows) + $ ./gradlew.bat clean test + $ ./gradlew.bat publishToMavenLocal +
+ +
+ integration-tested build (requires a nostr-relay for testing) + +valid relay(s) must **_first_** be defined in [relays.properties](nostr-java-api/src/main/resources/relays.properties) file, then + +###### maven + (unix) + $ ./mvnw clean install + + (windows) + $ ./mvnw.cmd clean install + +###### gradle + (unix) + $ ./gradlew clean check + $ ./gradlew publishToMavenLocal + + (windows) + $ ./gradlew.bat clean check + $ ./gradlew.bat publishToMavenLocal +
+ +#### add dependency to your pom.xml + ```xml - - - nostr-java - nostr-java-api - ${nostr-java.version} - - + + + nostr-java + nostr-java-api + ${nostr-java.version} + + ``` + + ## Examples I recommend having a look at these repositories/module for examples: - - [nostr-example](https://github.com/tcheeric/nostr-java/tree/main/nostr-java-examples) module - - [subdivisions](https://github.com/avlo/subdivisions/tree/master) nostr web-socket client & client-complementary utilities - - [superconductor](https://github.com/avlo/superconductor) nostr relay +- [nostr-example](https://github.com/tcheeric/nostr-java/tree/main/nostr-java-examples) module +- [nostr-client](https://github.com/tcheeric/nostr-client) github repository +- [SuperConductor](https://github.com/avlo/superconductor) nostr relay ## Supported NIPs