|
| 1 | +import AnyCodable |
| 2 | +import Foundation |
| 3 | + |
| 4 | +/// Enumeration that can represent any `Message` type. |
| 5 | +/// |
| 6 | +/// `AnyMessage` should be used in contexts when given a `Message`, but the exact type |
| 7 | +/// of the `Message` is unknown until runtime. |
| 8 | +/// |
| 9 | +/// Example: When calling an endpoint that returns `Message`s, but it's impossible to know exactly |
| 10 | +/// what kind of `Message` it is until the JSON response is parsed. |
| 11 | +public enum AnyMessage { |
| 12 | + case close(Close) |
| 13 | + case order(Order) |
| 14 | + case orderStatus(OrderStatus) |
| 15 | + case quote(Quote) |
| 16 | + case rfq(RFQ) |
| 17 | + |
| 18 | + /// Parse a JSON string into an `AnyMessage` object, which can represent any message type. |
| 19 | + /// - Parameter jsonString: A string containing a JSON representation of a `Message` |
| 20 | + /// - Returns: An `AnyMessage` object, representing the parsed JSON string |
| 21 | + public static func parse(_ jsonString: String) throws -> AnyMessage { |
| 22 | + guard let data = jsonString.data(using: .utf8) else { |
| 23 | + throw Error.invalidJSONString |
| 24 | + } |
| 25 | + |
| 26 | + return try tbDEXJSONDecoder().decode(AnyMessage.self, from: data) |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +// MARK: - Decodable |
| 31 | + |
| 32 | +extension AnyMessage: Decodable { |
| 33 | + |
| 34 | + public init(from decoder: Decoder) throws { |
| 35 | + let container = try decoder.singleValueContainer() |
| 36 | + |
| 37 | + // Read the JSON payload into a dictionary representation |
| 38 | + let messageJSONObject = try container.decode([String: AnyCodable].self) |
| 39 | + |
| 40 | + // Ensure that a metadata object is present within the JSON payload |
| 41 | + guard let metadataJSONObject = messageJSONObject["metadata"]?.value as? [String: Any] else { |
| 42 | + throw DecodingError.valueNotFound( |
| 43 | + AnyMessage.self, |
| 44 | + DecodingError.Context( |
| 45 | + codingPath: decoder.codingPath, |
| 46 | + debugDescription: "metadata not found" |
| 47 | + ) |
| 48 | + ) |
| 49 | + } |
| 50 | + |
| 51 | + // Decode the metadata into a strongly-typed `MessageMetadata` object |
| 52 | + let metadataData = try JSONSerialization.data(withJSONObject: metadataJSONObject) |
| 53 | + let metadata = try tbDEXJSONDecoder().decode(MessageMetadata.self, from: metadataData) |
| 54 | + |
| 55 | + // Decode the message itself into it's strongly-typed representation, indicated by the `metadata.kind` field |
| 56 | + switch metadata.kind { |
| 57 | + case .close: |
| 58 | + self = .close(try container.decode(Close.self)) |
| 59 | + case .order: |
| 60 | + self = .order(try container.decode(Order.self)) |
| 61 | + case .orderStatus: |
| 62 | + self = .orderStatus(try container.decode(OrderStatus.self)) |
| 63 | + case .quote: |
| 64 | + self = .quote(try container.decode(Quote.self)) |
| 65 | + case .rfq: |
| 66 | + self = .rfq(try container.decode(RFQ.self)) |
| 67 | + } |
| 68 | + } |
| 69 | +} |
| 70 | + |
| 71 | +// MARK: - Errors |
| 72 | + |
| 73 | +extension AnyMessage { |
| 74 | + |
| 75 | + public enum Error: Swift.Error { |
| 76 | + /// The provided JSON string is invalid |
| 77 | + case invalidJSONString |
| 78 | + } |
| 79 | +} |
0 commit comments