diff options
author | Doug <git@pixlwave.uk> | 2021-09-30 20:25:15 +0100 |
---|---|---|
committer | Doug <git@pixlwave.uk> | 2021-09-30 20:25:15 +0100 |
commit | 35c3fe43c3cfdfa5ff8efa3553dcb991c88626b0 (patch) | |
tree | 1066f3155e5c84eaa9f9d32f6611d5ca1e839ec7 | |
parent | 3993f4880d252a02b0031aa8594acf4ee7609631 (diff) | |
download | MatrixCore-35c3fe43c3cfdfa5ff8efa3553dcb991c88626b0.tar.gz MatrixCore-35c3fe43c3cfdfa5ff8efa3553dcb991c88626b0.tar.xz MatrixCore-35c3fe43c3cfdfa5ff8efa3553dcb991c88626b0.zip |
Add common events with DecodableRoomEvents property wrapper.
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? + } +} |