Skip to content

Commit

Permalink
Merge pull request #1 from beatt83/feature/any-codable-service
Browse files Browse the repository at this point in the history
feat(diddocument): use AnyCodable for service endpoint.
  • Loading branch information
beatt83 authored Feb 6, 2024
2 parents 0a1c4f5 + 3752f91 commit 3d62fc9
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 117 deletions.
77 changes: 3 additions & 74 deletions Sources/DIDCore/DIDDocument/DIDDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,37 +29,19 @@ public struct DIDDocument {
}

public struct Service {

public enum ServiceEndpoint {
case string(String)
case set([String])
case map([String: String])
case combo([EndpointElement])

public enum EndpointElement {
case stringValue(String)
case mapValue([String: String])
}
}

public let id: String
public let type: String
public let serviceEndpoint: ServiceEndpoint
public let routingKeys: [String]?
public let accept: [String]?
// Its not ideal that the service endpoint is a AnyCodable but since by the specification we cannot know the structure of the serviceEndpoint structure this is the best way.
public let serviceEndpoint: AnyCodable

public init(
id: String,
type: String,
serviceEndpoint: ServiceEndpoint,
routingKeys: [String]? = nil,
accept: [String]? = nil
serviceEndpoint: AnyCodable
) {
self.id = id
self.type = type
self.serviceEndpoint = serviceEndpoint
self.routingKeys = routingKeys
self.accept = accept
}
}

Expand Down Expand Up @@ -103,36 +85,6 @@ public struct DIDDocument {

extension DIDDocument: Codable {}
extension DIDDocument.Service: Codable {}
extension DIDDocument.Service.ServiceEndpoint: Codable {
// Codable conformance for ServiceEndpoint
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let value = try? container.decode(String.self) {
self = .string(value)
} else if let set = try? container.decode([String].self) {
self = .set(set)
} else if let map = try? container.decode([String: String].self) {
self = .map(map)
} else {
let combo = try container.decode([EndpointElement].self)
self = .combo(combo)
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let value):
try container.encode(value)
case .set(let set):
try container.encode(set)
case .map(let map):
try container.encode(map)
case .combo(let combo):
try container.encode(combo)
}
}
}

extension DIDDocument.VerificationMethodMapping: Codable {
// Codable conformance for EndpointElement
Expand All @@ -157,29 +109,6 @@ extension DIDDocument.VerificationMethodMapping: Codable {
}
}

extension DIDDocument.Service.ServiceEndpoint.EndpointElement: Codable {
// Codable conformance for EndpointElement
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let value = try? container.decode(String.self) {
self = .stringValue(value)
} else {
let mapValue = try container.decode([String: String].self)
self = .mapValue(mapValue)
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .stringValue(let value):
try container.encode(value)
case .mapValue(let map):
try container.encode(map)
}
}
}

extension DIDDocument.VerificationMethod: Codable {
enum CodingKeys: String, CodingKey {
case id
Expand Down
203 changes: 203 additions & 0 deletions Sources/DIDCore/Helper/AnyCodable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import Foundation

public struct AnyCodable {
public let value: Any

public init<T: Codable>(_ value: T) {
self.value = value
}

init(_ value: Any) {
self.value = value
}

public func get<T>() -> T? {
value as? T
}

public func get() -> Any {
value
}
}

extension AnyCodable: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()

if container.decodeNil() {
self.init(())
} 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([AnyCodable].self) {
self.init(array.map { $0.value })
} else if let dictionary = try? container.decode([String: AnyCodable].self) {
self.init(dictionary.mapValues { $0.value })
} else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Not a known JSON Primitive"
)
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()

switch self.value {
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)
case let date as Date:
try container.encode(date)
case let url as URL:
try container.encode(url)
case let array as [Any]:
try container.encode(array.map { AnyCodable($0) })
case let dictionary as [String: Any]:
try container.encode(dictionary.mapValues { AnyCodable($0) })
default:
let context = EncodingError.Context(
codingPath: container.codingPath,
debugDescription: "Value is not codable"
)
throw EncodingError.invalidValue(self.value, context)
}
}
}

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], let rhs as [String: AnyCodable]):
return lhs == rhs
case (let lhs as [AnyCodable], let rhs as [AnyCodable]):
return lhs == rhs
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(\(self.description))"
}
}
}

extension AnyCodable: ExpressibleByNilLiteral, ExpressibleByBooleanLiteral, ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByStringLiteral, ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral {

public init(nilLiteral: ()) {
self.init(nil ?? ())
}

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(Dictionary<AnyHashable, Any>(elements, uniquingKeysWith: { (first, _) in first }))
}
}
2 changes: 1 addition & 1 deletion Sources/DIDCore/Helper/JSONEconder+Helper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation
extension JSONEncoder {
static func DIDDocumentEncoder() -> JSONEncoder {
let encoder = JSONEncoder()
if #available(macOS 10.15, *) {
if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) {
encoder.outputFormatting = [.withoutEscapingSlashes, .sortedKeys]
} else {
encoder.outputFormatting = .sortedKeys
Expand Down
Loading

0 comments on commit 3d62fc9

Please sign in to comment.