Skip to content

Commit bfe929e

Browse files
author
Oleksandr Abakumov
committed
feat: add structured error codes to WalletError for DCQL query failures
1 parent a08abd3 commit bfe929e

File tree

2 files changed

+29
-7
lines changed

2 files changed

+29
-7
lines changed

Sources/EudiWalletKit/Models/WalletError.swift

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,34 @@
1717
import Foundation
1818
/// Wallet error
1919
public struct WalletError: LocalizedError {
20+
/// Structured error code for programmatic handling
21+
public enum Code: String, Sendable {
22+
/// The verifier requested a claim that is not present in the credential
23+
case claimNotFound
24+
/// The verifier requested a credential/document type that is not in the wallet
25+
case credentialNotFound
26+
/// The verifier requested a claim whose value does not match
27+
case claimValueMismatch
28+
/// No claim_set option could be satisfied
29+
case claimSetNotSatisfied
30+
/// A required credential_set cannot be satisfied
31+
case credentialSetNotSatisfied
32+
/// The DCQL query could not be satisfied (general)
33+
case dcqlQueryNotSatisfied
34+
}
35+
2036
public let description: String
2137
public let localizationKey: String?
38+
/// Structured error code for programmatic handling. `nil` for legacy errors.
39+
public let code: Code?
40+
/// Additional context about the error (e.g. claim path, docType).
41+
public let context: [String: String]
2242

23-
public init(description: String, localizationKey: String? = nil) {
43+
public init(description: String, localizationKey: String? = nil, code: Code? = nil, context: [String: String] = [:]) {
2444
self.description = description
2545
self.localizationKey = localizationKey
46+
self.code = code
47+
self.context = context
2648
}
2749

2850
public var errorDescription: String? {

Sources/EudiWalletKit/Services/Openid4VpUtils.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ extension OpenId4VpUtils {
217217
let format = credQuery.dataFormat
218218
// Find matching credentials
219219
let matchingCredIds = queryable.getCredentials(docOrVctType: docType, docDataFormat: format)
220-
if matchingCredIds.isEmpty, dcql.credentialSets == nil { throw WalletError(description: "Credential with docType \(docType) cannot be found.") }
220+
if matchingCredIds.isEmpty, dcql.credentialSets == nil { throw WalletError(description: "Credential with docType \(docType) cannot be found.", code: .credentialNotFound, context: ["docType": docType]) }
221221
// Try to find a credential that satisfies the claim requirements
222222
for credId in matchingCredIds {
223223
do {
@@ -259,7 +259,7 @@ extension OpenId4VpUtils {
259259
}
260260
let isSetRequired = credSet.required ?? CredentialSetQuery.defaultRequiredValue
261261
if isSetRequired, !isSetSatisfied {
262-
throw WalletError(description: "Required credential_set \(credSet.options) cannot be satisfied")
262+
throw WalletError(description: "Required credential_set \(credSet.options) cannot be satisfied", code: .credentialSetNotSatisfied)
263263
}
264264
}
265265
} else {
@@ -270,7 +270,7 @@ extension OpenId4VpUtils {
270270
if result.isEmpty {
271271
let notFoundCred = dcql.credentials.first { c in credentialQueryResults[c.id] == nil }
272272
if let notFoundCred {logger.warning("No credential found matching docType: \(notFoundCred.docType ?? "") with format: \(notFoundCred.format)")}
273-
throw lastError ?? WalletError(description: "DCQL query could not be satisfied")
273+
throw lastError ?? WalletError(description: "DCQL query could not be satisfied", code: .dcqlQueryNotSatisfied)
274274
}
275275
return result
276276
}
@@ -315,7 +315,7 @@ extension OpenId4VpUtils {
315315
} // next claimSetOption
316316
// No claim set option could be satisfied
317317
let claimPathStr = firstMissingClaimInOption?.value.map(\.claimName).joined(separator: "/") ?? "unknown"
318-
throw WalletError(description: "No claim_set option satisfied. First missing claim: \(claimPathStr)")
318+
throw WalletError(description: "No claim_set option satisfied. First missing claim: \(claimPathStr)", code: .claimSetNotSatisfied, context: ["claimPath": claimPathStr])
319319
} else {
320320
// No claim_sets: all claims must be available
321321
var selectedPaths: [ClaimsQuery] = []
@@ -324,11 +324,11 @@ extension OpenId4VpUtils {
324324
if let values = claim.values, !values.isEmpty {
325325
if !queryable.hasClaimWithValue(id: credId, claimPath: claim.path, values: values) {
326326
let claimPathStr = claim.path.value.map(\.claimName).joined(separator: "/")
327-
throw WalletError(description: "Claim value mismatch for: \(claimPathStr)")
327+
throw WalletError(description: "Claim value mismatch for: \(claimPathStr)", code: .claimValueMismatch, context: ["claimPath": claimPathStr])
328328
}
329329
} else if !queryable.hasClaim(id: credId, claimPath: claim.path) {
330330
let claimPathStr = claim.path.value.map(\.claimName).joined(separator: "/")
331-
throw WalletError(description: "Claim not found: \(claimPathStr)")
331+
throw WalletError(description: "Claim not found: \(claimPathStr)", code: .claimNotFound, context: ["claimPath": claimPathStr])
332332
}
333333
selectedPaths.append(claim)
334334
}

0 commit comments

Comments
 (0)