Skip to content

Commit

Permalink
Add method JSONError.withPrefix(_:)
Browse files Browse the repository at this point in the history
Fixes #26.
  • Loading branch information
lilyball committed Nov 7, 2018
1 parent 037a026 commit 51bdd47
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 8 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,12 @@ Unless you explicitly state otherwise, any contribution intentionally submitted

## Version History

#### Development

* Add method `JSONError.withPrefix(_:)` that returns a new error by prepending a prefix onto the path. This can be used in custom parsing code to produce good errors if the existing convenience functions don't do what you want. ([#26][])

[#26]: https://github.com/postmates/PMJSON/issues/26 "GitHub: JSONError should have structured way to add prefixes"

#### v3.1.1 (2018-05-17)

* Squelch Swift 4.1 warnings.
Expand Down
34 changes: 26 additions & 8 deletions Sources/JSONError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,34 @@ public enum JSONError: Error, CustomStringConvertible {
prefix += key.stringValue
}
}
return prefix.isEmpty ? self : withPrefix(prefix)
return prefix.isEmpty ? self : withPrefix(.key(prefix))
}

fileprivate func withPrefix(_ prefix: String) -> JSONError {
func prefixPath(_ path: String?, with prefix: String) -> String {
guard let path = path, !path.isEmpty else { return prefix }
/// An element that can be prefixed onto the error path.
public enum Prefix {
/// A key, as from an object.
case key(String)
/// An index used to subscript an array.
case index(Int)

fileprivate var asString: String {
switch self {
case .key(let key): return key
case .index(let x): return "[\(x)]"
}
}
}

/// Returns a new `JSONError` by prefixing the given `Prefix` onto the path.
/// - Parameter prefix: The prefix to prepend to the error path.
/// - Returns: A new `JSONError`.
public func withPrefix(_ prefix: Prefix) -> JSONError {
func prefixPath(_ path: String?, with prefix: Prefix) -> String {
guard let path = path, !path.isEmpty else { return prefix.asString }
if path.unicodeScalars.first == "[" {
return prefix + path
return prefix.asString + path
} else {
return "\(prefix).\(path)"
return "\(prefix.asString).\(path)"
}
}
switch self {
Expand Down Expand Up @@ -1782,15 +1800,15 @@ internal func scoped<T>(_ key: String, _ f: () throws -> T) rethrows -> T {
do {
return try f()
} catch let error as JSONError {
throw error.withPrefix(key)
throw error.withPrefix(.key(key))
}
}

internal func scoped<T>(_ index: Int, _ f: () throws -> T) rethrows -> T {
do {
return try f()
} catch let error as JSONError {
throw error.withPrefix("[\(index)]")
throw error.withPrefix(.index(index))
}
}

Expand Down
33 changes: 33 additions & 0 deletions Tests/PMJSONTests/JSONAccessorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -319,4 +319,37 @@ public final class JSONAccessorTests: XCTestCase {
XCTAssertEqual(JSON.double(42.1), JSON.decimal(42.1))
XCTAssertEqual(JSON.double(1e100), JSON.decimal(Decimal(string: "1e100")!)) // Decimal(_: Double) can produce incorrect values
}

func testErrorPaths() {
XCTAssertThrowsError(try JSON.int64(42).getString()) { (error) in
switch error {
case let error as JSONError: XCTAssertNil(error.path)
default: XCTFail("Expected JSONError, got \(error)")
}
}
XCTAssertThrowsError(try (["foo": 42] as JSON).getString("bar")) { (error) in
switch error {
case let error as JSONError: XCTAssertEqual(error.path, "bar")
default: XCTFail("Expected JSONError, got \(error)")
}
}
XCTAssertThrowsError(try (["foo": 42] as JSON).getString("foo")) { (error) in
switch error {
case let error as JSONError: XCTAssertEqual(error.path, "foo")
default: XCTFail("Expected JSONError, got \(error)")
}
}
XCTAssertThrowsError(try (["foo": ["bar": 42]] as JSON).getObject("foo", { try $0.getString("bar") })) { (error) in
switch error {
case let error as JSONError: XCTAssertEqual(error.path, "foo.bar")
default: XCTFail("Expected JSONError, got \(error)")
}
}
XCTAssertThrowsError(try (["foo": ["bar": [1, 2]]] as JSON).getObject("foo", { try $0.mapArray("bar", { try $0.getString() }) })) { (error) in
switch error {
case let error as JSONError: XCTAssertEqual(error.path, "foo.bar[0]")
default: XCTFail("Expected JSONError, got \(error)")
}
}
}
}

0 comments on commit 51bdd47

Please sign in to comment.