Skip to content

Commit

Permalink
add custom error type for duplicated keys to encapsulate all the info
Browse files Browse the repository at this point in the history
  • Loading branch information
tejassharma96 committed Jul 10, 2024
1 parent 332e67c commit da7a3e4
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 14 deletions.
27 changes: 18 additions & 9 deletions Sources/Yams/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -356,22 +356,31 @@ private extension Parser {
event = try parse()
}
let keys = pairs.map { $0.0 }
let duplicateKeys = Dictionary(grouping: keys, by: {$0}).filter { $1.count > 1 }.keys
if let duplicatedKey = duplicateKeys.first {
throw YamlError.parser(
context: YamlError.Context(text: "expected all keys in mapping to be unique",
mark: Mark(line: 1, column: 1)),
problem: "but found multiple instances of: \(duplicateKeys.compactMap { $0.string })",
duplicatedKey.mark!,
yaml: yaml)
}
try checkDuplicates(mappingKeys: keys)
let node = Node.mapping(.init(pairs, tag(firstEvent.mappingTag), event.mappingStyle, firstEvent.startMark))
if let anchor = firstEvent.mappingAnchor {
anchors[anchor] = node
}
return node
}

private func checkDuplicates(mappingKeys: [Node]) throws {
var duplicates: [String: [Node]] = [:]
for key in mappingKeys {
if let keyString = key.string {
if duplicates.keys.contains(keyString) {
duplicates[keyString]?.append(key)
} else {
duplicates[keyString] = [key]
}
}
}
duplicates = duplicates.filter { $1.count > 1 }
guard duplicates.isEmpty else {
throw YamlError.duplicatedKeysInMapping(duplicates: duplicates, yaml: yaml)
}
}

func tag(_ string: String?) -> Tag {
let tagName = string.map(Tag.Name.init(rawValue:)) ?? .implicit
return Tag(tagName, resolver, constructor)
Expand Down
19 changes: 19 additions & 0 deletions Sources/Yams/YamlError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ public enum YamlError: Error {
/// - parameter encoding: The string encoding used to decode the string data.
case dataCouldNotBeDecoded(encoding: String.Encoding)

/// Multiple uses of the same key detected in a mapping
///
/// - parameter duplicates: A dictionary keyed by the duplicated node value, with all nodes that duplicate said value
case duplicatedKeysInMapping(duplicates: [String: [Node]], yaml: String)

/// The error context.
public struct Context: CustomStringConvertible {
/// Context text.
Expand Down Expand Up @@ -175,6 +180,20 @@ extension YamlError: CustomStringConvertible {
return problem
case .dataCouldNotBeDecoded(encoding: let encoding):
return "String could not be decoded from data using '\(encoding)' encoding"
case let .duplicatedKeysInMapping(duplicates, yaml):
return duplicates.duplicatedKeyErrorDescription(yaml: yaml)
}
}
}

private extension Dictionary where Key == String, Value == [Node] {
func duplicatedKeyErrorDescription(yaml: String) -> String {
var error = "error: parser: expected all keys to be unique but found the following duplicated key(s):"
for key in self.keys.sorted() {
let duplicatedNodes = self[key]!
let marks = duplicatedNodes.compactMap { $0.mark }
error += "\n\(key) (\(marks)):\n\(marks.map { $0.snippet(from: yaml) }.joined(separator: "\n"))"
}
return error
}
}
40 changes: 35 additions & 5 deletions Tests/YamsTests/YamlErrorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,45 @@ class YamlErrorTests: XCTestCase {

func testDuplicateKeysCannotBeParsed() throws {
let yamlString = """
key: value
key: different_value
a: value
a: different_value
"""
XCTAssertThrowsError(try Parser(yaml: yamlString).singleRoot()) { error in
XCTAssertTrue(error is YamlError)
XCTAssertEqual("\(error)", """
1:5: error: parser: expected all keys in mapping to be unique in line 1, column 1
but found multiple instances of: ["key"]:
key: value
error: parser: expected all keys to be unique but found the following duplicated key(s):
a ([1:5, 2:5]):
a: value
^
a: different_value
^
""")
}
}

func testDuplicatedKeysCannotBeParsed_MultipleDuplicates() throws {
let yamlString = """
a: value
a: different_value
b: value
b: different_value
b: different_different_value
"""
XCTAssertThrowsError(try Parser(yaml: yamlString).singleRoot()) { error in
XCTAssertTrue(error is YamlError)
XCTAssertEqual("\(error)", """
error: parser: expected all keys to be unique but found the following duplicated key(s):
a ([1:5, 2:5]):
a: value
^
a: different_value
^
b ([3:5, 4:5, 5:5]):
b: value
^
b: different_value
^
b: different_different_value
^
""")
}
Expand Down

0 comments on commit da7a3e4

Please sign in to comment.