From faf86a42f9fd062e8954caafe660031db9079e30 Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sun, 9 Jun 2024 18:28:21 -0700 Subject: [PATCH 1/2] Implement YAML schemas for property reading and writing --- Package.resolved | 9 ++ Package.swift | 10 +- Sources/PotentCodables/Regexes.swift | 34 +++++ .../{Cfyaml.swift => Libfyaml.swift} | 4 +- Sources/PotentYAML/YAML.swift | 8 +- Sources/PotentYAML/YAMLReader.swift | 131 +++++------------- Sources/PotentYAML/YAMLSchema.swift | 34 +++++ Sources/PotentYAML/YAMLSchemaCore.swift | 60 ++++++++ Sources/PotentYAML/YAMLWriter.swift | 21 ++- Tests/YAMLEncoderTests.swift | 4 +- Tests/YAMLErrorTests.swift | 13 ++ Tests/YAMLSchemaTests.swift | 89 ++++++++++++ Tests/YAMLTests.swift | 2 +- 13 files changed, 313 insertions(+), 106 deletions(-) create mode 100644 Sources/PotentCodables/Regexes.swift rename Sources/PotentYAML/{Cfyaml.swift => Libfyaml.swift} (98%) create mode 100644 Sources/PotentYAML/YAMLSchema.swift create mode 100644 Sources/PotentYAML/YAMLSchemaCore.swift create mode 100644 Tests/YAMLSchemaTests.swift diff --git a/Package.resolved b/Package.resolved index 71f61a322..c8f0855c2 100644 --- a/Package.resolved +++ b/Package.resolved @@ -19,6 +19,15 @@ "version": "1.1.1" } }, + { + "package": "Regex", + "repositoryURL": "https://github.com/sharplet/Regex.git", + "state": { + "branch": null, + "revision": "76c2b73d4281d77fc3118391877efd1bf972f515", + "version": "2.1.1" + } + }, { "package": "swift-collections", "repositoryURL": "https://github.com/apple/swift-collections.git", diff --git a/Package.swift b/Package.swift index 16720b7fb..09ac6d30a 100644 --- a/Package.swift +++ b/Package.swift @@ -16,13 +16,15 @@ import PackageDescription let pcDeps: [Target.Dependency] = [ "BigInt", .product(name: "Collections", package: "swift-collections"), - .byName(name: "Float16", condition: .when(platforms: [.macOS, .macCatalyst, .linux])) + .byName(name: "Float16", condition: .when(platforms: [.macOS, .macCatalyst, .linux])), + .product(name: "Regex", package: "regex"), ] #else let pcDeps: [Target.Dependency] = [ "BigInt", .product(name: "Collections", package: "swift-collections"), "Float16", + .product(name: "Regex", package: "regex"), ] #endif @@ -43,7 +45,8 @@ let package = Package( dependencies: [ .package(url: "https://github.com/attaswift/BigInt.git", .upToNextMinor(from: "5.3.0")), .package(url: "https://github.com/SusanDoggie/Float16.git", .upToNextMinor(from: "1.1.1")), - .package(url: "https://github.com/apple/swift-collections.git", .upToNextMinor(from: "1.0.4")) + .package(url: "https://github.com/apple/swift-collections.git", .upToNextMinor(from: "1.0.4")), + .package(url: "https://github.com/sharplet/Regex.git", .upToNextMinor(from: "2.1.1")) ], targets: [ .target( @@ -100,7 +103,8 @@ let package = Package( "PotentCodables", "BigInt", "Cfyaml", - .product(name: "Collections", package: "swift-collections") + .product(name: "Collections", package: "swift-collections"), + .product(name: "Regex", package: "regex") ] ), .testTarget( diff --git a/Sources/PotentCodables/Regexes.swift b/Sources/PotentCodables/Regexes.swift new file mode 100644 index 000000000..89fd826a3 --- /dev/null +++ b/Sources/PotentCodables/Regexes.swift @@ -0,0 +1,34 @@ +// +// Pattern.swift +// PotentCodables +// +// Copyright © 2019 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +import Regex + + +extension Regex: ExpressibleByStringLiteral { + + public init(stringLiteral value: StaticString) { + self.init(value) + } + +} + +extension Regex { + + public static func anyOf(_ regexes: Regex...) -> Regex { + do { + return try Regex(string: regexes.map { "(\($0.description))" }.joined(separator: "|")) + } + catch { + preconditionFailure("unexpected error creating regex from joined set: \(error)") + } + } + +} diff --git a/Sources/PotentYAML/Cfyaml.swift b/Sources/PotentYAML/Libfyaml.swift similarity index 98% rename from Sources/PotentYAML/Cfyaml.swift rename to Sources/PotentYAML/Libfyaml.swift index 3382678a0..4b04907ba 100644 --- a/Sources/PotentYAML/Cfyaml.swift +++ b/Sources/PotentYAML/Libfyaml.swift @@ -1,5 +1,5 @@ // -// Cfyaml.swift +// Libfyaml.swift // PotentCodables // // Copyright © 2021 Outfox, inc. @@ -11,7 +11,7 @@ import Cfyaml -enum Libfyaml { +internal enum Libfyaml { internal static func createParser() -> OpaquePointer? { diff --git a/Sources/PotentYAML/YAML.swift b/Sources/PotentYAML/YAML.swift index e81e70d5d..1934b6935 100644 --- a/Sources/PotentYAML/YAML.swift +++ b/Sources/PotentYAML/YAML.swift @@ -75,8 +75,8 @@ public enum YAML { self.isNegative = isNegative } - public init(_ value: String) { - self.init(value, isInteger: value.allSatisfy { $0.isNumber || $0 == "-" }, isNegative: value.hasPrefix("-")) + public init(_ value: String, schema: YAMLSchema = .core) { + self.init(value, isInteger: schema.isInt(value), isNegative: value.hasPrefix("-")) } public init(_ value: T) { @@ -193,6 +193,10 @@ public enum YAML { case doubleQuoted = 2 case literal = 3 case folded = 4 + + public var isQuoted: Bool { + self == .singleQuoted || self == .doubleQuoted + } } public enum CollectionStyle: Int32 { diff --git a/Sources/PotentYAML/YAMLReader.swift b/Sources/PotentYAML/YAMLReader.swift index 9a7a98d10..89e4fb620 100644 --- a/Sources/PotentYAML/YAMLReader.swift +++ b/Sources/PotentYAML/YAMLReader.swift @@ -12,11 +12,11 @@ import Cfyaml import Foundation -internal enum YAMLReader { +internal struct YAMLReader { typealias Error = YAMLSerialization.Error - static func read(data: Data) throws -> YAML.Sequence { + static func read(data: Data, schema: YAMLSchema = .core) throws -> YAML.Sequence { guard let parser = Libfyaml.createParser().map(Parser.init) else { throw Error.unableToCreateParser @@ -29,11 +29,19 @@ internal enum YAMLReader { try parser.expect(eventType: FYET_STREAM_START) - return try stream(parser: parser) + return try YAMLReader(parser: parser, schema: schema).stream() } } - static func stream(parser: Parser) throws -> YAML.Sequence { + let parser: Parser + let schema: YAMLSchema + + init(parser: Parser, schema: YAMLSchema = .core) { + self.parser = parser + self.schema = schema + } + + private func stream() throws -> YAML.Sequence { var documents: YAML.Sequence = [] @@ -47,7 +55,7 @@ internal enum YAMLReader { } if event.type == FYET_DOCUMENT_START { - documents.append(try document(parser: parser)) + documents.append(try document()) } else { throw parser.error(fallback: .unexpectedEvent) @@ -58,12 +66,12 @@ internal enum YAMLReader { } - static func document(parser: Parser) throws -> YAML { + private func document() throws -> YAML { let root = try parser.next() defer { parser.free(event: root) } - let document = try value(event: root, parser: parser) + let document = try value(event: root) try parser.expect(eventType: FYET_DOCUMENT_END) @@ -71,7 +79,7 @@ internal enum YAMLReader { } - static func mapping(parser: Parser) throws -> YAML.Mapping { + private func mapping() throws -> YAML.Mapping { var result: YAML.Mapping = [] @@ -84,12 +92,12 @@ internal enum YAMLReader { break } - let key = try value(event: event, parser: parser) + let key = try value(event: event) let valEvent = try parser.next() defer { parser.free(event: valEvent) } - let val = try value(event: valEvent, parser: parser) + let val = try value(event: valEvent) result.append(YAML.Mapping.Element(key: key, value: val)) } @@ -98,7 +106,7 @@ internal enum YAMLReader { } - static func sequence(parser: Parser) throws -> YAML.Sequence { + private func sequence() throws -> YAML.Sequence { var result: YAML.Sequence = [] @@ -111,7 +119,7 @@ internal enum YAMLReader { break } - let val = try value(event: event, parser: parser) + let val = try value(event: event) result.append(val) } @@ -119,16 +127,10 @@ internal enum YAMLReader { return result } - private static let nullRegex = RegEx(pattern: #"^(null|Null|NULL|~)$"#) - private static let trueRegex = RegEx(pattern: #"^(true|True|TRUE)$"#) - private static let falseRegex = RegEx(pattern: #"^(false|False|FALSE)$"#) - private static let integerRegex = RegEx(pattern: #"^(([-+]?[0-9]+)|(0o[0-7]+)|(0x[0-9a-fA-F]+))$"#) - private static let floatRegex = RegEx(pattern: #"^([-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?)$"#) - private static let infinityRegex = RegEx(pattern: #"^([-+]?(\.inf|\.Inf|\.INF))$"#) - private static let nanRegex = RegEx(pattern: #"^(\.nan|\.NaN|\.NAN)$"#) - - static func scalar(value: String, style: fy_scalar_style, tag: YAML.Tag?, anchor: String?) throws -> YAML { - let stringStyle = YAML.StringStyle(rawValue: style.rawValue) ?? .any + private func scalar(value: String, style: fy_scalar_style, tag: YAML.Tag?, anchor: String?) throws -> YAML { + guard let stringStyle = YAML.StringStyle(rawValue: style.rawValue) else { + preconditionFailure("String style not recognized") + } switch tag { case .none: @@ -152,7 +154,7 @@ internal enum YAMLReader { } - static func untaggedScalar( + private func untaggedScalar( value: String, stringStyle: YAML.StringStyle, style: fy_scalar_style, @@ -161,32 +163,26 @@ internal enum YAMLReader { if style == FYSS_PLAIN { - if nullRegex.matches(string: value) { + if schema.isNull(value) { return .null(anchor: anchor) } - if trueRegex.matches(string: value) { + if schema.isTrue(value) { return .bool(true, anchor: anchor) } - if falseRegex.matches(string: value) { + if schema.isFalse(value) { return .bool(false, anchor: anchor) } - if integerRegex.matches(string: value) { - return .integer(YAML.Number(value), anchor: anchor) - } - - if floatRegex.matches(string: value) { - return .float(YAML.Number(value), anchor: anchor) - } - - if infinityRegex.matches(string: value) { - return .float(YAML.Number(value.lowercased()), anchor: anchor) + if schema.isInt(value) { + return .integer(YAML.Number(value.lowercased(), isInteger: true, isNegative: value.hasPrefix("-")), + anchor: anchor) } - if nanRegex.matches(string: value) { - return .float(YAML.Number(value.lowercased()), anchor: anchor) + if schema.isFloat(value) { + return .float(YAML.Number(value.lowercased(), isInteger: false, isNegative: value.hasPrefix("-")), + anchor: anchor) } } @@ -194,7 +190,7 @@ internal enum YAMLReader { return .string(value, style: stringStyle, tag: nil, anchor: nil) } - static func boolTaggedScalar(value: String, anchor: String?) throws -> YAML { + private func boolTaggedScalar(value: String, anchor: String?) throws -> YAML { if let bool = Bool(value.lowercased()) { return .bool(bool, anchor: anchor) @@ -209,14 +205,14 @@ internal enum YAMLReader { throw Error.invalidTaggedBool } - static func value(event: Parser.Event, parser: Parser) throws -> YAML { + private func value(event: Parser.Event) throws -> YAML { switch event.type { case FYET_MAPPING_START: - let value = try mapping(parser: parser) + let value = try mapping() return .mapping(value, style: event.style.collectionStyle, tag: YAML.Tag(event.tag), anchor: event.anchor) case FYET_SEQUENCE_START: - let value = try sequence(parser: parser) + let value = try sequence() return .sequence(value, style: event.style.collectionStyle, tag: YAML.Tag(event.tag), anchor: event.anchor) case FYET_SCALAR: @@ -338,57 +334,6 @@ extension String { } - -private class RegEx { - - struct Options: OptionSet { - let rawValue: Int32 - - init(rawValue: Int32) { - self.rawValue = rawValue - } - - static let basic = Options([]) - static let extended = Options(rawValue: 1 << 0) - static let caseInsensitive = Options(rawValue: 1 << 1) - static let resultOnly = Options(rawValue: 1 << 2) - static let newLineSensitive = Options(rawValue: 1 << 3) - } - - struct MatchOptions: OptionSet { - let rawValue: Int32 - - init(rawValue: Int32) { - self.rawValue = rawValue - } - - static let firstCharacterNotAtBeginningOfLine = MatchOptions(rawValue: REG_NOTBOL) - static let lastCharacterNotAtEndOfLine = MatchOptions(rawValue: REG_NOTEOL) - } - - private var posixRegex = regex_t() - - init(pattern: String, options: Options = [.extended]) { - let res = pattern.withCString { patternPtr in - regcomp(&posixRegex, patternPtr, options.rawValue) - } - guard res == 0 else { - fatalError("invalid pattern") - } - } - - deinit { - regfree(&posixRegex) - } - - func matches(string: String, options: MatchOptions = []) -> Bool { - return string.withCString { stringPtr in - regexec(&posixRegex, stringPtr, 0, nil, options.rawValue) == 0 - } - } - -} - extension fy_node_style { var collectionStyle: YAML.CollectionStyle { diff --git a/Sources/PotentYAML/YAMLSchema.swift b/Sources/PotentYAML/YAMLSchema.swift new file mode 100644 index 000000000..dc287f1f5 --- /dev/null +++ b/Sources/PotentYAML/YAMLSchema.swift @@ -0,0 +1,34 @@ +// +// YAMLSchema.swift +// PotentCodables +// +// Copyright © 2019 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation + + +public protocol YAMLSchema { + + func isNull(_ string: String) -> Bool + + func isBool(_ string: String) -> Bool + func isTrue(_ string: String) -> Bool + func isFalse(_ string: String) -> Bool + + func isInt(_ string: String) -> Bool + + func isFloat(_ string: String) -> Bool + func isNumber(_ string: String) -> Bool + func isInfinity(_ string: String) -> Bool + func isNaN(_ string: String) -> Bool + + func requiresQuotes(for string: String) -> Bool + +} + +public enum YAMLSchemas { +} diff --git a/Sources/PotentYAML/YAMLSchemaCore.swift b/Sources/PotentYAML/YAMLSchemaCore.swift new file mode 100644 index 000000000..33650d738 --- /dev/null +++ b/Sources/PotentYAML/YAMLSchemaCore.swift @@ -0,0 +1,60 @@ +// +// YAMLSchemaCore.swift +// PotentCodables +// +// Copyright © 2019 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +import PotentCodables +import Regex + + +extension YAMLSchema where Self == YAMLSchemas.Core { + + public static var core: Self { .instance } +} + +extension YAMLSchemas { + + public enum Core: YAMLSchema { + case instance + + static let nullRegex: Regex = #"^((null)|(Null)|(NULL)|(~)|(^$))$"# + + static let boolRegex: Regex = .anyOf(trueRegex, falseRegex) + static let trueRegex: Regex = #"^((true)|(True)|(TRUE))$"# + static let falseRegex: Regex = #"^((false)|(False)|(FALSE))$"# + + static let int8Regex: Regex = #"^(0o[0-7]+)$"# + static let int10Regex: Regex = #"^([-+]?[0-9]+)$"# + static let int16Regex: Regex = #"^(0x[0-9a-fA-F]+)$"# + static let intRegex: Regex = .anyOf(int8Regex, int10Regex, int16Regex) + + static let numRegex: Regex = #"^([-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?)$"# + static let infRegex: Regex = #"^([-+]?((\.inf)|(\.Inf)|(\.INF)))$"# + static let nanRegex: Regex = #"^((\.nan)|(\.NaN)|(\.NAN))$"# + static let floatRegex: Regex = .anyOf(numRegex, infRegex, nanRegex) + + public func isNull(_ string: String) -> Bool { Self.nullRegex.matches(string) } + + public func isBool(_ string: String) -> Bool { Self.boolRegex.matches(string) } + public func isTrue(_ string: String) -> Bool { Self.trueRegex.matches(string) } + public func isFalse(_ string: String) -> Bool { Self.falseRegex.matches(string) } + + public func isInt(_ string: String) -> Bool { Self.intRegex.matches(string) } + + public func isFloat(_ string: String) -> Bool { Self.floatRegex.matches(string) } + public func isNumber(_ string: String) -> Bool { Self.numRegex.matches(string) } + public func isInfinity(_ string: String) -> Bool { Self.infRegex.matches(string) } + public func isNaN(_ string: String) -> Bool { Self.nanRegex.matches(string) } + + public func requiresQuotes(for string: String) -> Bool { + isNull(string) || isBool(string) || isInt(string) || isFloat(string) + } + } + +} diff --git a/Sources/PotentYAML/YAMLWriter.swift b/Sources/PotentYAML/YAMLWriter.swift index eb0e62d80..37db7f71b 100644 --- a/Sources/PotentYAML/YAMLWriter.swift +++ b/Sources/PotentYAML/YAMLWriter.swift @@ -26,6 +26,7 @@ internal struct YAMLWriter { static let `default` = Options() + var schema: YAMLSchema = .core var preferredCollectionStyle: YAML.CollectionStyle = .any var preferredStringStyle: YAML.StringStyle = .any var json: Bool = false @@ -98,9 +99,7 @@ internal struct YAMLWriter { try emit(scalar: "null", style: FYSS_PLAIN, anchor: anchor, tag: nil) case .string(let string, style: let style, tag: let tag, anchor: let anchor): - let stringStyle = style != .any ? style : options.preferredStringStyle - let scalarStyle = fy_scalar_style(rawValue: stringStyle.rawValue) - try emit(scalar: string, style: scalarStyle, anchor: anchor, tag: tag?.rawValue) + try emit(string: string, style: style, anchor: anchor, tag: tag?.rawValue) case .integer(let integer, anchor: let anchor): try emit(scalar: integer.value, style: FYSS_PLAIN, anchor: anchor, tag: nil) @@ -156,7 +155,23 @@ internal struct YAMLWriter { try emit(type: FYET_SEQUENCE_END) } + private func emit(string: String, style: YAML.StringStyle, anchor: String?, tag: String?) throws { + + let stringStyle: YAML.StringStyle + if options.schema.requiresQuotes(for: string) { + stringStyle = (options.preferredStringStyle.isQuoted ? options.preferredStringStyle : .doubleQuoted) + } + else { + stringStyle = style != .any ? style : options.preferredStringStyle + } + + let scalarStyle = fy_scalar_style(rawValue: stringStyle.rawValue) + + try emit(scalar: string, style: scalarStyle, anchor: anchor, tag: tag) + } + private func emit(scalar: String, style: fy_scalar_style, anchor: String?, tag: String?) throws { + try scalar.withCString { scalarPtr in try anchor.withCString { anchorPtr in try tag.withCString { tagPtr in diff --git a/Tests/YAMLEncoderTests.swift b/Tests/YAMLEncoderTests.swift index c7b784cef..d7c77b3fb 100644 --- a/Tests/YAMLEncoderTests.swift +++ b/Tests/YAMLEncoderTests.swift @@ -405,7 +405,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - data: \(data.hexEncodedString()) + data: "\(data.hexEncodedString())" """ @@ -1138,7 +1138,7 @@ class YAMLEncoderTests: XCTestCase { let yaml = """ - null: null + "null": null """ diff --git a/Tests/YAMLErrorTests.swift b/Tests/YAMLErrorTests.swift index e62464621..13cbd437d 100644 --- a/Tests/YAMLErrorTests.swift +++ b/Tests/YAMLErrorTests.swift @@ -16,6 +16,19 @@ import XCTest /// Tests covering *fixed* errors in libfyaml or its usage. /// class YAMLErrorTests: XCTestCase { + + public func testEmitNumberInPlainString() throws { + + let scalarYaml1: YAML = "1924." + XCTAssertEqual( + try YAMLSerialization.string(from: scalarYaml1), + #""" + "1924." + + """# + ) + } + public func testSimpleScalarNotOnDocMarkerLinePretty() throws { let scalarYaml1: YAML = "simple" diff --git a/Tests/YAMLSchemaTests.swift b/Tests/YAMLSchemaTests.swift new file mode 100644 index 000000000..e3cfa8831 --- /dev/null +++ b/Tests/YAMLSchemaTests.swift @@ -0,0 +1,89 @@ +// +// YAMLSchemaTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +@testable import PotentYAML +import XCTest + +class YAMLSchemaTests: XCTestCase { + + let schema: YAMLSchema = .core + + func testNullMatching() { + XCTAssertTrue(schema.isNull("null")) + XCTAssertTrue(schema.isNull("Null")) + XCTAssertTrue(schema.isNull("NULL")) + XCTAssertTrue(schema.isNull("~")) + XCTAssertTrue(schema.isNull("")) + + XCTAssertFalse(schema.isNull("NuLL")) + } + + func testBoolMatching() { + XCTAssertTrue(schema.isBool("true")) + XCTAssertTrue(schema.isBool("True")) + XCTAssertTrue(schema.isBool("TRUE")) + + XCTAssertFalse(schema.isBool("TRue")) + + XCTAssertTrue(schema.isBool("false")) + XCTAssertTrue(schema.isBool("False")) + XCTAssertTrue(schema.isBool("FALSE")) + + XCTAssertFalse(schema.isBool("FaLSe")) + } + + func testIntMatching() { + XCTAssertTrue(schema.isInt("0")) + XCTAssertTrue(schema.isInt("00")) + XCTAssertTrue(schema.isInt("19")) + XCTAssertTrue(schema.isInt("+19")) + XCTAssertTrue(schema.isInt("-19")) + XCTAssertTrue(schema.isInt("0o7")) + XCTAssertTrue(schema.isInt("0x3A")) + } + + func testFloatMatching() { + XCTAssertTrue(schema.isFloat("0.")) + XCTAssertTrue(schema.isFloat("+0.")) + XCTAssertTrue(schema.isFloat("-0.")) + XCTAssertTrue(schema.isFloat("0.0")) + XCTAssertTrue(schema.isFloat("+0.0")) + XCTAssertTrue(schema.isFloat("-0.0")) + XCTAssertTrue(schema.isFloat(".5")) + XCTAssertTrue(schema.isFloat("+.5")) + XCTAssertTrue(schema.isFloat("-.5")) + XCTAssertTrue(schema.isFloat("12e03")) + XCTAssertTrue(schema.isFloat("+12e03")) + XCTAssertTrue(schema.isFloat("-12e03")) + XCTAssertTrue(schema.isFloat("2E+05")) + XCTAssertTrue(schema.isFloat("+2E+05")) + XCTAssertTrue(schema.isFloat("-2E+05")) + + XCTAssertTrue(schema.isFloat(".inf")) + XCTAssertTrue(schema.isFloat("+.inf")) + XCTAssertTrue(schema.isFloat("-.inf")) + XCTAssertTrue(schema.isFloat(".Inf")) + XCTAssertTrue(schema.isFloat("+.Inf")) + XCTAssertTrue(schema.isFloat("-.Inf")) + XCTAssertTrue(schema.isFloat(".INF")) + XCTAssertTrue(schema.isFloat("+.INF")) + XCTAssertTrue(schema.isFloat("-.INF")) + + XCTAssertFalse(schema.isFloat(".inF")) + + XCTAssertTrue(schema.isFloat(".nan")) + XCTAssertTrue(schema.isFloat(".NaN")) + XCTAssertTrue(schema.isFloat(".NAN")) + + XCTAssertFalse(schema.isFloat(".naN")) + } + +} diff --git a/Tests/YAMLTests.swift b/Tests/YAMLTests.swift index 916c5d51a..4cd148b45 100644 --- a/Tests/YAMLTests.swift +++ b/Tests/YAMLTests.swift @@ -47,7 +47,7 @@ class YAMLTests: XCTestCase { XCTAssertEqual(yaml["test1"]!, nil) XCTAssertEqual(yaml["test2"]!, nil) - XCTAssertEqual(yaml["test3"]!, "") + XCTAssertEqual(yaml["test3"]!, nil) XCTAssertEqual(yaml["test4"]!, false) XCTAssertEqual(yaml["test5"]!, true) XCTAssertEqual(yaml["test6"]!, 123) From 7046ec7260d8757f0ed664351ade42047fffb5a2 Mon Sep 17 00:00:00 2001 From: Kevin Wooten Date: Sun, 9 Jun 2024 23:18:22 -0700 Subject: [PATCH 2/2] Refactor Libfyaml error handling --- Sources/PotentYAML/Libfyaml.swift | 22 ++++++++++++++++++++++ Sources/PotentYAML/YAMLReader.swift | 11 ++--------- Sources/PotentYAML/YAMLWriter.swift | 12 ++++-------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/Sources/PotentYAML/Libfyaml.swift b/Sources/PotentYAML/Libfyaml.swift index 4b04907ba..02a716b5b 100644 --- a/Sources/PotentYAML/Libfyaml.swift +++ b/Sources/PotentYAML/Libfyaml.swift @@ -91,6 +91,28 @@ internal enum Libfyaml { return diag } + internal static func hasError(in diag: OpaquePointer?) -> Bool { + + guard let diag else { + return true + } + + return fy_diag_got_error(diag) + } + + internal static func error(from diag: OpaquePointer?) -> (message: String, line: Int, column: Int)? { + + guard let diag else { + return nil + } + + var prev: UnsafeMutableRawPointer? + guard let error = fy_diag_errors_iterate(diag, &prev) else { + return nil + } + + return (String(cString: error.pointee.msg), Int(error.pointee.line), Int(error.pointee.column)) + } } diff --git a/Sources/PotentYAML/YAMLReader.swift b/Sources/PotentYAML/YAMLReader.swift index 89e4fb620..dee950326 100644 --- a/Sources/PotentYAML/YAMLReader.swift +++ b/Sources/PotentYAML/YAMLReader.swift @@ -296,18 +296,11 @@ internal struct YAMLReader { func error(fallback: Error) -> Error { - guard let diag = fy_parser_get_diag(parser) else { + guard let error = Libfyaml.error(from: fy_parser_get_diag(parser)) else { return fallback } - var prev: UnsafeMutableRawPointer? - if let error = fy_diag_errors_iterate(diag, &prev) { - return Error.parserError(message: String(cString: error.pointee.msg), - line: Int(error.pointee.line), - column: Int(error.pointee.column)) - } - - return fallback + return Error.parserError(message: error.message, line: error.line, column: error.column) } func destroy() { diff --git a/Sources/PotentYAML/YAMLWriter.swift b/Sources/PotentYAML/YAMLWriter.swift index 37db7f71b..9c32c71ab 100644 --- a/Sources/PotentYAML/YAMLWriter.swift +++ b/Sources/PotentYAML/YAMLWriter.swift @@ -198,7 +198,7 @@ internal struct YAMLWriter { private func failIfError() throws { - guard fy_diag_got_error(fy_emitter_get_diag(emitter)) else { + guard Libfyaml.hasError(in: fy_emitter_get_diag(emitter)) else { return } @@ -206,16 +206,12 @@ internal struct YAMLWriter { } private func error() -> Error { - if let diag = fy_emitter_get_diag(emitter) { - var prev: UnsafeMutableRawPointer? - if let error = fy_diag_errors_iterate(diag, &prev) { - - return Error.emitError(message: String(cString: error.pointee.msg)) - } + guard let error = Libfyaml.error(from: fy_emitter_get_diag(emitter)) else { + return .emitError(message: "Unknown emitter error") } - return .emitError(message: "Unknown emitter error") + return .emitError(message: error.message) } private static let emitOutput: Libfyaml.EmitterOutput = { _, _, str, len, userInfo in