@@ -29,6 +29,7 @@ public enum DCQLError: Error {
2929 case emptyNamespace
3030 case emptyClaimName
3131 case emptyDocType
32+ case error( String )
3233}
3334
3435public struct CredentialQuery : Codable , Equatable , Sendable {
@@ -166,8 +167,8 @@ internal struct DCQLId {
166167 throw ValidationError . emptyValue
167168 }
168169
169- let regex = " ^[a-zA-Z0-9_-]+$ " // Alphanumeric, underscore, hyphen
170- let predicate = NSPredicate ( format: " SELF MATCHES %@ " , regex)
170+ let regex = " ^[a-zA-Z0-9_-]+$ "
171+ let predicate : NSPredicate = . init ( format: " SELF MATCHES %@ " , regex)
171172
172173 guard predicate. evaluate ( with: value) else {
173174 throw ValidationError . invalidFormat
@@ -195,6 +196,9 @@ public struct DCQL: Codable, Equatable, Sendable {
195196 if let credentialSets = credentialSets {
196197 try credentialSets. ensureValid ( knownIds: uniqueIds)
197198 }
199+
200+ try credentials. ensureFormatsValid ( )
201+
198202 self . credentials = credentials
199203 self . credentialSets = credentialSets
200204 }
@@ -225,6 +229,16 @@ public extension Credentials {
225229 guard uniqueIds. count == count else { throw DCQLError . duplicateQueryId }
226230 return uniqueIds
227231 }
232+
233+ func ensureFormatsValid( ) throws {
234+ try self . forEach { credentialQuery in
235+ let credentialQueryFormat = credentialQuery. format
236+ switch credentialQueryFormat. format {
237+ case OpenId4VPSpec . FORMAT_MSO_MDOC: try credentialQuery. claims? . forEach { try _ = $0. ensureMsoMdoc ( ) }
238+ default : try credentialQuery. claims? . forEach { try _ = $0. ensureNotMsoMdoc ( ) }
239+ }
240+ }
241+ }
228242}
229243
230244public extension CredentialSets {
@@ -248,57 +262,36 @@ public extension CredentialSetQuery {
248262
249263public struct ClaimsQuery : Codable , Equatable , Sendable {
250264 public let id : ClaimId ?
251- public let path : ClaimPath ?
265+ public let path : ClaimPath
252266 public let values : [ String ] ?
253- public let namespace : MsoMdocNamespace ?
254- public let claimName : MsoMdocClaimName ?
267+ public let intentToRetain : Bool ?
255268
256269 enum CodingKeys : String , CodingKey {
257270 case id
258271 case path
259272 case values
260- case namespace = " namespace "
261- case claimName = " claim_name "
273+ case intentToRetain = " intent_to_retain "
262274 }
263275
264276 public init (
265277 id: ClaimId ? ,
266- path: ClaimPath ? ,
278+ path: ClaimPath ,
267279 values: [ String ] ? ,
268- namespace: MsoMdocNamespace ? ,
269- claimName: MsoMdocClaimName ?
280+ intentToRetain: Bool ? = nil
270281 ) {
271282 self . id = id
272283 self . path = path
273284 self . values = values
274- self . namespace = namespace
275- self . claimName = claimName
285+ self . intentToRetain = intentToRetain
276286 }
277287
278288 public init ( from decoder: Decoder ) throws {
279289 let container = try decoder. container ( keyedBy: CodingKeys . self)
280290
281291 self . id = try container. decodeIfPresent ( ClaimId . self, forKey: . id)
282- self . path = try container. decodeIfPresent ( ClaimPath . self, forKey: . path)
292+ self . path = try container. decode ( ClaimPath . self, forKey: . path)
283293 self . values = try container. decodeIfPresent ( [ String ] . self, forKey: . values)
284- self . namespace = try container. decodeIfPresent ( MsoMdocNamespace . self, forKey: . namespace)
285- self . claimName = try container. decodeIfPresent ( MsoMdocClaimName . self, forKey: . claimName)
286-
287- if namespace == nil && claimName != nil {
288- throw DecodingError . dataCorruptedError (
289- forKey: . path,
290- in: container,
291- debugDescription: " Namespace must be present when claim name is present "
292- )
293- }
294-
295- if namespace != nil && claimName == nil {
296- throw DecodingError . dataCorruptedError (
297- forKey: . path,
298- in: container,
299- debugDescription: " Claim name must be present when namespace is present "
300- )
301- }
294+ self . intentToRetain = try container. decodeIfPresent ( Bool . self, forKey: . intentToRetain)
302295 }
303296
304297 public func encode( to encoder: Encoder ) throws {
@@ -307,112 +300,83 @@ public struct ClaimsQuery: Codable, Equatable, Sendable {
307300 try container. encodeIfPresent ( id, forKey: . id)
308301 try container. encodeIfPresent ( values, forKey: . values)
309302 try container. encodeIfPresent ( path, forKey: . path)
310-
311- // Enforce consistency: either both namespace and claimName must be present or both absent
312- switch ( namespace, claimName) {
313- case ( . some( let ns) , . some( let name) ) :
314- try container. encode ( ns, forKey: . namespace)
315- try container. encode ( name, forKey: . claimName)
316- case ( nil , nil ) : break
317- default :
318- throw EncodingError . invalidValue (
319- self ,
320- EncodingError . Context (
321- codingPath: container. codingPath,
322- debugDescription: " Namespace and claim name must either both be present or both be nil. "
323- )
324- )
325- }
303+ try container. encodeIfPresent ( intentToRetain, forKey: . intentToRetain)
326304 }
327305
328306 public static func sdJwtVc(
329307 id: ClaimId ? = nil ,
330308 path: ClaimPath ,
331309 values: [ String ] ? = nil
332- ) -> ClaimsQuery {
333- ClaimsQuery (
310+ ) throws -> ClaimsQuery {
311+ try ClaimsQuery (
334312 id: id,
335313 path: path,
314+ values: values
315+ ) . ensureNotMsoMdoc ( )
316+ }
317+
318+ public static func mdoc(
319+ id: ClaimId ? = nil ,
320+ values: [ String ] ? = nil ,
321+ namespace: String ,
322+ claimName: String ,
323+ intentToRetain: Bool ? = nil
324+ ) throws -> ClaimsQuery {
325+ try ClaimsQuery (
326+ id: id,
327+ path: . claim( namespace) . claim ( claimName) ,
336328 values: values,
337- namespace: nil ,
338- claimName: nil
339- )
329+ intentToRetain: intentToRetain
330+ ) . ensureMsoMdoc ( )
340331 }
341332
342333 public static func mdoc(
343334 id: ClaimId ? = nil ,
344335 values: [ String ] ? = nil ,
345- namespace : MsoMdocNamespace ,
346- claimName : MsoMdocClaimName
347- ) -> ClaimsQuery {
348- ClaimsQuery (
336+ path : ClaimPath ,
337+ intentToRetain : Bool ? = nil
338+ ) throws -> ClaimsQuery {
339+ try ClaimsQuery (
349340 id: id,
350- path: nil ,
341+ path: path ,
351342 values: values,
352- namespace: namespace,
353- claimName: claimName
354- )
343+ intentToRetain: intentToRetain
344+ ) . ensureMsoMdoc ( )
355345 }
356346}
357347
358- public struct MsoMdocNamespace : Codable , CustomStringConvertible , Equatable , Sendable {
359- public let namespace : String
348+ extension ClaimsQuery {
360349
361- public init ( _ namespace: String ) throws {
362- guard !namespace. isEmpty else {
363- throw DCQLError . emptyNamespace
350+ func ensureMsoMdoc( ) throws -> ClaimsQuery {
351+ if path. value. count != 2 {
352+ throw DCQLError . error (
353+ " Claim paths for mso mdoc based must have exactly two elements "
354+ )
364355 }
365- self . namespace = namespace
366- }
367-
368- enum CodingKeys : String , CodingKey {
369- case namespace = " namespace "
370- }
371-
372- public var description : String {
373- return namespace
374- }
375-
376- public init ( from decoder: Decoder ) throws {
377- let container = try decoder. singleValueContainer ( )
378- let namespace = try container. decode ( String . self)
379-
380- if namespace. isEmpty {
381- throw DCQLError . emptyNamespace
356+
357+ let claimsSatisfy = path. value. allSatisfy { element in
358+ switch element {
359+ case . claim:
360+ return true
361+ default :
362+ return false
363+ }
382364 }
383-
384- self . namespace = namespace
385- }
386- }
387-
388- public struct MsoMdocClaimName : Codable , CustomStringConvertible , Equatable , Sendable {
389-
390- public let claimName : String
391-
392- public init ( claimName: String ) throws {
393- guard !claimName. isEmpty else {
394- throw DCQLError . emptyClaimName
365+ if !claimsSatisfy {
366+ throw DCQLError . error (
367+ " ClaimPaths for MSO MDoc based formats must contain only Claim ClaimPathElements "
368+ )
395369 }
396- self . claimName = claimName
370+ return self
397371 }
398372
399- public var description : String {
400- return claimName
401- }
402-
403- enum CodingKeys : String , CodingKey {
404- case claimName = " claim_name "
405- }
406-
407- public init ( from decoder: Decoder ) throws {
408- let container = try decoder. singleValueContainer ( )
409- let decodedValue = try container. decode ( String . self)
410-
411- guard !decodedValue. isEmpty else {
412- throw DCQLError . emptyClaimName
373+ func ensureNotMsoMdoc( ) throws -> ClaimsQuery {
374+ if intentToRetain != nil {
375+ throw DCQLError . error (
376+ " \( OpenId4VPSpec . DCQL_MSO_MDOC_INTENT_TO_RETAIN) can be used only with msp mdoc based formats "
377+ )
413378 }
414-
415- self . claimName = decodedValue
379+ return self
416380 }
417381}
418382
@@ -457,14 +421,3 @@ public struct MsoMdocDocType: CustomStringConvertible {
457421 return value
458422 }
459423}
460-
461- public struct MsoMdocClaimsQueryExtension : Decodable {
462-
463- public let namespace : MsoMdocNamespace ?
464- public let claimName : MsoMdocClaimName ?
465-
466- enum CodingKeys : String , CodingKey {
467- case namespace
468- case claimName = " claim_name "
469- }
470- }
0 commit comments