Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 33 additions & 19 deletions Sources/Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,28 +102,42 @@ extension Array where Element: FixedWidthInteger {

extension BigInt: Codable {
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
if let container = try? decoder.singleValueContainer(), let stringValue = try? container.decode(String.self) {
if stringValue.hasPrefix("0x") || stringValue.hasPrefix("0X") {
guard let bigUInt = BigUInt(stringValue.dropFirst(2), radix: 16) else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid hexadecimal BigInt string")
}
self.init(sign: .plus, magnitude: bigUInt)
} else {
guard let bigInt = BigInt(stringValue) else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid decimal BigInt string")
}
self = bigInt
}
} else {
var container = try decoder.unkeyedContainer()

// Decode sign
let sign: BigInt.Sign
switch try container.decode(String.self) {
case "+":
sign = .plus
case "-":
sign = .minus
default:
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath,
debugDescription: "Invalid big integer sign"))
}
// Decode sign
let sign: BigInt.Sign
switch try container.decode(String.self) {
case "+":
sign = .plus
case "-":
sign = .minus
default:
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath,
debugDescription: "Invalid big integer sign"))
}

// Decode magnitude
let words = try [UInt](count: container.count?.advanced(by: -1)) { () -> UInt64? in
guard !container.isAtEnd else { return nil }
return try container.decode(UInt64.self)
}
let magnitude = BigUInt(words: words)
// Decode magnitude
let words = try [UInt](count: container.count?.advanced(by: -1)) { () -> UInt64? in
guard !container.isAtEnd else { return nil }
return try container.decode(UInt64.self)
}
let magnitude = BigUInt(words: words)

self.init(sign: sign, magnitude: magnitude)
self.init(sign: sign, magnitude: magnitude)
}
}

public func encode(to encoder: Encoder) throws {
Expand Down
38 changes: 38 additions & 0 deletions Tests/BigIntTests/BigIntTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,44 @@ class BigIntTests: XCTestCase {
XCTAssertEqual(context.debugDescription, "Invalid big integer sign")
}
}

func testDecodableString() {
func test(_ a: BigInt, _ v: String? = nil, file: StaticString = #file, line: UInt = #line) {
do {
let json = try JSONEncoder().encode(v ?? a.description)
let b = try JSONDecoder().decode(BigInt.self, from: json)
XCTAssertEqual(a, b, file: file, line: line)
} catch let error {
XCTFail("Error thrown: \(error.localizedDescription)", file: file, line: line)
}
}

test(1, "1")
test(1, "+1")
test(-1, "-1")
test(0, "+0")
test(0, "-0")
test(15, "0xf")
test(15, "0Xf")
test(15, "0x0f")
test(BigInt(1) << 64)
test(-BigInt(1) << 64)
}

func testDecodableStringError() {
func test(_ v: String, _ m: String) {
XCTAssertThrowsError(try JSONDecoder().decode(BigInt.self, from: try! JSONEncoder().encode(v))) { error in
guard let error = error as? DecodingError else { XCTFail("Expected a decoding error"); return }
guard case .dataCorrupted(let context) = error else { XCTFail("Expected a dataCorrupted error"); return }
XCTAssertEqual(m, context.debugDescription)
}
}

test("124q", "Invalid decimal BigInt string")
test("-124q", "Invalid decimal BigInt string")
test("0xXYZ", "Invalid hexadecimal BigInt string")
}


func testConversionToData() {
func test(_ b: BigInt, _ d: Array<UInt8>, file: StaticString = #file, line: UInt = #line) {
Expand Down