From 770dd3b849bfa809e442a58e19f27a0d71c45710 Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Tue, 1 Aug 2023 12:04:11 -0700 Subject: [PATCH] Reply Content Type (#134) * add reply content type * add the public init * make content type id codable * trying to make it codecable * Get reply test passing * format * format * remove codable of content type id --------- Co-authored-by: Pat Nakajima --- Sources/XMTP/CodecRegistry.swift | 10 ++++ Sources/XMTP/Codecs/ContentCodec.swift | 4 ++ Sources/XMTP/Codecs/ContentTypeID.swift | 4 ++ Sources/XMTP/Codecs/ReplyCodec.swift | 62 +++++++++++++++++++++++++ Tests/XMTPTests/ReplyTests.swift | 43 +++++++++++++++++ XMTP.podspec | 2 +- 6 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 Sources/XMTP/Codecs/ReplyCodec.swift create mode 100644 Tests/XMTPTests/ReplyTests.swift 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 e55cb16d..bd0457b9 100644 --- a/Sources/XMTP/Codecs/ContentTypeID.swift +++ b/Sources/XMTP/Codecs/ContentTypeID.swift @@ -21,4 +21,8 @@ extension ContentTypeID { var id: String { "\(authorityID):\(typeID)" } + + var description: String { + "\(authorityID)/\(typeID):\(versionMajor).\(versionMinor)" + } } diff --git a/Sources/XMTP/Codecs/ReplyCodec.swift b/Sources/XMTP/Codecs/ReplyCodec.swift new file mode 100644 index 00000000..10149069 --- /dev/null +++ b/Sources/XMTP/Codecs/ReplyCodec.swift @@ -0,0 +1,62 @@ +// +// 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 { + public var reference: String + public var content: Any + public var contentType: ContentTypeID +} + +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 + } + } +} diff --git a/Tests/XMTPTests/ReplyTests.swift b/Tests/XMTPTests/ReplyTests.swift new file mode 100644 index 00000000..90a35728 --- /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) + } +} 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.