// // RelayURL.swift // damus // // Created by Daniel D’Aquino on 2023-09-29. // import Foundation public struct RelayURL: Hashable, Equatable, Codable, CodingKeyRepresentable, Identifiable, Comparable, CustomStringConvertible, Sendable { private(set) var url: URL public var id: URL { return url } public var description: String { return self.absoluteString } public var absoluteString: String { return url.absoluteString } init?(_ str: String) { var trimmed_url_str = str while trimmed_url_str.hasSuffix("/") { trimmed_url_str.removeLast() } guard let url = URL(string: trimmed_url_str) else { return nil } guard let scheme = url.scheme else { return nil } guard scheme == "ws" || scheme == "wss" else { return nil } self.url = url } // MARK: - Codable public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let urlString = try container.decode(String.self) guard let instance = RelayURL(urlString) else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid URL string.") } self = instance } public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(url.absoluteString) } // MARK: - CodingKeyRepresentable // CodingKeyRepresentable conformance is necessary to ensure that // a dictionary with type "[RelayURL: T] where T: Codable" can be encoded into a keyed container // e.g. `{: , : }` instead of `[, , , ]`, which is Swift's default for non-string-keyed dictionaries public var codingKey: CodingKey { return StringKey(stringValue: self.url.absoluteString) } public init?(codingKey: T) where T : CodingKey { self.init(codingKey.stringValue) } // MARK: - Equatable public static func == (lhs: RelayURL, rhs: RelayURL) -> Bool { return lhs.url == rhs.url } // MARK: - Hashable public func hash(into hasher: inout Hasher) { hasher.combine(self.url) } // MARK: - Comparable public static func < (lhs: RelayURL, rhs: RelayURL) -> Bool { return lhs.url.absoluteString < rhs.url.absoluteString } } private struct StringKey: CodingKey { var stringValue: String init(stringValue: String) { self.stringValue = stringValue } var intValue: Int? { return nil } init?(intValue: Int) { return nil } }