Skip to content

Commit e4258af

Browse files
authored
24 create class types for common identifiers (#40)
* Fix UserID toString identifier * Add EventID - Unlike other IDs, this one has lax verification to not mess up with early room specs. * Remove unnecesary escapes * Add tests * Add ID classes into existing records. - Add JsonCreator and JsonValue to bring serialization into the class - Patch tests to match correct class type --------- Co-authored-by: J.C <73438877+1hiking@users.noreply.github.com>
1 parent ecb64c1 commit e4258af

13 files changed

Lines changed: 215 additions & 67 deletions

File tree

src/main/java/io/github/hikingc/matrixsdk/api/Event.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import io.github.hikingc.matrixsdk.api.events.*;
44
import io.github.hikingc.matrixsdk.api.events.content.MessageEventContent;
55
import io.github.hikingc.matrixsdk.api.events.content.StateEventContent;
6-
import io.github.hikingc.matrixsdk.api.events.model.RoomMemberEvent;
76
import io.github.hikingc.matrixsdk.api.events.queries.ChronologicalDirection;
87
import io.github.hikingc.matrixsdk.api.events.queries.Membership;
98
import io.github.hikingc.matrixsdk.api.events.queries.QueryParametersMessages;
@@ -12,6 +11,7 @@
1211
import io.github.hikingc.matrixsdk.api.identifiers.RoomID;
1312
import io.github.hikingc.matrixsdk.exceptions.MatrixIOException;
1413
import io.github.hikingc.matrixsdk.exceptions.MatrixNetworkException;
14+
import io.github.hikingc.matrixsdk.api.events.model.RoomMemberEvent;
1515
import java.nio.file.Path;
1616
import java.util.List;
1717

@@ -33,9 +33,8 @@ public interface Event {
3333
/// @param roomId the room ID where the event is.
3434
/// @param eventId the event ID to retrieve.
3535
/// @return the full event.
36-
/// @throws MatrixIOException when the payload cannot be processed.
37-
/// @throws MatrixNetworkException when the response status is not successful.
38-
ClientEvent getEvent(RoomID roomId, String eventId);
36+
@SuppressWarnings("java:S1452")
37+
ClientEvent<?> getEvent(RoomID roomId, String eventId);
3938

4039
/// Returns currently-joined members
4140
///
@@ -79,9 +78,7 @@ List<RoomMemberEvent> getMembers(
7978
/// @throws MatrixIOException when the payload cannot be processed.
8079
/// @throws MatrixNetworkException when the response status is not successful.
8180
@SuppressWarnings("java:S1452")
82-
// Caller doesn't know content type ahead of time; polymorphic dispatch via @JsonTypeInfo resolves
83-
// it
84-
ClientEvent<?> getStateEvent(RoomID roomId, String eventType, String stateKey);
81+
StateEvent<?> getStateEvent(RoomID roomId, String eventType, String stateKey);
8582

8683
/// Returns a list of message and state events for a room. It uses pagination query parameters to
8784
/// paginate history in the room. The content is not parsed or escaped which means newlines (`\n`)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package io.github.hikingc.matrixsdk.api.identifiers;
2+
3+
import java.nio.charset.StandardCharsets;
4+
import java.util.Objects;
5+
6+
/// This class allows for the representation and validation of an Event ID in Matrix.
7+
///
8+
/// Their form is as follows: `$opaque_id`, some room versions include a `domain` component, whereas
9+
/// more recent room versions omit the domain and use a base64-encoded hash instead.
10+
///
11+
/// The length of a [EventID], including the `$` sigil, **MUST NOT** exceed 255 bytes.
12+
///
13+
/// @see <a href="https://spec.matrix.org/v1.19/appendices/#event-ids">Event Identifiers as defined
14+
/// in the specification</a>
15+
public final class EventID implements Validator {
16+
private final String opaqueId;
17+
18+
private EventID(String opaqueId) {
19+
this.opaqueId = opaqueId;
20+
}
21+
22+
/// Builds and validates a [RoomID]
23+
///
24+
/// @param rawRoomId the [String] to validate.
25+
/// @return a [RoomID].
26+
/// @throws IllegalArgumentException if the [String] has broken a rule from the spec.
27+
/// @throws NullPointerException if the [String] is null.
28+
public static EventID parse(String rawRoomId) {
29+
Objects.requireNonNull(rawRoomId, "Room ID" + " must not be null");
30+
31+
if (rawRoomId.getBytes(StandardCharsets.UTF_8).length > MAX_BYTES) {
32+
throw new IllegalArgumentException("Event ID exceeds " + MAX_BYTES + " bytes");
33+
}
34+
35+
if (rawRoomId.isEmpty()) {
36+
throw new IllegalArgumentException("Event ID must not be empty");
37+
}
38+
39+
if (rawRoomId.charAt(0) != '$') {
40+
throw new IllegalArgumentException("Event ID must start with '$'");
41+
}
42+
43+
if (rawRoomId.contentEquals("$")) {
44+
throw new IllegalArgumentException("Event ID must not only contain '$'");
45+
}
46+
47+
return new EventID(rawRoomId);
48+
}
49+
50+
@Override
51+
public int hashCode() {
52+
return Objects.hash(opaqueId);
53+
}
54+
55+
@Override
56+
public boolean equals(Object obj) {
57+
if (obj == this) return true;
58+
if (obj == null || obj.getClass() != this.getClass()) return false;
59+
var that = (EventID) obj;
60+
return Objects.equals(this.opaqueId, that.opaqueId);
61+
}
62+
63+
@Override
64+
public String toString() {
65+
return "$" + opaqueId;
66+
}
67+
}

src/main/java/io/github/hikingc/matrixsdk/api/identifiers/RoomAlias.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.github.hikingc.matrixsdk.api.identifiers;
22

3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonValue;
35
import java.util.Objects;
46

57
/// This class allows for the representation and validation of a Room Alias in Matrix.
@@ -32,6 +34,7 @@ private RoomAlias(String opaqueId, String domain) {
3234
/// @return a [RoomAlias].
3335
/// @throws IllegalArgumentException if the [String] has broken a rule from the spec.
3436
/// @throws NullPointerException if the [String] is null.
37+
@JsonCreator
3538
public static RoomAlias parse(String rawAliasId) {
3639
Objects.requireNonNull(rawAliasId, "Alias ID" + " must not be null");
3740

@@ -58,6 +61,7 @@ public boolean equals(Object obj) {
5861
}
5962

6063
@Override
64+
@JsonValue
6165
public String toString() {
6266
return "#" + opaqueId + ":" + domain;
6367
}

src/main/java/io/github/hikingc/matrixsdk/api/identifiers/RoomID.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.github.hikingc.matrixsdk.api.identifiers;
22

3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonValue;
35
import java.util.Objects;
46

57
/// This class allows for the representation and validation of a Room Identifier in Matrix.
@@ -32,6 +34,7 @@ private RoomID(String opaqueId, String domain) {
3234
/// @return a [RoomID].
3335
/// @throws IllegalArgumentException if the [String] has broken a rule from the spec.
3436
/// @throws NullPointerException if the [String] is null.
37+
@JsonCreator
3538
public static RoomID parse(String rawRoomId) {
3639
Objects.requireNonNull(rawRoomId, "Room ID" + " must not be null");
3740

@@ -58,6 +61,7 @@ public boolean equals(Object obj) {
5861
}
5962

6063
@Override
64+
@JsonValue
6165
public String toString() {
6266
return "!" + opaqueId + ":" + domain;
6367
}

src/main/java/io/github/hikingc/matrixsdk/api/identifiers/UserID.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.github.hikingc.matrixsdk.api.identifiers;
22

3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonValue;
35
import java.util.Objects;
46

57
/// This class allows for the representation and validation of a User Identifier in Matrix.
@@ -30,6 +32,7 @@ private UserID(String opaqueId, String domain) {
3032
/// @return a [UserID].
3133
/// @throws IllegalArgumentException if the [String] has broken a rule from the spec.
3234
/// @throws NullPointerException if the [String] is null.
35+
@JsonCreator
3336
public static UserID parse(String rawUserId) {
3437
Objects.requireNonNull(rawUserId, "User ID" + " must not be null");
3538

@@ -61,7 +64,8 @@ public boolean equals(Object obj) {
6164
}
6265

6366
@Override
67+
@JsonValue
6468
public String toString() {
65-
return "!" + localpart + ":" + domain;
69+
return "@" + localpart + ":" + domain;
6670
}
6771
}

src/main/java/io/github/hikingc/matrixsdk/api/rooms/RoomMembershipRequest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package io.github.hikingc.matrixsdk.api.rooms;
22

3+
import io.github.hikingc.matrixsdk.api.identifiers.UserID;
4+
35
import java.util.Objects;
46

57
/// This record represents the required values to be supplied to actions like banning or kicking.
68
///
79
/// @param reason The reason of the expulsion, the target will receive this message.
810
/// @param userId The id of the target to expel.
9-
public record RoomMembershipRequest(String reason, String userId) {
11+
public record RoomMembershipRequest(String reason, UserID userId) {
1012

1113
/// Compact constructor designed to validate nullity.
1214
///

src/main/java/io/github/hikingc/matrixsdk/api/rooms/ThirdPartySigned.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import com.fasterxml.jackson.annotation.JsonProperty;
44
import java.util.Map;
5+
6+
import io.github.hikingc.matrixsdk.api.identifiers.UserID;
57
import org.jspecify.annotations.NullMarked;
68

79
/// Holds information to supply the server and verify a `m.room.third_party_invite` event.
@@ -12,7 +14,7 @@
1214
/// @param token the state key of the `m.third_party_invite` event.
1315
@NullMarked
1416
public record ThirdPartySigned(
15-
@JsonProperty(required = true) String mxid,
16-
@JsonProperty(required = true) String sender,
17+
@JsonProperty(required = true) UserID mxid,
18+
@JsonProperty(required = true) UserID sender,
1719
@JsonProperty(required = true) Map<String, Map<String, String>> signatures,
1820
@JsonProperty(required = true) String token) {}

src/main/java/io/github/hikingc/matrixsdk/api/rooms/models/PublishedRoomsChunk.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import com.fasterxml.jackson.annotation.JsonProperty;
44
import io.github.hikingc.matrixsdk.api.Room;
5+
import io.github.hikingc.matrixsdk.api.identifiers.RoomAlias;
6+
import io.github.hikingc.matrixsdk.api.identifiers.RoomID;
57
import io.github.hikingc.matrixsdk.api.identifiers.Validator;
68
import java.net.URI;
79
import java.util.List;
@@ -25,12 +27,12 @@
2527
/// additional values for a determinate room
2628
public record PublishedRoomsChunk(
2729
URI avatarUrl,
28-
String canonicalAlias,
30+
RoomAlias canonicalAlias,
2931
@JsonProperty(required = true) boolean guestCanJoin,
3032
String joinRule,
3133
String name,
3234
@JsonProperty(required = true) int numJoinedMembers,
33-
@NonNull @JsonProperty(required = true) String roomId,
35+
@NonNull @JsonProperty(required = true) RoomID roomId,
3436
String roomType,
3537
String topic,
3638
@JsonProperty(required = true) boolean worldReadable) {}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package io.github.hikingc.matrixsdk.api.rooms.models;
22

3+
import io.github.hikingc.matrixsdk.api.identifiers.RoomID;
34
import java.util.List;
45

56
/// This record contains data when resolving a room alias.
67
///
78
/// @param roomId the room id for the room alias.
89
/// @param servers a list of servers aware of said alias.
9-
public record ResolvedAlias(String roomId, List<String> servers) {}
10+
public record ResolvedAlias(RoomID roomId, List<String> servers) {}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package io.github.hikingc.matrixsdk.api.identifiers;
2+
3+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import org.junit.jupiter.params.ParameterizedTest;
7+
import org.junit.jupiter.params.provider.EmptySource;
8+
import org.junit.jupiter.params.provider.NullSource;
9+
import org.junit.jupiter.params.provider.ValueSource;
10+
11+
class EventIDTest {
12+
13+
@ParameterizedTest(name = "[{index}] \"{0}\"")
14+
@ValueSource(
15+
strings = {
16+
// v3+ reference-hash shape
17+
"$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
18+
"$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI9dSaz3jRoiQ-fXE",
19+
"$acR1l0raRc2h8DzKlR4E9RAxwbrIY8v_4V-1kfBGCiA",
20+
"$LWXstUyAjMr8vBiVjTMH_hEcnKMhc0zVi52gxHYzc-4",
21+
"$1c-AYXvOG3AH0z9OTfHktZ4b6l3f1uK1Wv4h5CkQY9U",
22+
// legacy v1/v2 shape
23+
"$acR1l0raRc2h8DzKlR4E9RAxwbrIY8v_4V-1kfBGCiA:matrix.org",
24+
"$event1:example.com",
25+
"$143273582443PhrSn:example.org",
26+
// opaque content is allowed to contain "unusual" characters per spec —
27+
// clients must not impose structure beyond the sigil
28+
"$acR1l0raRc2h8DzKlR4E9RAxwbrIY8v/4V+1kfBGCiA", // non-base64url chars, still opaque
29+
"$has spaces in it",
30+
"$has\nnewline",
31+
"$emoji🎉event",
32+
"$has\"quote",
33+
"$ " // single space after sigil is still non-empty content
34+
})
35+
void withValidStrings_ReturnEventID(String id) {
36+
assertDoesNotThrow(() -> EventID.parse(id), "Exception not expected for input: " + id);
37+
}
38+
39+
@ParameterizedTest(name = "[{index}] \"{0}\"")
40+
@ValueSource(
41+
strings = {
42+
"acR1l0raRc2h8DzKlR4E9RAxwbrIY8v_4V-1kfBGCiA", // missing sigil
43+
"@acR1l0raRc2h8DzKlR4E9RAxwbrIY8v_4V-1kfBGCiA", // wrong sigil (User ID)
44+
"!acR1l0raRc2h8DzKlR4E9RAxwbrIY8v_4V-1kfBGCiA", // wrong sigil (Room ID)
45+
"#acR1l0raRc2h8DzKlR4E9RAxwbrIY8v_4V-1kfBGCiA", // wrong sigil (Room Alias)
46+
" ", // no sigil at all, just whitespace
47+
"$" // sigil present but zero content after it
48+
})
49+
void withInvalidStrings_ThrowsException(String id) {
50+
assertThrows(
51+
IllegalArgumentException.class,
52+
() -> EventID.parse(id),
53+
"Exception expected for input: " + id);
54+
}
55+
56+
@ParameterizedTest
57+
@NullSource
58+
void withNull_ThrowsException(String id) {
59+
assertThrows(NullPointerException.class, () -> EventID.parse(id));
60+
}
61+
62+
@ParameterizedTest
63+
@EmptySource
64+
void withEmpty_ThrowsException(String id) {
65+
assertThrows(IllegalArgumentException.class, () -> EventID.parse(id));
66+
}
67+
}

0 commit comments

Comments
 (0)