Skip to content
Open
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
10 changes: 10 additions & 0 deletions Decodable.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@
8F87BCC51B592F0E00E53A8C /* DecodingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F87BCC31B592F0E00E53A8C /* DecodingError.swift */; };
8F956D1F1B4D6FF700243072 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F956D1E1B4D6FF700243072 /* Operators.swift */; };
8FA733591D328D13003A90A7 /* Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3E45AF1D32853F00FB71FC /* Header.swift */; };
8FA8B0601D90A4A3008E5728 /* ErrorFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FA8B05F1D90A4A3008E5728 /* ErrorFormatter.swift */; };
8FA8B0611D90A4A3008E5728 /* ErrorFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FA8B05F1D90A4A3008E5728 /* ErrorFormatter.swift */; };
8FA8B0621D90A4A3008E5728 /* ErrorFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FA8B05F1D90A4A3008E5728 /* ErrorFormatter.swift */; };
8FA8B0631D90A4A3008E5728 /* ErrorFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FA8B05F1D90A4A3008E5728 /* ErrorFormatter.swift */; };
8FB48ECA1D306C4700BC50A1 /* KeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FB48EC91D306C4700BC50A1 /* KeyPath.swift */; };
8FB48ECB1D306C4700BC50A1 /* KeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FB48EC91D306C4700BC50A1 /* KeyPath.swift */; };
8FB48ECC1D306C4700BC50A1 /* KeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FB48EC91D306C4700BC50A1 /* KeyPath.swift */; };
Expand Down Expand Up @@ -139,6 +143,7 @@
8F87BCBA1B580CE200E53A8C /* ErrorPathTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorPathTests.swift; sourceTree = "<group>"; };
8F87BCC31B592F0E00E53A8C /* DecodingError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecodingError.swift; sourceTree = "<group>"; };
8F956D1E1B4D6FF700243072 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = "<group>"; };
8FA8B05F1D90A4A3008E5728 /* ErrorFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorFormatter.swift; sourceTree = "<group>"; };
8FB48EC91D306C4700BC50A1 /* KeyPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPath.swift; sourceTree = "<group>"; };
8FD3D92E1C270A2D00D1AF4E /* MissingKeyOperatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MissingKeyOperatorTests.swift; sourceTree = "<group>"; };
8FE7B5621B4C9FB900837609 /* Decodable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Decodable.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -261,6 +266,7 @@
8F72DC551C3CB8C500A39E10 /* NSValueCastable.swift */,
8F3E45A31D327E4500FB71FC /* Decoders.swift */,
8FB48EC91D306C4700BC50A1 /* KeyPath.swift */,
8FA8B05F1D90A4A3008E5728 /* ErrorFormatter.swift */,
8F3E45991D31362B00FB71FC /* OptionalKeyPath.swift */,
8F956D1E1B4D6FF700243072 /* Operators.swift */,
8F00623E1C81EF61007BCF48 /* Overloads.swift */,
Expand Down Expand Up @@ -624,6 +630,7 @@
buildActionMask = 2147483647;
files = (
8F87BCC51B592F0E00E53A8C /* DecodingError.swift in Sources */,
8FA8B0611D90A4A3008E5728 /* ErrorFormatter.swift in Sources */,
8FFAB8131B7CFA9500E2D724 /* Parse.swift in Sources */,
8F012EF61BB5A920007D0B5C /* Castable.swift in Sources */,
8FB48ECB1D306C4700BC50A1 /* KeyPath.swift in Sources */,
Expand Down Expand Up @@ -664,6 +671,7 @@
buildActionMask = 2147483647;
files = (
57FCDE5B1BA283C900130C48 /* DecodingError.swift in Sources */,
8FA8B0631D90A4A3008E5728 /* ErrorFormatter.swift in Sources */,
57FCDE5C1BA283C900130C48 /* Parse.swift in Sources */,
8F012EF81BB5A928007D0B5C /* Castable.swift in Sources */,
8FB48ECD1D306C4700BC50A1 /* KeyPath.swift in Sources */,
Expand All @@ -688,6 +696,7 @@
8FB48ECA1D306C4700BC50A1 /* KeyPath.swift in Sources */,
8FE7B57E1B4CA01400837609 /* Decodable.swift in Sources */,
8F72DC561C3CB8C500A39E10 /* NSValueCastable.swift in Sources */,
8FA8B0601D90A4A3008E5728 /* ErrorFormatter.swift in Sources */,
8F3E459A1D31362B00FB71FC /* OptionalKeyPath.swift in Sources */,
8F00623F1C81EF61007BCF48 /* Overloads.swift in Sources */,
8F3E45B81D32884700FB71FC /* Documentation.swift in Sources */,
Expand Down Expand Up @@ -724,6 +733,7 @@
buildActionMask = 2147483647;
files = (
D0DC547A1B78150900F79CB0 /* DecodingError.swift in Sources */,
8FA8B0621D90A4A3008E5728 /* ErrorFormatter.swift in Sources */,
8FFAB8141B7CFA9500E2D724 /* Parse.swift in Sources */,
8F012EF71BB5A920007D0B5C /* Castable.swift in Sources */,
8FB48ECC1D306C4700BC50A1 /* KeyPath.swift in Sources */,
Expand Down
24 changes: 24 additions & 0 deletions Sources/DecodingError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,30 @@ public enum DecodingError: Error, Equatable {

}

extension DecodingError: CustomStringConvertible {

private var errorMessage: String {
switch self {
case let .typeMismatch(expected, actual, _):
return "TypeMismatch: expected \(expected), not \(actual)"
case let .missingKey(key, _):
return "MissingKey: \(key)"
case .rawRepresentableInitializationError(_, _):
return "RawRepresentableInitializationError: could not be used to initialize \("TYPE").)" // FIXME
case let .other(error, _):
return "\(error)"
}
}

public var description: String {
var formatter = JSONErrorFormatter(path: metadata.path, message: errorMessage)
guard let rootObject = metadata.rootObject else {
return debugDescription // FIXME: Don't do this
}
return formatter.format(json: rootObject)
}
}


// Allow types to be used in pattern matching
// E.g case typeMismatchError(NSNull.self, _, _) but be careful
Expand Down
171 changes: 171 additions & 0 deletions Sources/ErrorFormatter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
//
// ErrorFormatter.swift
// Decodable
//
// Created by Johannes Lund on 2016-09-20.
// Copyright © 2016 anviking. All rights reserved.
//

import Foundation

extension String {
var indented: String {
return " " + self.replacingOccurrences(of: "\n", with: "\n ")
}

func replaceNewline(with string: String) -> String {
return replacingOccurrences(of: "\n", with: string)
}
}

/* TODO: Propagate errors through arrays
enum DebugKey {

/// Dictionary/Object key
case key(String)

/// Array index
case index(Int)

var key: String? {
switch self {
case .key(let key):
return key
default:
return nil
}
}

var index: Int? {
switch self {
case .index(let index):
return index
default:
return nil
}
}
}

extension DebugKey: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
self = .key(value)
}

public init(extendedGraphemeClusterLiteral value: String) {
self = .key(value)
}

public init(unicodeScalarLiteral value: String) {
self = .key(value)
}
}

extension DebugKey: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) {
self = .index(value)
}

}
*/
/// Sorry. /Past me
struct JSONErrorFormatter {
var path: [String]
let message: String

mutating func format(json: Any) -> String {
var result = ""
switch json {
case let json as NSDictionary:

for (key, value) in json {
result.append("\n")

// Check if key matches first item in `path`.
var highlightMatch = false
var isFinalKey = false
//if let a = (key as? String), let b = path.first?.key, a == b {
if let a = (key as? String), let b = path.first, a == b {
highlightMatch = true
path.removeFirst()
}

if path.count == 0 && highlightMatch {
isFinalKey = true
}

var formattedValue = format(json: value)
let formattedKey = "\(key)"

switch (highlightMatch, formattedValue.contains("\n")) {
case (true, true):
formattedValue = formattedValue.replaceNewline(with: "\n | ")
case (false, true):
formattedValue = formattedValue.replaceNewline(with: "\n ")
default:
break
}

if isFinalKey {
var lines = formattedValue.components(separatedBy: "\n")
if lines.count > 0 {
lines[0] = lines[0] + " <<<<---- \(message)"
}
formattedValue = lines.joined(separator: "\n")
}

result.append("\(formattedKey): \(formattedValue)")


}

case let json as [Any]:

var items: [String] = []
result.append("\n")
for (index, value) in json.enumerated() {

print(index)
// Check if key matches first item in `path`.
/*
var highlightMatch = false
var isFinalKey = false

if let i = path.first?.index, index == i {
highlightMatch = true
path.removeFirst()
}

if path.count == 0 && highlightMatch {
isFinalKey = true
}
*/
var formattedValue = format(json: value)
/*


if isFinalKey {
var lines = formattedValue.components(separatedBy: "\n")
if lines.count > 0 {
lines[0] = lines[0] + " <<<<---- \(message)"
}
formattedValue = lines.joined(separator: "\n")
}
*/
formattedValue = formattedValue.indented
/*
if highlightMatch {
formattedValue = formattedValue.replacingOccurrences(of: "\n", with: "\n |")
}
*/
items.append("[\(index)] \(formattedValue)")
}
result.append(items.joined(separator: "\n"))
case let json as String:
return "\"\(json)\""
default:
result += "\(json)"
}

return result
}
}
11 changes: 11 additions & 0 deletions Tests/ErrorPathTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ class ErrorPathTests: XCTestCase {
}
}

func testErrorDescription() {
// Actually there is no test here...
let dict: NSDictionary = ["object": ["repo": ["owner": ["id" : 1, "login": "anviking"]]]]

do {
_ = try dict => "object" => "repo" => "owner" => "oops" as String
} catch {
print(error)
}
}

// FIXME: #
func testNestedUnexpectedNSNull() {
let dict: NSDictionary = ["id": 1, "color": ["name": NSNull()]]
Expand Down