diff --git a/.github/workflows/build-package.yml b/.github/workflows/build-package.yml index 56cdb29..3bf7507 100644 --- a/.github/workflows/build-package.yml +++ b/.github/workflows/build-package.yml @@ -8,11 +8,11 @@ on: tags: [ v* ] jobs: build: - runs-on: "macos-13" + runs-on: "macos-14" steps: - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '14.3.1' + xcode-version: '16.0' - uses: actions/checkout@v4 - run: fastlane tests diff --git a/Package.swift b/Package.swift index 981f1a0..035e11b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.8 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription diff --git a/Sources/Claim/Claim.swift b/Sources/Claim/Claim.swift index b0ff2e6..a77acd4 100644 --- a/Sources/Claim/Claim.swift +++ b/Sources/Claim/Claim.swift @@ -14,16 +14,17 @@ * limitations under the License. */ import Foundation +@preconcurrency import SwiftyJSON -public struct Claim { +public struct Claim: Sendable { public let id: ClaimId public let format: String - public let jsonObject: JSONObject + public let jsonObject: JSON public init( id: ClaimId, format: String, - jsonObject: JSONObject + jsonObject: JSON ) { self.id = id self.format = format diff --git a/Sources/Matching/Matcher.swift b/Sources/Matching/Matcher.swift index 7fe7f42..55fd4ba 100644 --- a/Sources/Matching/Matcher.swift +++ b/Sources/Matching/Matcher.swift @@ -134,7 +134,7 @@ private extension PresentationMatcher { with field: Field ) -> CandidateField { for path in field.paths { - let json = claim.jsonObject.toJSONString() + let json = try? claim.jsonObject.toJSONString() if let values = json?.query(values: path)?.compactMap({ $0 }), let value = values.first as? String, filter( @@ -157,12 +157,15 @@ private extension PresentationMatcher { // Note: the JSONSchema validation library does not support // date validation as of 0.6.0 - if let date = filter["format"] as? String, date == "date" { + if let date = filter["format"].string, date == "date" { return value.isValidDate() } do { - let result = try JSONSchema.validate(value, schema: filter) + let result = try JSONSchema.validate( + value, + schema: filter.dictionaryObject ?? [:] + ) return result.valid } catch { @@ -236,7 +239,7 @@ extension PresentationMatcher: EvaluatorType { candidateClaims: InputDescriptorEvaluationPerClaim, notMatchingClaims: InputDescriptorEvaluationPerClaim ) -> Match { - if let submissionRequirements = definition.submissionRequirements { + if definition.submissionRequirements != nil { return .notMatched(details: [:]) } else { diff --git a/Sources/PresentationDefinition/Field.swift b/Sources/PresentationDefinition/Field.swift index 4217d95..a167660 100644 --- a/Sources/PresentationDefinition/Field.swift +++ b/Sources/PresentationDefinition/Field.swift @@ -14,10 +14,11 @@ * limitations under the License. */ import Foundation +import SwiftyJSON public struct Field: Codable, Hashable { public let paths: [String] - public let filter: JSONObject? + public let filter: JSON? public let purpose: String? public let intentToRetain: Bool? public let optional: Bool? @@ -30,7 +31,7 @@ public struct Field: Codable, Hashable { public init( paths: [String], - filter: JSONObject?, + filter: JSON?, purpose: String?, intentToRetain: Bool?, optional: Bool?) { @@ -44,7 +45,7 @@ public struct Field: Codable, Hashable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) paths = try container.decode([String].self, forKey: .path) - filter = try? container.decode(JSONObject.self, forKey: .filter) + filter = try? container.decode(JSON.self, forKey: .filter) purpose = try? container.decode(String.self, forKey: .purpose) intentToRetain = try? container.decode(Bool.self, forKey: .intentToRetain) optional = try? container.decode(Bool.self, forKey: .optional) @@ -62,11 +63,8 @@ public struct Field: Codable, Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(paths) if let filter = filter { - for (key, value) in filter { + for (key, _) in filter { hasher.combine(key) - if let value = value as? String { - hasher.combine(value) - } } } hasher.combine(purpose) @@ -77,6 +75,6 @@ public struct Field: Codable, Hashable { return lhs.paths == rhs.paths && lhs.purpose == rhs.purpose && lhs.intentToRetain == rhs.intentToRetain && - lhs.filter ?? JSONObject() == rhs.filter ?? JSONObject() + lhs.filter ?? JSON() == rhs.filter ?? JSON() } } diff --git a/Sources/PresentationDefinition/PresentationAliases.swift b/Sources/PresentationDefinition/PresentationAliases.swift index 171f78d..c039119 100644 --- a/Sources/PresentationDefinition/PresentationAliases.swift +++ b/Sources/PresentationDefinition/PresentationAliases.swift @@ -14,8 +14,9 @@ * limitations under the License. */ import Foundation +import SwiftyJSON -public typealias Filter = JSONObject +public typealias Filter = JSON public typealias ClaimId = String public typealias Purpose = String public typealias Name = String diff --git a/Sources/PresentationDefinition/PresentationDefinitionSource.swift b/Sources/PresentationDefinition/PresentationDefinitionSource.swift index 0971099..e754713 100644 --- a/Sources/PresentationDefinition/PresentationDefinitionSource.swift +++ b/Sources/PresentationDefinition/PresentationDefinitionSource.swift @@ -14,6 +14,7 @@ * limitations under the License. */ import Foundation +import SwiftyJSON public enum PresentationDefinitionSource { case passByValue(presentationDefinition: PresentationDefinition) @@ -22,17 +23,17 @@ public enum PresentationDefinitionSource { } public extension PresentationDefinitionSource { - init(authorizationRequestObject: JSONObject) throws { - if let presentationDefinitionObject = authorizationRequestObject[Constants.PRESENTATION_DEFINITION] as? JSONObject { + init(authorizationRequestObject: JSON) throws { + if let presentationDefinitionObject = authorizationRequestObject[Constants.PRESENTATION_DEFINITION].dictionary { let jsonData = try JSONSerialization.data(withJSONObject: presentationDefinitionObject, options: []) let presentationDefinition = try JSONDecoder().decode(PresentationDefinition.self, from: jsonData) self = .passByValue(presentationDefinition: presentationDefinition) - } else if let uri = authorizationRequestObject[Constants.PRESENTATION_DEFINITION_URI] as? String, + } else if let uri = authorizationRequestObject[Constants.PRESENTATION_DEFINITION_URI].string, let uri = URL(string: uri) { self = .fetchByReference(url: uri) - } else if let scope = authorizationRequestObject[Constants.SCOPE] as? String, + } else if let scope = authorizationRequestObject[Constants.SCOPE].string, !scope.components(separatedBy: " ").isEmpty { self = .implied(scope: scope.components(separatedBy: " ")) diff --git a/Sources/Support/Encodable+Extensions.swift b/Sources/Support/Encodable+Extensions.swift new file mode 100644 index 0000000..9615dd0 --- /dev/null +++ b/Sources/Support/Encodable+Extensions.swift @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 European Commission + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import Foundation + +extension Encodable { + func toJSONString(outputFormatting: JSONEncoder.OutputFormatting = .prettyPrinted) throws -> String { + let encoder = JSONEncoder() + encoder.outputFormatting = outputFormatting + + let jsonData = try encoder.encode(self) + + if let jsonString = String(data: jsonData, encoding: .utf8) { + return jsonString + } else { + throw PresentationError.invalidFormat + } + } +} diff --git a/Sources/Support/JSONCodingKeys.swift b/Sources/Support/JSONCodingKeys.swift index fece47c..0711282 100644 --- a/Sources/Support/JSONCodingKeys.swift +++ b/Sources/Support/JSONCodingKeys.swift @@ -15,8 +15,6 @@ */ import Foundation -public typealias JSONObject = [String: Any] - public struct JSONCodingKeys: CodingKey { public var stringValue: String diff --git a/Tests/EntitiesTests.swift b/Tests/EntitiesTests.swift index 607d0be..ae61c77 100644 --- a/Tests/EntitiesTests.swift +++ b/Tests/EntitiesTests.swift @@ -14,6 +14,8 @@ * limitations under the License. */ import XCTest +import SwiftyJSON + @testable import PresentationExchange class EntitiesTests: XCTestCase { @@ -262,7 +264,7 @@ class AuthorizationRequestUnprocessedDataTests: XCTestCase { final class PresentationDefinitionSourceTests: XCTestCase { func testInitFromAuthorizationRequestObjectWithPresentationDefinitionUri() throws { - let authorizationRequestObject: JSONObject = [ + let authorizationRequestObject: JSON = [ "presentation_definition_uri": "https://example.com/presentation-definition" ] @@ -277,7 +279,7 @@ final class PresentationDefinitionSourceTests: XCTestCase { } func testInitFromAuthorizationRequestObjectWithScope() throws { - let authorizationRequestObject: JSONObject = [ + let authorizationRequestObject: JSON = [ "scope": "openid email profile" ] @@ -292,7 +294,7 @@ final class PresentationDefinitionSourceTests: XCTestCase { } func testInitFromAuthorizationRequestObjectWithInvalidPresentationDefinition() { - let authorizationRequestObject: JSONObject = [:] + let authorizationRequestObject: JSON = [:] XCTAssertThrowsError(try PresentationDefinitionSource(authorizationRequestObject: authorizationRequestObject)) { error in XCTAssertEqual(error as? PresentationError, PresentationError.invalidPresentationDefinition)