Skip to content

Commit

Permalink
Replace AnyCodable with custom JSONValue
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmassicotte committed Nov 12, 2022
1 parent 55071da commit 5da9787
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 30 deletions.
16 changes: 0 additions & 16 deletions Package.resolved

This file was deleted.

3 changes: 1 addition & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ let package = Package(
.library(name: "JSONRPC", targets: ["JSONRPC"]),
],
dependencies: [
.package(url: "https://github.com/Flight-School/AnyCodable", "0.6.0"..<"0.6.3"),
],
targets: [
.target(name: "JSONRPC", dependencies: ["AnyCodable"]),
.target(name: "JSONRPC", dependencies: []),
.testTarget(name: "JSONRPCTests", dependencies: ["JSONRPC"]),
]
)
119 changes: 119 additions & 0 deletions Sources/JSONRPC/JSONValue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import Foundation

public enum JSONValue: Codable, Hashable, Sendable {
case null
case bool(Bool)
case number(Double)
case string(String)
case array([JSONValue])
case hash([String: JSONValue])

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

switch self {
case .null:
try container.encodeNil()
case .bool(let value):
try container.encode(value)
case .number(let value):
try container.encode(value)
case .string(let value):
try container.encode(value)
case .array(let value):
try container.encode(value)
case .hash(let value):
try container.encode(value)
}
}

public init(from decoder: Decoder) throws {
let single = try? decoder.singleValueContainer()

if let value = try? single?.decode([String: JSONValue].self) {
self = .hash(value)
return
}

if let value = try? single?.decode([JSONValue].self) {
self = .array(value)
return
}

if let value = try? single?.decode(String.self) {
self = .string(value)
return
}

if let value = try? single?.decode(Double.self) {
self = .number(value)
return
}

if let value = try? single?.decode(Bool.self) {
self = .bool(value)
return
}

if single?.decodeNil() == true {
self = .null
return
}

throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "failed to decode JSON object"))
}
}

extension JSONValue: ExpressibleByNilLiteral {
public init(nilLiteral: ()) {
self = .null
}
}

extension JSONValue: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, JSONValue)...) {
var hash = [String: JSONValue]()

for element in elements {
hash[element.0] = element.1
}

self = .hash(hash)
}
}

extension JSONValue: ExpressibleByStringLiteral {
public init(stringLiteral: String) {
self = .string(stringLiteral)
}
}

extension JSONValue: ExpressibleByIntegerLiteral {
public init(integerLiteral value: IntegerLiteralType) {
self = .number(Double(value))
}
}

extension JSONValue: ExpressibleByFloatLiteral {
public init(floatLiteral value: FloatLiteralType) {
self = .number(value)
}
}

extension JSONValue: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: JSONValue...) {
var array = [JSONValue]()

for element in elements {
array.append(element)
}

self = .array(array)
}
}

extension JSONValue: ExpressibleByBooleanLiteral {
public init(booleanLiteral value: BooleanLiteralType) {
self = .bool(value)
}
}
23 changes: 11 additions & 12 deletions Sources/JSONRPC/Protocol.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import Foundation
import AnyCodable

public enum JSONRPCProtocolError: Error {
case unsupportedVersion(String)
case malformedMessage
}

