diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 526c754..cb393e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,14 +12,14 @@ on: jobs: macos: - runs-on: macOS-latest + runs-on: macOS-13 steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 with: fetch-depth: 1 - name: SPM tests - run: swift test --enable-code-coverage --sanitize=thread + run: swift test --enable-code-coverage - name: Convert coverage files run: | xcrun llvm-cov export -format "lcov" \ @@ -27,7 +27,7 @@ jobs: -ignore-filename-regex="\/Tests\/" \ -instr-profile=.build/debug/codecov/default.profdata > info.lcov - name: Upload to codecov.io - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v4 with: file: info.lcov @@ -36,14 +36,15 @@ jobs: strategy: matrix: tag: - - swift:5.6 - - swift:5.7 - swift:5.8 + - swift:5.9 + - swift:5.10 + - swiftlang/swift:nightly-6.0-jammy container: image: ${{ matrix.tag }} steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 with: fetch-depth: 1 - name: Test @@ -55,6 +56,6 @@ jobs: -ignore-filename-regex="\/Tests\/" \ -instr-profile .build/debug/codecov/default.profdata > info.lcov - name: Upload to codecov.io - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v4 with: file: info.lcov diff --git a/.gitignore b/.gitignore index 9365cec..81cda61 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /.build /.swiftpm /.vscode +/.devcontainer /Packages /*.xcodeproj /docs diff --git a/Sources/JMESPath/Variable.swift b/Sources/JMESPath/Variable.swift index 5e10f94..b4863b3 100644 --- a/Sources/JMESPath/Variable.swift +++ b/Sources/JMESPath/Variable.swift @@ -1,4 +1,3 @@ -import CoreFoundation import Foundation public typealias JMESArray = [Any] @@ -27,7 +26,7 @@ public enum JMESVariable { case let number as NSNumber: // both booleans and integer/float point types can be converted to a `NSNumber` // We have to check to see the type id to see if it is a boolean - if CFGetTypeID(number) == CFBooleanGetTypeID() { + if type(of: number) == Self.nsNumberBoolType { self = .boolean(number.boolValue) } else { self = .number(number) @@ -56,7 +55,9 @@ public enum JMESVariable { case .dictionary: var object: JMESObject = [:] var index: Int = 0 - while let key = mirror.descendant(index, "key") as? String, let value = mirror.descendant(index, "value") { + while let key = mirror.descendant(index, "key") as? String, + let value = mirror.descendant(index, "value") + { object[key] = Self.unwrap(value) ?? NSNull() index += 1 } @@ -115,12 +116,18 @@ public enum JMESVariable { case .boolean(let bool): return String(describing: bool) case .array(let array): - guard let jsonData = try? JSONSerialization.data(withJSONObject: array, options: [.fragmentsAllowed]) else { + guard + let jsonData = try? JSONSerialization.data( + withJSONObject: array, options: [.fragmentsAllowed]) + else { return nil } return String(decoding: jsonData, as: Unicode.UTF8.self) case .object(let object): - guard let jsonData = try? JSONSerialization.data(withJSONObject: object, options: [.fragmentsAllowed]) else { + guard + let jsonData = try? JSONSerialization.data( + withJSONObject: object, options: [.fragmentsAllowed]) + else { return nil } return String(decoding: jsonData, as: Unicode.UTF8.self) @@ -148,12 +155,12 @@ public enum JMESVariable { public func isSameType(as variable: JMESVariable) -> Bool { switch (self, variable) { case (.null, .null), - (.string, .string), - (.boolean, .boolean), - (.number, .number), - (.array, .array), - (.object, .object), - (.expRef, .expRef): + (.string, .string), + (.boolean, .boolean), + (.number, .number), + (.array, .array), + (.object, .object), + (.expRef, .expRef): return true default: return false @@ -259,6 +266,8 @@ public enum JMESVariable { guard let first = mirror.children.first else { return nil } return first.value } + + fileprivate static var nsNumberBoolType = type(of: NSNumber(value: true)) } extension JMESVariable: Equatable { @@ -304,7 +313,9 @@ extension JMESObject { fileprivate func equalTo(_ rhs: JMESObject) -> Bool { guard self.count == rhs.count else { return false } for element in self { - guard let rhsValue = rhs[element.key], JMESVariable(from: rhsValue) == JMESVariable(from: element.value) else { + guard let rhsValue = rhs[element.key], + JMESVariable(from: rhsValue) == JMESVariable(from: element.value) + else { return false } } diff --git a/Tests/JMESPathTests/ComplianceTests.swift b/Tests/JMESPathTests/ComplianceTests.swift index e2ed76a..1542891 100644 --- a/Tests/JMESPathTests/ComplianceTests.swift +++ b/Tests/JMESPathTests/ComplianceTests.swift @@ -6,13 +6,13 @@ // import Foundation +import XCTest + +@testable import JMESPath -import Foundation #if os(Linux) -import FoundationNetworking + import FoundationNetworking #endif -@testable import JMESPath -import XCTest public struct AnyDecodable: Decodable { public let value: Any @@ -22,8 +22,8 @@ public struct AnyDecodable: Decodable { } } -public extension AnyDecodable { - init(from decoder: Decoder) throws { +extension AnyDecodable { + public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if container.decodeNil() { @@ -43,7 +43,8 @@ public extension AnyDecodable { } else if let dictionary = try? container.decode([String: AnyDecodable].self) { self.init(dictionary.mapValues { $0.value }) } else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "AnyDecodable value cannot be decoded") + throw DecodingError.dataCorruptedError( + in: container, debugDescription: "AnyDecodable value cannot be decoded") } } } @@ -67,7 +68,7 @@ final class ComplianceTests: XCTestCase { @available(iOS 11.0, tvOS 11.0, watchOS 5.0, *) func run() throws { for c in self.cases { - if let _ = c.bench { + if c.bench != nil { self.testBenchmark(c) } else if let error = c.error { self.testError(c, error: error) @@ -106,11 +107,13 @@ final class ComplianceTests: XCTestCase { let expression = try JMESExpression.compile(c.expression) let resultJson: String? = try result.map { - let data = try JSONSerialization.data(withJSONObject: $0, options: [.fragmentsAllowed, .sortedKeys]) + let data = try JSONSerialization.data( + withJSONObject: $0, options: [.fragmentsAllowed, .sortedKeys]) return String(decoding: data, as: Unicode.UTF8.self) } if let value = try expression.search(object: self.given.value) { - let valueData = try JSONSerialization.data(withJSONObject: value, options: [.fragmentsAllowed, .sortedKeys]) + let valueData = try JSONSerialization.data( + withJSONObject: value, options: [.fragmentsAllowed, .sortedKeys]) let valueJson = String(decoding: valueData, as: Unicode.UTF8.self) XCTAssertEqual(resultJson, valueJson) } else { @@ -124,7 +127,8 @@ final class ComplianceTests: XCTestCase { @available(iOS 11.0, tvOS 11.0, watchOS 5.0, *) func output(_ c: Case, expected: String?, result: String?) { if expected != result { - let data = try! JSONSerialization.data(withJSONObject: self.given.value, options: [.fragmentsAllowed, .sortedKeys]) + let data = try! JSONSerialization.data( + withJSONObject: self.given.value, options: [.fragmentsAllowed, .sortedKeys]) let givenJson = String(decoding: data, as: Unicode.UTF8.self) if let comment = c.comment { print("Comment: \(comment)") @@ -137,13 +141,20 @@ final class ComplianceTests: XCTestCase { } } - func testCompliance(name: String, ignoring: [String] = []) throws { - let url = URL(string: "https://raw.githubusercontent.com/jmespath/jmespath.test/master/tests/\(name).json")! - try testCompliance(url: url, ignoring: ignoring) + func testCompliance(name: String, ignoring: [String] = []) async throws { + let url = URL( + string: + "https://raw.githubusercontent.com/jmespath/jmespath.test/master/tests/\(name).json" + )! + try await testCompliance(url: url, ignoring: ignoring) } - func testCompliance(url: URL, ignoring: [String] = []) throws { - let data = try Data(contentsOf: url) + func testCompliance(url: URL, ignoring: [String] = []) async throws { + #if compiler(>=6.0) + let (data, _) = try await URLSession.shared.data(from: url, delegate: nil) + #else + let data = try Data(contentsOf: url) + #endif let tests = try JSONDecoder().decode([ComplianceTest].self, from: data) if #available(iOS 11.0, tvOS 11.0, watchOS 5.0, *) { @@ -153,67 +164,67 @@ final class ComplianceTests: XCTestCase { } } - func testBasic() throws { - try self.testCompliance(name: "basic") + func testBasic() async throws { + try await self.testCompliance(name: "basic") } - func testBenchmarks() throws { - try self.testCompliance(name: "benchmarks") + func testBenchmarks() async throws { + try await self.testCompliance(name: "benchmarks") } - func testBoolean() throws { - try self.testCompliance(name: "boolean") + func testBoolean() async throws { + try await self.testCompliance(name: "boolean") } - func testCurrent() throws { - try self.testCompliance(name: "current") + func testCurrent() async throws { + try await self.testCompliance(name: "current") } - func testEscape() throws { - try self.testCompliance(name: "escape") + func testEscape() async throws { + try await self.testCompliance(name: "escape") } - func testFilters() throws { - try self.testCompliance(name: "filters") + func testFilters() async throws { + try await self.testCompliance(name: "filters") } - func testFunctions() throws { - try self.testCompliance(name: "functions") + func testFunctions() async throws { + try await self.testCompliance(name: "functions") } - func testIdentifiers() throws { - try self.testCompliance(name: "identifiers") + func testIdentifiers() async throws { + try await self.testCompliance(name: "identifiers") } - func testIndices() throws { - try self.testCompliance(name: "indices") + func testIndices() async throws { + try await self.testCompliance(name: "indices") } - func testLiteral() throws { - try self.testCompliance(name: "literal") + func testLiteral() async throws { + try await self.testCompliance(name: "literal") } - func testMultiSelect() throws { - try self.testCompliance(name: "multiselect") + func testMultiSelect() async throws { + try await self.testCompliance(name: "multiselect") } - func testPipe() throws { - try self.testCompliance(name: "pipe") + func testPipe() async throws { + try await self.testCompliance(name: "pipe") } - func testSlice() throws { - try self.testCompliance(name: "slice") + func testSlice() async throws { + try await self.testCompliance(name: "slice") } - func testSyntax() throws { - try self.testCompliance(name: "syntax") + func testSyntax() async throws { + try await self.testCompliance(name: "syntax") } - func testUnicode() throws { - try self.testCompliance(name: "unicode") + func testUnicode() async throws { + try await self.testCompliance(name: "unicode") } - func testWildcards() throws { - try self.testCompliance(name: "wildcard") + func testWildcards() async throws { + try await self.testCompliance(name: "wildcard") } }