Allow non-string values in profiles
Profiles were not loading from other clients because some fields were not strings Changelog-Fixed: Fixed profiles sometimes not loading from other clients
This commit is contained in:
@@ -166,6 +166,9 @@
|
|||||||
4CF0ABE12981A83900D66079 /* MutelistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE02981A83900D66079 /* MutelistView.swift */; };
|
4CF0ABE12981A83900D66079 /* MutelistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE02981A83900D66079 /* MutelistView.swift */; };
|
||||||
4CF0ABE32981BC7D00D66079 /* UserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE22981BC7D00D66079 /* UserView.swift */; };
|
4CF0ABE32981BC7D00D66079 /* UserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE22981BC7D00D66079 /* UserView.swift */; };
|
||||||
4CF0ABE52981EE0C00D66079 /* EULAView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE42981EE0C00D66079 /* EULAView.swift */; };
|
4CF0ABE52981EE0C00D66079 /* EULAView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE42981EE0C00D66079 /* EULAView.swift */; };
|
||||||
|
4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABE829844AF100D66079 /* AnyCodable.swift */; };
|
||||||
|
4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEB29844B4700D66079 /* AnyDecodable.swift */; };
|
||||||
|
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABED29844B5500D66079 /* AnyEncodable.swift */; };
|
||||||
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
|
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
|
||||||
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfileZoomView.swift */; };
|
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfileZoomView.swift */; };
|
||||||
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; };
|
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; };
|
||||||
@@ -419,6 +422,9 @@
|
|||||||
4CF0ABE02981A83900D66079 /* MutelistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutelistView.swift; sourceTree = "<group>"; };
|
4CF0ABE02981A83900D66079 /* MutelistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutelistView.swift; sourceTree = "<group>"; };
|
||||||
4CF0ABE22981BC7D00D66079 /* UserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserView.swift; sourceTree = "<group>"; };
|
4CF0ABE22981BC7D00D66079 /* UserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserView.swift; sourceTree = "<group>"; };
|
||||||
4CF0ABE42981EE0C00D66079 /* EULAView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EULAView.swift; sourceTree = "<group>"; };
|
4CF0ABE42981EE0C00D66079 /* EULAView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EULAView.swift; sourceTree = "<group>"; };
|
||||||
|
4CF0ABE829844AF100D66079 /* AnyCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyCodable.swift; sourceTree = "<group>"; };
|
||||||
|
4CF0ABEB29844B4700D66079 /* AnyDecodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyDecodable.swift; sourceTree = "<group>"; };
|
||||||
|
4CF0ABED29844B5500D66079 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = "<group>"; };
|
||||||
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
|
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
|
||||||
6439E013296790CF0020672B /* ProfileZoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZoomView.swift; sourceTree = "<group>"; };
|
6439E013296790CF0020672B /* ProfileZoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZoomView.swift; sourceTree = "<group>"; };
|
||||||
647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; };
|
647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; };
|
||||||
@@ -662,6 +668,7 @@
|
|||||||
4C7FF7D628233637009601DB /* Util */ = {
|
4C7FF7D628233637009601DB /* Util */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4CF0ABEA29844B2F00D66079 /* AnyCodable */,
|
||||||
4C3A1D322960DB0500558C0F /* Markdown.swift */,
|
4C3A1D322960DB0500558C0F /* Markdown.swift */,
|
||||||
4CEE2AF4280B29E600AB5EEF /* TimeAgo.swift */,
|
4CEE2AF4280B29E600AB5EEF /* TimeAgo.swift */,
|
||||||
4CE4F8CC281352B30009DFBB /* Notifications.swift */,
|
4CE4F8CC281352B30009DFBB /* Notifications.swift */,
|
||||||
@@ -824,6 +831,16 @@
|
|||||||
path = Muting;
|
path = Muting;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4CF0ABEA29844B2F00D66079 /* AnyCodable */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4CF0ABE829844AF100D66079 /* AnyCodable.swift */,
|
||||||
|
4CF0ABEB29844B4700D66079 /* AnyDecodable.swift */,
|
||||||
|
4CF0ABED29844B5500D66079 /* AnyEncodable.swift */,
|
||||||
|
);
|
||||||
|
path = AnyCodable;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
F7F0BA23297892AE009531F3 /* Modifiers */ = {
|
F7F0BA23297892AE009531F3 /* Modifiers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1003,6 +1020,7 @@
|
|||||||
4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */,
|
4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */,
|
||||||
4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */,
|
4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */,
|
||||||
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
|
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
|
||||||
|
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */,
|
||||||
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */,
|
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */,
|
||||||
4C3EA67728FF7A9800C48A62 /* talstr.c in Sources */,
|
4C3EA67728FF7A9800C48A62 /* talstr.c in Sources */,
|
||||||
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
|
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
|
||||||
@@ -1053,6 +1071,7 @@
|
|||||||
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
|
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
|
||||||
4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */,
|
4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */,
|
||||||
7C45AE71297353390031D7BC /* KFImageModel.swift in Sources */,
|
7C45AE71297353390031D7BC /* KFImageModel.swift in Sources */,
|
||||||
|
4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */,
|
||||||
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
|
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
|
||||||
4CC7AAF2297F129C00430951 /* EmbeddedEventView.swift in Sources */,
|
4CC7AAF2297F129C00430951 /* EmbeddedEventView.swift in Sources */,
|
||||||
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
|
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
|
||||||
@@ -1122,6 +1141,7 @@
|
|||||||
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
||||||
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
|
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
|
||||||
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */,
|
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */,
|
||||||
|
4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */,
|
||||||
4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */,
|
4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */,
|
||||||
4C3EA67D28FFBBA300C48A62 /* InvoicesView.swift in Sources */,
|
4C3EA67D28FFBBA300C48A62 /* InvoicesView.swift in Sources */,
|
||||||
4C363A8E28236FE4006E126D /* NoteContentView.swift in Sources */,
|
4C363A8E28236FE4006E126D /* NoteContentView.swift in Sources */,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct Profile: Codable {
|
struct Profile: Codable {
|
||||||
var value: [String: String]
|
var value: [String: AnyCodable]
|
||||||
|
|
||||||
init (name: String?, display_name: String?, about: String?, picture: String?, banner: String?, website: String?, lud06: String?, lud16: String?, nip05: String?) {
|
init (name: String?, display_name: String?, about: String?, picture: String?, banner: String?, website: String?, lud06: String?, lud16: String?, nip05: String?) {
|
||||||
self.value = [:]
|
self.value = [:]
|
||||||
@@ -23,50 +23,71 @@ struct Profile: Codable {
|
|||||||
self.nip05 = nip05
|
self.nip05 = nip05
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func str(_ str: String) -> String? {
|
||||||
|
guard let val = self.value[str] else{
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let s = val.value as? String else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
private mutating func set_str(_ key: String, _ val: String?) {
|
||||||
|
if val == nil {
|
||||||
|
self.value.removeValue(forKey: key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.value[key] = AnyCodable.init(val)
|
||||||
|
}
|
||||||
|
|
||||||
var display_name: String? {
|
var display_name: String? {
|
||||||
get { return value["display_name"]; }
|
get { return str("display_name"); }
|
||||||
set(s) { value["display_name"] = s }
|
set(s) { set_str("display_name", s) }
|
||||||
}
|
}
|
||||||
|
|
||||||
var name: String? {
|
var name: String? {
|
||||||
get { return value["name"]; }
|
get { return str("name"); }
|
||||||
set(s) { value["name"] = s }
|
set(s) { set_str("name", s) }
|
||||||
}
|
}
|
||||||
|
|
||||||
var about: String? {
|
var about: String? {
|
||||||
get { return value["about"]; }
|
get { return str("about"); }
|
||||||
set(s) { value["about"] = s }
|
set(s) { set_str("about", s) }
|
||||||
}
|
}
|
||||||
|
|
||||||
var picture: String? {
|
var picture: String? {
|
||||||
get { return value["picture"]; }
|
get { return str("picture"); }
|
||||||
set(s) { value["picture"] = s }
|
set(s) { set_str("picture", s) }
|
||||||
}
|
}
|
||||||
|
|
||||||
var banner: String? {
|
var banner: String? {
|
||||||
get { return value["banner"]; }
|
get { return str("banner"); }
|
||||||
set(s) { value["banner"] = s }
|
set(s) { set_str("banner", s) }
|
||||||
}
|
}
|
||||||
|
|
||||||
var website: String? {
|
var website: String? {
|
||||||
get { return value["website"]; }
|
get { return str("website"); }
|
||||||
set(s) { value["website"] = s }
|
set(s) { set_str("website", s) }
|
||||||
|
}
|
||||||
|
|
||||||
|
var lud06: String? {
|
||||||
|
get { return str("lud06"); }
|
||||||
|
set(s) { set_str("lud06", s) }
|
||||||
|
}
|
||||||
|
|
||||||
|
var lud16: String? {
|
||||||
|
get { return str("lud16"); }
|
||||||
|
set(s) { set_str("lud16", s) }
|
||||||
}
|
}
|
||||||
|
|
||||||
var website_url: URL? {
|
var website_url: URL? {
|
||||||
return self.website.flatMap { URL(string: $0) }
|
return self.website.flatMap { URL(string: $0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
var lud06: String? {
|
|
||||||
get { return value["lud06"]; }
|
|
||||||
set(s) { value["lud06"] = s }
|
|
||||||
}
|
|
||||||
|
|
||||||
var lud16: String? {
|
|
||||||
get { return value["lud16"]; }
|
|
||||||
set(s) { value["lud16"] = s }
|
|
||||||
}
|
|
||||||
|
|
||||||
var lnurl: String? {
|
var lnurl: String? {
|
||||||
guard let addr = lud06 ?? lud16 else {
|
guard let addr = lud06 ?? lud16 else {
|
||||||
return nil;
|
return nil;
|
||||||
@@ -80,8 +101,8 @@ struct Profile: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var nip05: String? {
|
var nip05: String? {
|
||||||
get { return value["nip05"]; }
|
get { return str("nip05"); }
|
||||||
set(s) { value["nip05"] = s }
|
set(s) { set_str("nip05", s) }
|
||||||
}
|
}
|
||||||
|
|
||||||
var lightning_uri: URL? {
|
var lightning_uri: URL? {
|
||||||
@@ -90,7 +111,7 @@ struct Profile: Codable {
|
|||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.singleValueContainer()
|
let container = try decoder.singleValueContainer()
|
||||||
self.value = try container.decode([String: String].self)
|
self.value = try container.decode([String: AnyCodable].self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func encode(to encoder: Encoder) throws {
|
func encode(to encoder: Encoder) throws {
|
||||||
|
|||||||
147
damus/Util/AnyCodable/AnyCodable.swift
Normal file
147
damus/Util/AnyCodable/AnyCodable.swift
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import Foundation
|
||||||
|
/**
|
||||||
|
A type-erased `Codable` value.
|
||||||
|
|
||||||
|
The `AnyCodable` type forwards encoding and decoding responsibilities
|
||||||
|
to an underlying value, hiding its specific underlying type.
|
||||||
|
|
||||||
|
You can encode or decode mixed-type values in dictionaries
|
||||||
|
and other collections that require `Encodable` or `Decodable` conformance
|
||||||
|
by declaring their contained type to be `AnyCodable`.
|
||||||
|
|
||||||
|
- SeeAlso: `AnyEncodable`
|
||||||
|
- SeeAlso: `AnyDecodable`
|
||||||
|
*/
|
||||||
|
@frozen public struct AnyCodable: Codable {
|
||||||
|
public let value: Any
|
||||||
|
|
||||||
|
public init<T>(_ value: T?) {
|
||||||
|
self.value = value ?? ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyCodable: _AnyEncodable, _AnyDecodable {}
|
||||||
|
|
||||||
|
extension AnyCodable: Equatable {
|
||||||
|
public static func == (lhs: AnyCodable, rhs: AnyCodable) -> Bool {
|
||||||
|
switch (lhs.value, rhs.value) {
|
||||||
|
case is (Void, Void):
|
||||||
|
return true
|
||||||
|
case let (lhs as Bool, rhs as Bool):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int, rhs as Int):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int8, rhs as Int8):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int16, rhs as Int16):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int32, rhs as Int32):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int64, rhs as Int64):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt, rhs as UInt):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt8, rhs as UInt8):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt16, rhs as UInt16):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt32, rhs as UInt32):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt64, rhs as UInt64):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Float, rhs as Float):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Double, rhs as Double):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as String, rhs as String):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as [String: AnyCodable], rhs as [String: AnyCodable]):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as [AnyCodable], rhs as [AnyCodable]):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as [String: Any], rhs as [String: Any]):
|
||||||
|
return NSDictionary(dictionary: lhs) == NSDictionary(dictionary: rhs)
|
||||||
|
case let (lhs as [Any], rhs as [Any]):
|
||||||
|
return NSArray(array: lhs) == NSArray(array: rhs)
|
||||||
|
case is (NSNull, NSNull):
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyCodable: CustomStringConvertible {
|
||||||
|
public var description: String {
|
||||||
|
switch value {
|
||||||
|
case is Void:
|
||||||
|
return String(describing: nil as Any?)
|
||||||
|
case let value as CustomStringConvertible:
|
||||||
|
return value.description
|
||||||
|
default:
|
||||||
|
return String(describing: value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyCodable: CustomDebugStringConvertible {
|
||||||
|
public var debugDescription: String {
|
||||||
|
switch value {
|
||||||
|
case let value as CustomDebugStringConvertible:
|
||||||
|
return "AnyCodable(\(value.debugDescription))"
|
||||||
|
default:
|
||||||
|
return "AnyCodable(\(description))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyCodable: ExpressibleByNilLiteral {}
|
||||||
|
extension AnyCodable: ExpressibleByBooleanLiteral {}
|
||||||
|
extension AnyCodable: ExpressibleByIntegerLiteral {}
|
||||||
|
extension AnyCodable: ExpressibleByFloatLiteral {}
|
||||||
|
extension AnyCodable: ExpressibleByStringLiteral {}
|
||||||
|
extension AnyCodable: ExpressibleByStringInterpolation {}
|
||||||
|
extension AnyCodable: ExpressibleByArrayLiteral {}
|
||||||
|
extension AnyCodable: ExpressibleByDictionaryLiteral {}
|
||||||
|
|
||||||
|
|
||||||
|
extension AnyCodable: Hashable {
|
||||||
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
switch value {
|
||||||
|
case let value as Bool:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Int:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Int8:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Int16:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Int32:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Int64:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as UInt:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as UInt8:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as UInt16:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as UInt32:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as UInt64:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Float:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Double:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as String:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as [String: AnyCodable]:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as [AnyCodable]:
|
||||||
|
hasher.combine(value)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
188
damus/Util/AnyCodable/AnyDecodable.swift
Normal file
188
damus/Util/AnyCodable/AnyDecodable.swift
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
#if canImport(Foundation)
|
||||||
|
import Foundation
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
A type-erased `Decodable` value.
|
||||||
|
|
||||||
|
The `AnyDecodable` type forwards decoding responsibilities
|
||||||
|
to an underlying value, hiding its specific underlying type.
|
||||||
|
|
||||||
|
You can decode mixed-type values in dictionaries
|
||||||
|
and other collections that require `Decodable` conformance
|
||||||
|
by declaring their contained type to be `AnyDecodable`:
|
||||||
|
|
||||||
|
let json = """
|
||||||
|
{
|
||||||
|
"boolean": true,
|
||||||
|
"integer": 42,
|
||||||
|
"double": 3.141592653589793,
|
||||||
|
"string": "string",
|
||||||
|
"array": [1, 2, 3],
|
||||||
|
"nested": {
|
||||||
|
"a": "alpha",
|
||||||
|
"b": "bravo",
|
||||||
|
"c": "charlie"
|
||||||
|
},
|
||||||
|
"null": null
|
||||||
|
}
|
||||||
|
""".data(using: .utf8)!
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
let dictionary = try! decoder.decode([String: AnyDecodable].self, from: json)
|
||||||
|
*/
|
||||||
|
@frozen public struct AnyDecodable: Decodable {
|
||||||
|
public let value: Any
|
||||||
|
|
||||||
|
public init<T>(_ value: T?) {
|
||||||
|
self.value = value ?? ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@usableFromInline
|
||||||
|
protocol _AnyDecodable {
|
||||||
|
var value: Any { get }
|
||||||
|
init<T>(_ value: T?)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyDecodable: _AnyDecodable {}
|
||||||
|
|
||||||
|
extension _AnyDecodable {
|
||||||
|
public init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
|
||||||
|
if container.decodeNil() {
|
||||||
|
#if canImport(Foundation)
|
||||||
|
self.init(NSNull())
|
||||||
|
#else
|
||||||
|
self.init(Optional<Self>.none)
|
||||||
|
#endif
|
||||||
|
} else if let bool = try? container.decode(Bool.self) {
|
||||||
|
self.init(bool)
|
||||||
|
} else if let int = try? container.decode(Int.self) {
|
||||||
|
self.init(int)
|
||||||
|
} else if let uint = try? container.decode(UInt.self) {
|
||||||
|
self.init(uint)
|
||||||
|
} else if let double = try? container.decode(Double.self) {
|
||||||
|
self.init(double)
|
||||||
|
} else if let string = try? container.decode(String.self) {
|
||||||
|
self.init(string)
|
||||||
|
} else if let array = try? container.decode([AnyDecodable].self) {
|
||||||
|
self.init(array.map { $0.value })
|
||||||
|
} else if let dictionary = try? container.decode([String: AnyDecodable].self) {
|
||||||
|
self.init(dictionary.mapValues { $0.value })
|
||||||
|
} else {
|
||||||
|
throw DecodingError.dataCorruptedError(in: container, debugDescription: "AnyDecodable value cannot be decoded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyDecodable: Equatable {
|
||||||
|
public static func == (lhs: AnyDecodable, rhs: AnyDecodable) -> Bool {
|
||||||
|
switch (lhs.value, rhs.value) {
|
||||||
|
#if canImport(Foundation)
|
||||||
|
case is (NSNull, NSNull), is (Void, Void):
|
||||||
|
return true
|
||||||
|
#endif
|
||||||
|
case let (lhs as Bool, rhs as Bool):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int, rhs as Int):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int8, rhs as Int8):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int16, rhs as Int16):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int32, rhs as Int32):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int64, rhs as Int64):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt, rhs as UInt):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt8, rhs as UInt8):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt16, rhs as UInt16):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt32, rhs as UInt32):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt64, rhs as UInt64):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Float, rhs as Float):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Double, rhs as Double):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as String, rhs as String):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as [String: AnyDecodable], rhs as [String: AnyDecodable]):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as [AnyDecodable], rhs as [AnyDecodable]):
|
||||||
|
return lhs == rhs
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyDecodable: CustomStringConvertible {
|
||||||
|
public var description: String {
|
||||||
|
switch value {
|
||||||
|
case is Void:
|
||||||
|
return String(describing: nil as Any?)
|
||||||
|
case let value as CustomStringConvertible:
|
||||||
|
return value.description
|
||||||
|
default:
|
||||||
|
return String(describing: value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyDecodable: CustomDebugStringConvertible {
|
||||||
|
public var debugDescription: String {
|
||||||
|
switch value {
|
||||||
|
case let value as CustomDebugStringConvertible:
|
||||||
|
return "AnyDecodable(\(value.debugDescription))"
|
||||||
|
default:
|
||||||
|
return "AnyDecodable(\(description))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyDecodable: Hashable {
|
||||||
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
switch value {
|
||||||
|
case let value as Bool:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Int:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Int8:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Int16:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Int32:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Int64:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as UInt:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as UInt8:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as UInt16:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as UInt32:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as UInt64:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Float:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Double:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as String:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as [String: AnyDecodable]:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as [AnyDecodable]:
|
||||||
|
hasher.combine(value)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
291
damus/Util/AnyCodable/AnyEncodable.swift
Normal file
291
damus/Util/AnyCodable/AnyEncodable.swift
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
#if canImport(Foundation)
|
||||||
|
import Foundation
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
A type-erased `Encodable` value.
|
||||||
|
|
||||||
|
The `AnyEncodable` type forwards encoding responsibilities
|
||||||
|
to an underlying value, hiding its specific underlying type.
|
||||||
|
|
||||||
|
You can encode mixed-type values in dictionaries
|
||||||
|
and other collections that require `Encodable` conformance
|
||||||
|
by declaring their contained type to be `AnyEncodable`:
|
||||||
|
|
||||||
|
let dictionary: [String: AnyEncodable] = [
|
||||||
|
"boolean": true,
|
||||||
|
"integer": 42,
|
||||||
|
"double": 3.141592653589793,
|
||||||
|
"string": "string",
|
||||||
|
"array": [1, 2, 3],
|
||||||
|
"nested": [
|
||||||
|
"a": "alpha",
|
||||||
|
"b": "bravo",
|
||||||
|
"c": "charlie"
|
||||||
|
],
|
||||||
|
"null": nil
|
||||||
|
]
|
||||||
|
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
let json = try! encoder.encode(dictionary)
|
||||||
|
*/
|
||||||
|
@frozen public struct AnyEncodable: Encodable {
|
||||||
|
public let value: Any
|
||||||
|
|
||||||
|
public init<T>(_ value: T?) {
|
||||||
|
self.value = value ?? ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@usableFromInline
|
||||||
|
protocol _AnyEncodable {
|
||||||
|
var value: Any { get }
|
||||||
|
init<T>(_ value: T?)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyEncodable: _AnyEncodable {}
|
||||||
|
|
||||||
|
// MARK: - Encodable
|
||||||
|
|
||||||
|
extension _AnyEncodable {
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.singleValueContainer()
|
||||||
|
|
||||||
|
switch value {
|
||||||
|
#if canImport(Foundation)
|
||||||
|
case is NSNull:
|
||||||
|
try container.encodeNil()
|
||||||
|
#endif
|
||||||
|
case is Void:
|
||||||
|
try container.encodeNil()
|
||||||
|
case let bool as Bool:
|
||||||
|
try container.encode(bool)
|
||||||
|
case let int as Int:
|
||||||
|
try container.encode(int)
|
||||||
|
case let int8 as Int8:
|
||||||
|
try container.encode(int8)
|
||||||
|
case let int16 as Int16:
|
||||||
|
try container.encode(int16)
|
||||||
|
case let int32 as Int32:
|
||||||
|
try container.encode(int32)
|
||||||
|
case let int64 as Int64:
|
||||||
|
try container.encode(int64)
|
||||||
|
case let uint as UInt:
|
||||||
|
try container.encode(uint)
|
||||||
|
case let uint8 as UInt8:
|
||||||
|
try container.encode(uint8)
|
||||||
|
case let uint16 as UInt16:
|
||||||
|
try container.encode(uint16)
|
||||||
|
case let uint32 as UInt32:
|
||||||
|
try container.encode(uint32)
|
||||||
|
case let uint64 as UInt64:
|
||||||
|
try container.encode(uint64)
|
||||||
|
case let float as Float:
|
||||||
|
try container.encode(float)
|
||||||
|
case let double as Double:
|
||||||
|
try container.encode(double)
|
||||||
|
case let string as String:
|
||||||
|
try container.encode(string)
|
||||||
|
#if canImport(Foundation)
|
||||||
|
case let number as NSNumber:
|
||||||
|
try encode(nsnumber: number, into: &container)
|
||||||
|
case let date as Date:
|
||||||
|
try container.encode(date)
|
||||||
|
case let url as URL:
|
||||||
|
try container.encode(url)
|
||||||
|
#endif
|
||||||
|
case let array as [Any?]:
|
||||||
|
try container.encode(array.map { AnyEncodable($0) })
|
||||||
|
case let dictionary as [String: Any?]:
|
||||||
|
try container.encode(dictionary.mapValues { AnyEncodable($0) })
|
||||||
|
case let encodable as Encodable:
|
||||||
|
try encodable.encode(to: encoder)
|
||||||
|
default:
|
||||||
|
let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "AnyEncodable value cannot be encoded")
|
||||||
|
throw EncodingError.invalidValue(value, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if canImport(Foundation)
|
||||||
|
private func encode(nsnumber: NSNumber, into container: inout SingleValueEncodingContainer) throws {
|
||||||
|
switch Character(Unicode.Scalar(UInt8(nsnumber.objCType.pointee))) {
|
||||||
|
case "B":
|
||||||
|
try container.encode(nsnumber.boolValue)
|
||||||
|
case "c":
|
||||||
|
try container.encode(nsnumber.int8Value)
|
||||||
|
case "s":
|
||||||
|
try container.encode(nsnumber.int16Value)
|
||||||
|
case "i", "l":
|
||||||
|
try container.encode(nsnumber.int32Value)
|
||||||
|
case "q":
|
||||||
|
try container.encode(nsnumber.int64Value)
|
||||||
|
case "C":
|
||||||
|
try container.encode(nsnumber.uint8Value)
|
||||||
|
case "S":
|
||||||
|
try container.encode(nsnumber.uint16Value)
|
||||||
|
case "I", "L":
|
||||||
|
try container.encode(nsnumber.uint32Value)
|
||||||
|
case "Q":
|
||||||
|
try container.encode(nsnumber.uint64Value)
|
||||||
|
case "f":
|
||||||
|
try container.encode(nsnumber.floatValue)
|
||||||
|
case "d":
|
||||||
|
try container.encode(nsnumber.doubleValue)
|
||||||
|
default:
|
||||||
|
let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "NSNumber cannot be encoded because its type is not handled")
|
||||||
|
throw EncodingError.invalidValue(nsnumber, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyEncodable: Equatable {
|
||||||
|
public static func == (lhs: AnyEncodable, rhs: AnyEncodable) -> Bool {
|
||||||
|
switch (lhs.value, rhs.value) {
|
||||||
|
case is (Void, Void):
|
||||||
|
return true
|
||||||
|
case let (lhs as Bool, rhs as Bool):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int, rhs as Int):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int8, rhs as Int8):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int16, rhs as Int16):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int32, rhs as Int32):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int64, rhs as Int64):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt, rhs as UInt):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt8, rhs as UInt8):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt16, rhs as UInt16):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt32, rhs as UInt32):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt64, rhs as UInt64):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Float, rhs as Float):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Double, rhs as Double):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as String, rhs as String):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as [String: AnyEncodable], rhs as [String: AnyEncodable]):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as [AnyEncodable], rhs as [AnyEncodable]):
|
||||||
|
return lhs == rhs
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyEncodable: CustomStringConvertible {
|
||||||
|
public var description: String {
|
||||||
|
switch value {
|
||||||
|
case is Void:
|
||||||
|
return String(describing: nil as Any?)
|
||||||
|
case let value as CustomStringConvertible:
|
||||||
|
return value.description
|
||||||
|
default:
|
||||||
|
return String(describing: value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyEncodable: CustomDebugStringConvertible {
|
||||||
|
public var debugDescription: String {
|
||||||
|
switch value {
|
||||||
|
case let value as CustomDebugStringConvertible:
|
||||||
|
return "AnyEncodable(\(value.debugDescription))"
|
||||||
|
default:
|
||||||
|
return "AnyEncodable(\(description))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyEncodable: ExpressibleByNilLiteral {}
|
||||||
|
extension AnyEncodable: ExpressibleByBooleanLiteral {}
|
||||||
|
extension AnyEncodable: ExpressibleByIntegerLiteral {}
|
||||||
|
extension AnyEncodable: ExpressibleByFloatLiteral {}
|
||||||
|
extension AnyEncodable: ExpressibleByStringLiteral {}
|
||||||
|
extension AnyEncodable: ExpressibleByStringInterpolation {}
|
||||||
|
extension AnyEncodable: ExpressibleByArrayLiteral {}
|
||||||
|
extension AnyEncodable: ExpressibleByDictionaryLiteral {}
|
||||||
|
|
||||||
|
extension _AnyEncodable {
|
||||||
|
public init(nilLiteral _: ()) {
|
||||||
|
self.init(nil as Any?)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(booleanLiteral value: Bool) {
|
||||||
|
self.init(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(integerLiteral value: Int) {
|
||||||
|
self.init(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(floatLiteral value: Double) {
|
||||||
|
self.init(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(extendedGraphemeClusterLiteral value: String) {
|
||||||
|
self.init(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(stringLiteral value: String) {
|
||||||
|
self.init(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(arrayLiteral elements: Any...) {
|
||||||
|
self.init(elements)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(dictionaryLiteral elements: (AnyHashable, Any)...) {
|
||||||
|
self.init([AnyHashable: Any](elements, uniquingKeysWith: { first, _ in first }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyEncodable: Hashable {
|
||||||
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
switch value {
|
||||||
|
case let value as Bool:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Int:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Int8:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Int16:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Int32:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Int64:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as UInt:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as UInt8:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as UInt16:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as UInt32:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as UInt64:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Float:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as Double:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as String:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as [String: AnyEncodable]:
|
||||||
|
hasher.combine(value)
|
||||||
|
case let value as [AnyEncodable]:
|
||||||
|
hasher.combine(value)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user