aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDoug <git@pixlwave.uk>2021-09-30 20:25:15 +0100
committerDoug <git@pixlwave.uk>2021-09-30 20:25:15 +0100
commit35c3fe43c3cfdfa5ff8efa3553dcb991c88626b0 (patch)
tree1066f3155e5c84eaa9f9d32f6611d5ca1e839ec7
parent3993f4880d252a02b0031aa8594acf4ee7609631 (diff)
downloadMatrixCore-35c3fe43c3cfdfa5ff8efa3553dcb991c88626b0.tar.gz
MatrixCore-35c3fe43c3cfdfa5ff8efa3553dcb991c88626b0.tar.xz
MatrixCore-35c3fe43c3cfdfa5ff8efa3553dcb991c88626b0.zip
Add common events with DecodableRoomEvents property wrapper.
-rw-r--r--Sources/MatrixClient/API/Events/Membership.swift12
-rw-r--r--Sources/MatrixClient/API/Events/Relationship.swift27
-rw-r--r--Sources/MatrixClient/API/Events/Room/MessageContent.swift59
-rw-r--r--Sources/MatrixClient/API/Events/Room/RoomMessageEvent.swift22
-rw-r--r--Sources/MatrixClient/API/Events/Room/RoomReactionEvent.swift30
-rw-r--r--Sources/MatrixClient/API/Events/Room/RoomRedactionEvent.swift29
-rw-r--r--Sources/MatrixClient/API/Events/RoomEvent.swift92
-rw-r--r--Sources/MatrixClient/API/Events/State/RoomEncryptionEvent.swift28
-rw-r--r--Sources/MatrixClient/API/Events/State/RoomMemberEvent.swift39
-rw-r--r--Sources/MatrixClient/API/Events/State/RoomNameEvent.swift29
10 files changed, 367 insertions, 0 deletions
diff --git a/Sources/MatrixClient/API/Events/Membership.swift b/Sources/MatrixClient/API/Events/Membership.swift
new file mode 100644
index 0000000..1547af5
--- /dev/null
+++ b/Sources/MatrixClient/API/Events/Membership.swift
@@ -0,0 +1,12 @@
+import Foundation
+
+public enum Membership: String, Decodable {
+ case invite, join, knock, leave, ban, unknown
+
+ // implement a custom decoder that will decode as unknown if
+ // the string received can't be decoded as one of the cases
+ public init(from decoder: Decoder) throws {
+ let container = try decoder.singleValueContainer()
+ self = Membership(rawValue: (try? container.decode(String.self)) ?? "") ?? .unknown
+ }
+}
diff --git a/Sources/MatrixClient/API/Events/Relationship.swift b/Sources/MatrixClient/API/Events/Relationship.swift
new file mode 100644
index 0000000..b116229
--- /dev/null
+++ b/Sources/MatrixClient/API/Events/Relationship.swift
@@ -0,0 +1,27 @@
+import Foundation
+
+public struct Relationship: Codable {
+ public let type: RelationshipType?
+ public let eventID: String?
+ public let key: String?
+
+ enum CodingKeys: String, CodingKey {
+ case type = "rel_type"
+ case eventID = "event_id"
+ case key
+ }
+
+ public enum RelationshipType: String, Codable {
+ case annotation = "m.annotation"
+ case replace = "m.replace"
+ case reference = "m.reference"
+ case unknown
+
+ // implement a custom decoder that will decode as unknown if
+ // the string received can't be decoded as one of the cases
+ public init(from decoder: Decoder) throws {
+ let container = try decoder.singleValueContainer()
+ self = RelationshipType(rawValue: (try? container.decode(String.self)) ?? "" ) ?? .unknown
+ }
+ }
+}
diff --git a/Sources/MatrixClient/API/Events/Room/MessageContent.swift b/Sources/MatrixClient/API/Events/Room/MessageContent.swift
new file mode 100644
index 0000000..a85e1d5
--- /dev/null
+++ b/Sources/MatrixClient/API/Events/Room/MessageContent.swift
@@ -0,0 +1,59 @@
+import Foundation
+
+public struct MessageContent: Decodable {
+ // required
+ public let body: String?
+ public let type: MessageType?
+
+ // optional
+ public let relationship: Relationship?
+ public let mediaURL: URL?
+ public let mediaInfo: MediaInfo?
+
+ // edit event
+ public let newContent: NewContent?
+
+ enum CodingKeys: String, CodingKey {
+ case body
+ case type = "msgtype"
+ case relationship = "m.relates_to"
+ case mediaURL = "url"
+ case mediaInfo = "info"
+ case newContent = "m.new_content"
+ }
+
+ public struct MediaInfo: Decodable {
+ public let width: Int?
+ public let height: Int?
+ public let mimetype: String?
+ public let fileSize: Int?
+
+ enum CodingKeys: String, CodingKey {
+ case width = "w"
+ case height = "h"
+ case mimetype
+ case fileSize = "size"
+ }
+ }
+
+ public struct NewContent: Decodable {
+ public let body: String?
+ }
+
+ public enum MessageType: String, Decodable {
+ case text = "m.text"
+ case emote = "m.emote"
+ case notice = "m.notice"
+ case image = "m.image"
+ case file = "m.file"
+ case audio = "m.audio"
+ case location = "m.location"
+ case video = "m.video"
+ case unknown
+
+ public init(from decoder: Decoder) throws {
+ let container = try decoder.singleValueContainer()
+ self = MessageType(rawValue: (try? container.decode(String.self)) ?? "") ?? .unknown
+ }
+ }
+}
diff --git a/Sources/MatrixClient/API/Events/Room/RoomMessageEvent.swift b/Sources/MatrixClient/API/Events/Room/RoomMessageEvent.swift
new file mode 100644
index 0000000..9725d02
--- /dev/null
+++ b/Sources/MatrixClient/API/Events/Room/RoomMessageEvent.swift
@@ -0,0 +1,22 @@
+import Foundation
+import AnyCodable
+
+public struct RoomMessageEvent: RoomEvent {
+ public static var type = "m.room.message"
+
+ public let content: MessageContent
+ public let type: String
+ public let eventID: String
+ public let sender: String
+ public let date: Date
+ public let unsigned: AnyCodable?
+
+ enum CodingKeys: String, CodingKey {
+ case content
+ case type
+ case eventID = "event_id"
+ case sender
+ case date = "origin_server_ts"
+ case unsigned
+ }
+}
diff --git a/Sources/MatrixClient/API/Events/Room/RoomReactionEvent.swift b/Sources/MatrixClient/API/Events/Room/RoomReactionEvent.swift
new file mode 100644
index 0000000..d0e44cd
--- /dev/null
+++ b/Sources/MatrixClient/API/Events/Room/RoomReactionEvent.swift
@@ -0,0 +1,30 @@
+import Foundation
+import AnyCodable
+
+public struct RoomReactionEvent: RoomEvent {
+ public static var type = "m.reaction"
+
+ public let content: Content
+ public let type: String
+ public let eventID: String
+ public let sender: String
+ public let date: Date
+ public let unsigned: AnyCodable?
+
+ enum CodingKeys: String, CodingKey {
+ case content
+ case type
+ case eventID = "event_id"
+ case sender
+ case date = "origin_server_ts"
+ case unsigned
+ }
+
+ public struct Content: Decodable {
+ public let relationship: Relationship?
+
+ enum CodingKeys: String, CodingKey {
+ case relationship = "m.relates_to"
+ }
+ }
+}
diff --git a/Sources/MatrixClient/API/Events/Room/RoomRedactionEvent.swift b/Sources/MatrixClient/API/Events/Room/RoomRedactionEvent.swift
new file mode 100644
index 0000000..b0b0829
--- /dev/null
+++ b/Sources/MatrixClient/API/Events/Room/RoomRedactionEvent.swift
@@ -0,0 +1,29 @@
+import Foundation
+import AnyCodable
+
+public struct RoomRedactionEvent: RoomEvent {
+ public static var type = "m.room.redaction"
+
+ public let content: Content
+ public let type: String
+ public let eventID: String
+ public let sender: String
+ public let date: Date
+ public let unsigned: AnyCodable?
+
+ public let redacts: String?
+
+ enum CodingKeys: String, CodingKey {
+ case content
+ case type
+ case eventID = "event_id"
+ case sender
+ case date = "origin_server_ts"
+ case unsigned
+ case redacts
+ }
+
+ public struct Content: Decodable {
+ public let reason: String?
+ }
+}
diff --git a/Sources/MatrixClient/API/Events/RoomEvent.swift b/Sources/MatrixClient/API/Events/RoomEvent.swift
new file mode 100644
index 0000000..a2f2f14
--- /dev/null
+++ b/Sources/MatrixClient/API/Events/RoomEvent.swift
@@ -0,0 +1,92 @@
+import Foundation
+import AnyCodable
+
+/// A protocol that all room event structs should conform to in order
+/// to be decoded by the client. Don't forget to add any new structs
+/// to `Client.eventTypes` for included in the JSON decoder.
+public protocol RoomEvent: Decodable {
+ static var type: String { get }
+
+ // var content: Content { get }
+ var eventID: String { get }
+ var sender: String { get }
+ var date: Date { get }
+ var unsigned: AnyCodable? { get }
+}
+
+/// The coding keys needed to determine an event's type before decoding.
+enum RoomEventTypeKeys: CodingKey {
+ case type
+}
+
+enum RoomEventDecodableError: Error {
+ case missingTypes
+ case unableToFindType(String)
+ case unableToCast(decoded: RoomEvent?, into: String)
+}
+
+extension KeyedDecodingContainer {
+ // The synthesized decoding for RoomEventArray will throw if the key is missing. This fixes that.
+ func decode<T>(_ type: DecodableRoomEvents<T>.Type, forKey key: Self.Key) throws -> DecodableRoomEvents<T> {
+ return try decodeIfPresent(type, forKey: key) ?? DecodableRoomEvents<T>(wrappedValue: nil)
+ }
+}
+
+extension CodingUserInfoKey {
+ /// The key used to determing the types of `RoomEvent` that can be decoded.
+ static var roomEventTypes: CodingUserInfoKey {
+ CodingUserInfoKey(rawValue: "uk.pixlwave.RoomEventTypes")!
+ }
+}
+
+@propertyWrapper
+public struct DecodableRoomEvents<Value: Collection>: Decodable where Value.Element == RoomEvent {
+ public var wrappedValue: Value?
+
+ private struct RoomEventWrapper<T>: Decodable {
+ var wrappedEvent: T?
+
+ init(from decoder: Decoder) throws {
+ // these can throw as something has gone seriously wrong if the type key is missing
+ let container = try decoder.container(keyedBy: RoomEventTypeKeys.self)
+ let typeID = try container.decode(String.self, forKey: .type)
+
+ guard let types = decoder.userInfo[.roomEventTypes] as? [RoomEvent.Type] else {
+ // the decoder must be supplied with some event types to decode
+ throw RoomEventDecodableError.missingTypes
+ }
+
+ guard let matchingType = types.first(where: { $0.type == typeID }) else {
+ // simply ignore events with no matching type as throwing would prevent access to other events
+ return
+ }
+
+ guard let decoded = try? matchingType.init(from: decoder) else {
+ assertionFailure("Failed to decode RoomEvent as \(String(describing: T.self))")
+ return
+ }
+
+ guard let decoded = decoded as? T else {
+ // something has probably gone very wrong at this stage
+ throw RoomEventDecodableError.unableToCast(decoded: decoded, into: String(describing: T.self))
+ }
+
+ self.wrappedEvent = decoded
+ }
+ }
+
+
+ /// An initializer that allows initialization with a wrapped value of `nil`
+ /// to support arrays that may be excluded in the JSON responses.
+ public init(wrappedValue: Value?) {
+ self.wrappedValue = wrappedValue
+ }
+
+ public init(from decoder: Decoder) {
+ guard let container = try? decoder.singleValueContainer(),
+ let wrappers = try? container.decode([RoomEventWrapper<Value.Element>].self)
+ else { return }
+
+ wrappedValue = wrappers.compactMap(\.wrappedEvent) as? Value
+ }
+}
diff --git a/Sources/MatrixClient/API/Events/State/RoomEncryptionEvent.swift b/Sources/MatrixClient/API/Events/State/RoomEncryptionEvent.swift
new file mode 100644
index 0000000..033deb2
--- /dev/null
+++ b/Sources/MatrixClient/API/Events/State/RoomEncryptionEvent.swift
@@ -0,0 +1,28 @@
+import Foundation
+import AnyCodable
+
+public struct RoomEncryptionEvent: RoomEvent {
+ public static var type = "m.room.encryption"
+
+ public let content: Content
+ public let type: String
+ public let eventID: String
+ public let sender: String
+ public let date: Date
+ public let unsigned: AnyCodable?
+
+ public let stateKey: String?
+
+ enum CodingKeys: String, CodingKey {
+ case content
+ case type
+ case eventID = "event_id"
+ case sender
+ case date = "origin_server_ts"
+ case unsigned
+ case stateKey = "state_key"
+ }
+
+ public struct Content: Decodable { }
+}
+
diff --git a/Sources/MatrixClient/API/Events/State/RoomMemberEvent.swift b/Sources/MatrixClient/API/Events/State/RoomMemberEvent.swift
new file mode 100644
index 0000000..6ed777d
--- /dev/null
+++ b/Sources/MatrixClient/API/Events/State/RoomMemberEvent.swift
@@ -0,0 +1,39 @@
+import Foundation
+import AnyCodable
+
+public struct RoomMemberEvent: RoomEvent {
+ public static var type = "m.room.member"
+
+ public let content: Content
+ public let type: String
+ public let eventID: String
+ public let sender: String
+ public let date: Date
+ public let unsigned: AnyCodable?
+
+ public let stateKey: String?
+
+ enum CodingKeys: String, CodingKey {
+ case content
+ case type
+ case eventID = "event_id"
+ case sender
+ case date = "origin_server_ts"
+ case unsigned
+ case stateKey = "state_key"
+ }
+
+ public struct Content: Decodable {
+ public let avatarURL: String?
+ public let displayName: String?
+ public let membership: Membership?
+ public let isDirect: Bool?
+
+ enum CodingKeys: String, CodingKey {
+ case avatarURL = "avatar_url"
+ case displayName = "displayname"
+ case membership
+ case isDirect = "is_direct"
+ }
+ }
+}
diff --git a/Sources/MatrixClient/API/Events/State/RoomNameEvent.swift b/Sources/MatrixClient/API/Events/State/RoomNameEvent.swift
new file mode 100644
index 0000000..aaec37c
--- /dev/null
+++ b/Sources/MatrixClient/API/Events/State/RoomNameEvent.swift
@@ -0,0 +1,29 @@
+import Foundation
+import AnyCodable
+
+public struct RoomNameEvent: RoomEvent {
+ public static var type = "m.room.name"
+
+ public let content: Content
+ public let type: String
+ public let eventID: String
+ public let sender: String
+ public let date: Date
+ public let unsigned: AnyCodable?
+
+ public let stateKey: String?
+
+ enum CodingKeys: String, CodingKey {
+ case content
+ case type
+ case eventID = "event_id"
+ case sender
+ case date = "origin_server_ts"
+ case unsigned
+ case stateKey = "state_key"
+ }
+
+ public struct Content: Decodable {
+ public let name: String?
+ }
+}