public enum JSONRPCMessage {
case notification(String, AnyCodable)
case request(JSONId, String, AnyCodable?)
case notification(String, JSONValue)
case request(JSONId, String, JSONValue?)
case response(JSONId)
case undecodableId(AnyJSONRPCResponseError)
}
Expand All @@ -34,9 +33,9 @@ extension JSONRPCMessage: Codable {
// no id means notification
if container.contains(.id) == false {
let method = try container.decode(String.self, forKey: .method)
let params = try? container.decode(AnyCodable.self, forKey: .params)
let params = try? container.decode(JSONValue.self, forKey: .params)

self = .notification(method, params ?? nil)
self = .notification(method, params ?? .null)
return
}

Expand All @@ -52,7 +51,7 @@ extension JSONRPCMessage: Codable {

if container.contains(.method) {
let method = try container.decode(String.self, forKey: .method)
let params = try? container.decode(AnyCodable.self, forKey: .params)
let params = try? container.decode(JSONValue.self, forKey: .params)

self = .request(id, method, params)
return
Expand All @@ -73,7 +72,7 @@ extension JSONRPCMessage: Codable {
case .notification(let method, let params):
try container.encode(method, forKey: .method)

if params != AnyCodable(nilLiteral: ()) {
if params != JSONValue.null {
try container.encode(params, forKey: .params)
}
case .request(let id, let method, let params):
Expand Down Expand Up @@ -115,7 +114,7 @@ extension JSONRPCRequest: Equatable where T: Equatable {
extension JSONRPCRequest: Hashable where T: Hashable {
}

public typealias AnyJSONRPCRequest = JSONRPCRequest<AnyCodable>
public typealias AnyJSONRPCRequest = JSONRPCRequest<JSONValue>

public struct JSONRPCNotification<T>: Codable where T: Codable {
public var jsonrpc = "2.0"
Expand All @@ -134,7 +133,7 @@ extension JSONRPCNotification: Equatable where T: Equatable {
extension JSONRPCNotification: Hashable where T: Hashable {
}

public typealias AnyJSONRPCNotification = JSONRPCNotification<AnyCodable>
public typealias AnyJSONRPCNotification = JSONRPCNotification<JSONValue>

public struct JSONRPCResponseError<T>: Codable where T: Codable {
public var code: Int
Expand All @@ -154,7 +153,7 @@ extension JSONRPCResponseError: Equatable where T: Equatable {
extension JSONRPCResponseError: Hashable where T: Hashable {
}

public typealias AnyJSONRPCResponseError = JSONRPCResponseError<AnyCodable>
public typealias AnyJSONRPCResponseError = JSONRPCResponseError<JSONValue>

public enum JSONRPCResponse<T> where T: Codable {
case result(JSONId, T)
Expand Down Expand Up @@ -271,12 +270,12 @@ extension JSONRPCResponse: Hashable where T: Hashable {
}

extension JSONRPCResponse {
static func internalError(id: JSONId, message: String, data: AnyCodable = nil) -> JSONRPCResponse<AnyCodable> {
static func internalError(id: JSONId, message: String, data: JSONValue = nil) -> JSONRPCResponse<JSONValue> {
let error = AnyJSONRPCResponseError(code: JSONRPCErrors.internalError,
message: message,
data: data)
return .failure(id, error)
}
}

public typealias AnyJSONRPCResponse = JSONRPCResponse<AnyCodable>
public typealias AnyJSONRPCResponse = JSONRPCResponse<JSONValue>
73 changes: 73 additions & 0 deletions Tests/JSONRPCTests/JSONValueTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import XCTest
import JSONRPC

final class JSONValueTests: XCTestCase {
func testNullEncoding() throws {
let obj = JSONValue.null

let data = try JSONEncoder().encode(obj)
let string = try XCTUnwrap(String(data: data, encoding: .utf8))

XCTAssertEqual(string, "null")
}

func testBoolEncoding() throws {
let obj = JSONValue.bool(true)

let data = try JSONEncoder().encode(obj)
let string = try XCTUnwrap(String(data: data, encoding: .utf8))

XCTAssertEqual(string, "true")
}

func testIntEncoding() throws {
let obj = JSONValue.number(45)

let data = try JSONEncoder().encode(obj)
let string = try XCTUnwrap(String(data: data, encoding: .utf8))

XCTAssertEqual(string, "45")
}

func testArrayEncoding() throws {
let obj = JSONValue.array([1,2,3])

let data = try JSONEncoder().encode(obj)
let string = try XCTUnwrap(String(data: data, encoding: .utf8))

XCTAssertEqual(string, "[1,2,3]")
}

func testNullInDictionary() throws {
let obj = JSONValue.hash(["abc": nil])

let data = try JSONEncoder().encode(obj)
let string = try XCTUnwrap(String(data: data, encoding: .utf8))

XCTAssertEqual(string, "{\"abc\":null}")
}

func testDecoding() throws {
let string = """
{
"string": "abc",
"bool": true,
"null": null,
"int": 145,
"double": 145.0,
"array": [1,2,3]
}
"""
let value = try JSONDecoder().decode(JSONValue.self, from: string.data(using: .utf8)!)

let expected: JSONValue = [
"string": "abc",
"bool": true,
"null": nil,
"int": 145,
"double": 145.0,
"array": [1,2,3]
]
XCTAssertEqual(value, expected)
}
}

0 comments on commit 5da9787

Please sign in to comment.