99 lines
2.7 KiB
Swift
99 lines
2.7 KiB
Swift
//
|
||
// 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. `{<URL>: <VALUE>, <URL>: <VALUE>}` instead of `[<URL>, <VALUE>, <URL>, <VALUE>]`, which is Swift's default for non-string-keyed dictionaries
|
||
|
||
public var codingKey: CodingKey {
|
||
return StringKey(stringValue: self.url.absoluteString)
|
||
}
|
||
|
||
public init?<T>(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 }
|
||
}
|