From a514921cbbc80ea06d44ecd98f9520c3cff1e901 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Wed, 26 Jul 2023 17:38:12 -0700 Subject: [PATCH 1/8] add reply content type --- Sources/XMTP/Codecs/ReplyCodec.swift | 35 ++++++++++++++++++++++ Tests/XMTPTests/ReplyTests.swift | 43 ++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 Sources/XMTP/Codecs/ReplyCodec.swift create mode 100644 Tests/XMTPTests/ReplyTests.swift diff --git a/Sources/XMTP/Codecs/ReplyCodec.swift b/Sources/XMTP/Codecs/ReplyCodec.swift new file mode 100644 index 00000000..1454e2c9 --- /dev/null +++ b/Sources/XMTP/Codecs/ReplyCodec.swift @@ -0,0 +1,35 @@ +// +// ReplyCodec.swift +// +// +// Created by Naomi Plasterer on 7/26/23. +// + +import Foundation + + +public let ContentTypeReply = ContentTypeID(authorityID: "xmtp.org", typeID: "reply", versionMajor: 1, versionMinor: 0) + +public struct Reply: Codable { + public var reference: String + public var content: Any + public var contentType: ContentTypeID +} + +public struct ReplyCodec: ContentCodec { + public var contentType = ContentTypeReply + + public func encode(content: Reply) throws -> EncodedContent { + var encodedContent = EncodedContent() + + encodedContent.type = ContentTypeReply + encodedContent.content = try JSONEncoder().encode(content) + + return encodedContent + } + + public func decode(content: EncodedContent) throws -> Reply { + let reply = try JSONDecoder().decode(Reply.self, from: content.content) + return reply + } +} diff --git a/Tests/XMTPTests/ReplyTests.swift b/Tests/XMTPTests/ReplyTests.swift new file mode 100644 index 00000000..4e00a791 --- /dev/null +++ b/Tests/XMTPTests/ReplyTests.swift @@ -0,0 +1,43 @@ +// +// ReplyTests.swift +// +// +// Created by Naomi Plasterer on 7/26/23. +// +import Foundation + +import XCTest +@testable import XMTP + +@available(iOS 15, *) +class ReplyTests: XCTestCase { + func testCanUseReplyCodec() async throws { + Client.register(codec: ReplyCodec()) + + let fixtures = await fixtures() + let conversation = try await fixtures.aliceClient.conversations.newConversation(with: fixtures.bobClient.address) + + try await conversation.send(text: "hey alice 2 bob") + + let messageToReply = try await conversation.messages()[0] + + let reply = Reply( + reference: messageToReply.id, + content: "Hello", + contentType: ContentTypeText + ) + + try await conversation.send( + content: reply, + options: .init(contentType: ContentTypeReply) + ) + + let updatedMessages = try await conversation.messages() + + let message = try await conversation.messages()[0] + let content: Reply = try message.content() + XCTAssertEqual("Hello", content.content as? String) + XCTAssertEqual(messageToReply.id, content.reference) + XCTAssertEqual(ContentTypeText, content.contentType) + } +} From 8238fbf7f5d9a8c635deb0e25e80e3cd9926e513 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Fri, 28 Jul 2023 13:11:16 -0700 Subject: [PATCH 2/8] add the public init --- Sources/XMTP/Codecs/ReplyCodec.swift | 3 +++ XMTP.podspec | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/XMTP/Codecs/ReplyCodec.swift b/Sources/XMTP/Codecs/ReplyCodec.swift index 1454e2c9..70ff707c 100644 --- a/Sources/XMTP/Codecs/ReplyCodec.swift +++ b/Sources/XMTP/Codecs/ReplyCodec.swift @@ -17,8 +17,11 @@ public struct Reply: Codable { } public struct ReplyCodec: ContentCodec { + public typealias T = Reply public var contentType = ContentTypeReply + public init() {} + public func encode(content: Reply) throws -> EncodedContent { var encodedContent = EncodedContent() diff --git a/XMTP.podspec b/XMTP.podspec index 11e92b3e..084473c1 100644 --- a/XMTP.podspec +++ b/XMTP.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |spec| # spec.name = "XMTP" - spec.version = "0.4.3-alpha0" + spec.version = "0.4.4-alpha0" spec.summary = "XMTP SDK Cocoapod" # This description is used to generate tags and improve search results. From ac8648613c8d785fa2e10419d318e569747b887a Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 31 Jul 2023 09:39:57 -0700 Subject: [PATCH 3/8] make content type id codable --- Sources/XMTP/Codecs/ContentTypeID.swift | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Sources/XMTP/Codecs/ContentTypeID.swift b/Sources/XMTP/Codecs/ContentTypeID.swift index e55cb16d..a0c73034 100644 --- a/Sources/XMTP/Codecs/ContentTypeID.swift +++ b/Sources/XMTP/Codecs/ContentTypeID.swift @@ -22,3 +22,28 @@ extension ContentTypeID { "\(authorityID):\(typeID)" } } + +extension ContentTypeID: Codable { + enum CodingKeys: CodingKey { + case authorityID, typeID, versionMajor, versionMinor + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(authorityID, forKey: .authorityID) + try container.encode(typeID, forKey: .typeID) + try container.encode(versionMajor, forKey: .versionMajor) + try container.encode(versionMinor, forKey: .versionMinor) + } + + public init(from decoder: Decoder) throws { + self.init() + + let container = try decoder.container(keyedBy: CodingKeys.self) + authorityID = try container.decode(String.self, forKey: .authorityID) + typeID = try container.decode(String.self, forKey: .typeID) + versionMajor = try container.decode(UInt32.self, forKey: .versionMajor) + versionMinor = try container.decode(UInt32.self, forKey: .versionMinor) + } +} From 92fc5f87f21a6c1f99a5d21665549206dd8bdbce Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Mon, 31 Jul 2023 09:51:42 -0700 Subject: [PATCH 4/8] trying to make it codecable --- Sources/XMTP/Codecs/ReplyCodec.swift | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Sources/XMTP/Codecs/ReplyCodec.swift b/Sources/XMTP/Codecs/ReplyCodec.swift index 70ff707c..702f1b5f 100644 --- a/Sources/XMTP/Codecs/ReplyCodec.swift +++ b/Sources/XMTP/Codecs/ReplyCodec.swift @@ -10,29 +10,31 @@ import Foundation public let ContentTypeReply = ContentTypeID(authorityID: "xmtp.org", typeID: "reply", versionMajor: 1, versionMinor: 0) -public struct Reply: Codable { +public struct Reply { public var reference: String - public var content: Any + public var content: T public var contentType: ContentTypeID } -public struct ReplyCodec: ContentCodec { - public typealias T = Reply +public struct ReplyCodec: ContentCodec { + public typealias T = Reply public var contentType = ContentTypeReply public init() {} - public func encode(content: Reply) throws -> EncodedContent { + public func encode(content: Reply) throws -> EncodedContent { var encodedContent = EncodedContent() encodedContent.type = ContentTypeReply - encodedContent.content = try JSONEncoder().encode(content) + let codec = Client.codecRegistry.find(for: contentType) + + encodedContent.content = try codec.encode(content: content).serializedData() return encodedContent } - public func decode(content: EncodedContent) throws -> Reply { - let reply = try JSONDecoder().decode(Reply.self, from: content.content) + public func decode(content: EncodedContent) throws -> Reply { + let reply = try JSONDecoder().decode(Reply.self, from: content.content) return reply } } From 13ae93e7e562ce50fcb578584eb2865c18a60df3 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Mon, 31 Jul 2023 11:19:03 -0700 Subject: [PATCH 5/8] Get reply test passing --- Sources/XMTP/CodecRegistry.swift | 10 ++++ Sources/XMTP/Codecs/ContentCodec.swift | 4 ++ Sources/XMTP/Codecs/ContentTypeID.swift | 4 ++ Sources/XMTP/Codecs/ReplyCodec.swift | 69 ++++++++++++++++--------- 4 files changed, 64 insertions(+), 23 deletions(-) diff --git a/Sources/XMTP/CodecRegistry.swift b/Sources/XMTP/CodecRegistry.swift index 49f6d231..d39f8688 100644 --- a/Sources/XMTP/CodecRegistry.swift +++ b/Sources/XMTP/CodecRegistry.swift @@ -25,4 +25,14 @@ struct CodecRegistry { return TextCodec() } + + func find(for contentTypeString: String) -> any ContentCodec { + for (_, codec) in codecs { + if codec.description == contentTypeString { + return codec + } + } + + return TextCodec() + } } diff --git a/Sources/XMTP/Codecs/ContentCodec.swift b/Sources/XMTP/Codecs/ContentCodec.swift index c8feac96..8036a5e2 100644 --- a/Sources/XMTP/Codecs/ContentCodec.swift +++ b/Sources/XMTP/Codecs/ContentCodec.swift @@ -85,4 +85,8 @@ public extension ContentCodec { func hash(into hasher: inout Hasher) { hasher.combine(id) } + + var description: String { + contentType.description + } } diff --git a/Sources/XMTP/Codecs/ContentTypeID.swift b/Sources/XMTP/Codecs/ContentTypeID.swift index a0c73034..98dcdaf8 100644 --- a/Sources/XMTP/Codecs/ContentTypeID.swift +++ b/Sources/XMTP/Codecs/ContentTypeID.swift @@ -21,6 +21,10 @@ extension ContentTypeID { var id: String { "\(authorityID):\(typeID)" } + + var description: String { + "\(authorityID)/\(typeID):\(versionMajor).\(versionMinor)" + } } extension ContentTypeID: Codable { diff --git a/Sources/XMTP/Codecs/ReplyCodec.swift b/Sources/XMTP/Codecs/ReplyCodec.swift index 702f1b5f..be2dad64 100644 --- a/Sources/XMTP/Codecs/ReplyCodec.swift +++ b/Sources/XMTP/Codecs/ReplyCodec.swift @@ -10,31 +10,54 @@ import Foundation public let ContentTypeReply = ContentTypeID(authorityID: "xmtp.org", typeID: "reply", versionMajor: 1, versionMinor: 0) -public struct Reply { +public struct Reply { public var reference: String - public var content: T + public var content: Any public var contentType: ContentTypeID } -public struct ReplyCodec: ContentCodec { - public typealias T = Reply - public var contentType = ContentTypeReply - - public init() {} - - public func encode(content: Reply) throws -> EncodedContent { - var encodedContent = EncodedContent() - - encodedContent.type = ContentTypeReply - let codec = Client.codecRegistry.find(for: contentType) - - encodedContent.content = try codec.encode(content: content).serializedData() - - return encodedContent - } - - public func decode(content: EncodedContent) throws -> Reply { - let reply = try JSONDecoder().decode(Reply.self, from: content.content) - return reply - } +public struct ReplyCodec: ContentCodec { + public var contentType = ContentTypeReply + + public init() {} + + public func encode(content reply: Reply) throws -> EncodedContent { + var encodedContent = EncodedContent() + let replyCodec = Client.codecRegistry.find(for: reply.contentType) + + encodedContent.type = contentType + encodedContent.parameters["contentType"] = reply.contentType.description + encodedContent.parameters["reference"] = reply.reference + encodedContent.content = try encodeReply(codec: replyCodec, content: reply.content).serializedData() + + return encodedContent + } + + public func decode(content: EncodedContent) throws -> Reply { + guard let contentTypeString = content.parameters["contentType"] else { + throw CodecError.codecNotFound + } + + guard let reference = content.parameters["reference"] else { + throw CodecError.invalidContent + } + + let replyEncodedContent = try EncodedContent(serializedData: content.content) + let replyCodec = Client.codecRegistry.find(for: contentTypeString) + let replyContent = try replyCodec.decode(content: replyEncodedContent) + + return Reply( + reference: reference, + content: replyContent, + contentType: replyCodec.contentType + ) + } + + func encodeReply(codec: Codec, content: Any) throws -> EncodedContent { + if let content = content as? Codec.T { + return try codec.encode(content: content) + } else { + throw CodecError.invalidContent + } + } } From e048b6dded95aee97f4346fed78243e6fc9d1e92 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Mon, 31 Jul 2023 11:41:13 -0700 Subject: [PATCH 6/8] format --- Sources/XMTP/Codecs/ReplyCodec.swift | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Sources/XMTP/Codecs/ReplyCodec.swift b/Sources/XMTP/Codecs/ReplyCodec.swift index be2dad64..10149069 100644 --- a/Sources/XMTP/Codecs/ReplyCodec.swift +++ b/Sources/XMTP/Codecs/ReplyCodec.swift @@ -7,17 +7,16 @@ import Foundation - public let ContentTypeReply = ContentTypeID(authorityID: "xmtp.org", typeID: "reply", versionMajor: 1, versionMinor: 0) public struct Reply { - public var reference: String - public var content: Any - public var contentType: ContentTypeID + public var reference: String + public var content: Any + public var contentType: ContentTypeID } public struct ReplyCodec: ContentCodec { - public var contentType = ContentTypeReply + public var contentType = ContentTypeReply public init() {} From dfb7fbaf50059bd8565b9747122e469bc6a580b2 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Mon, 31 Jul 2023 11:41:51 -0700 Subject: [PATCH 7/8] format --- Tests/XMTPTests/ReplyTests.swift | 44 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Tests/XMTPTests/ReplyTests.swift b/Tests/XMTPTests/ReplyTests.swift index 4e00a791..90a35728 100644 --- a/Tests/XMTPTests/ReplyTests.swift +++ b/Tests/XMTPTests/ReplyTests.swift @@ -11,33 +11,33 @@ import XCTest @available(iOS 15, *) class ReplyTests: XCTestCase { - func testCanUseReplyCodec() async throws { - Client.register(codec: ReplyCodec()) + func testCanUseReplyCodec() async throws { + Client.register(codec: ReplyCodec()) - let fixtures = await fixtures() - let conversation = try await fixtures.aliceClient.conversations.newConversation(with: fixtures.bobClient.address) + let fixtures = await fixtures() + let conversation = try await fixtures.aliceClient.conversations.newConversation(with: fixtures.bobClient.address) - try await conversation.send(text: "hey alice 2 bob") + try await conversation.send(text: "hey alice 2 bob") - let messageToReply = try await conversation.messages()[0] + let messageToReply = try await conversation.messages()[0] - let reply = Reply( - reference: messageToReply.id, - content: "Hello", - contentType: ContentTypeText - ) + let reply = Reply( + reference: messageToReply.id, + content: "Hello", + contentType: ContentTypeText + ) - try await conversation.send( - content: reply, - options: .init(contentType: ContentTypeReply) - ) + try await conversation.send( + content: reply, + options: .init(contentType: ContentTypeReply) + ) - let updatedMessages = try await conversation.messages() + let updatedMessages = try await conversation.messages() - let message = try await conversation.messages()[0] - let content: Reply = try message.content() - XCTAssertEqual("Hello", content.content as? String) - XCTAssertEqual(messageToReply.id, content.reference) - XCTAssertEqual(ContentTypeText, content.contentType) - } + let message = try await conversation.messages()[0] + let content: Reply = try message.content() + XCTAssertEqual("Hello", content.content as? String) + XCTAssertEqual(messageToReply.id, content.reference) + XCTAssertEqual(ContentTypeText, content.contentType) + } } From cabfedde043728001160ca4cb056047939158da6 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Tue, 1 Aug 2023 11:58:33 -0700 Subject: [PATCH 8/8] remove codable of content type id --- Sources/XMTP/Codecs/ContentTypeID.swift | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/Sources/XMTP/Codecs/ContentTypeID.swift b/Sources/XMTP/Codecs/ContentTypeID.swift index 98dcdaf8..bd0457b9 100644 --- a/Sources/XMTP/Codecs/ContentTypeID.swift +++ b/Sources/XMTP/Codecs/ContentTypeID.swift @@ -26,28 +26,3 @@ extension ContentTypeID { "\(authorityID)/\(typeID):\(versionMajor).\(versionMinor)" } } - -extension ContentTypeID: Codable { - enum CodingKeys: CodingKey { - case authorityID, typeID, versionMajor, versionMinor - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(authorityID, forKey: .authorityID) - try container.encode(typeID, forKey: .typeID) - try container.encode(versionMajor, forKey: .versionMajor) - try container.encode(versionMinor, forKey: .versionMinor) - } - - public init(from decoder: Decoder) throws { - self.init() - - let container = try decoder.container(keyedBy: CodingKeys.self) - authorityID = try container.decode(String.self, forKey: .authorityID) - typeID = try container.decode(String.self, forKey: .typeID) - versionMajor = try container.decode(UInt32.self, forKey: .versionMajor) - versionMinor = try container.decode(UInt32.self, forKey: .versionMinor) - } -}