@@ -20,6 +20,7 @@ pub struct RequestMatch180137 {
2020 pub credential_id : Uuid ,
2121 pub field_map : FieldMap ,
2222 pub requested_fields : Vec < RequestedField180137 > ,
23+ pub missing_fields : BTreeMap < String , String > ,
2324}
2425
2526uniffi:: custom_newtype!( FieldId180137 , String ) ;
7677 credentials
7778 . filter_map (
7879 |credential| match find_match ( input_descriptor, credential) {
79- Ok ( ( field_map, requested_fields) ) => Some ( Arc :: new ( RequestMatch180137 {
80- field_map,
81- requested_fields,
82- credential_id : credential. id ( ) ,
83- } ) ) ,
80+ Ok ( m) => Some ( Arc :: new ( m) ) ,
8481 Err ( e) => {
8582 tracing:: info!( "credential did not match: {e}" ) ;
8683 None
9087 . collect ( )
9188}
9289
93- fn find_match (
94- input_descriptor : & InputDescriptor ,
95- credential : & Mdoc ,
96- ) -> Result < ( FieldMap , Vec < RequestedField180137 > ) > {
90+ fn find_match ( input_descriptor : & InputDescriptor , credential : & Mdoc ) -> Result < RequestMatch180137 > {
9791 let mdoc = credential. document ( ) ;
9892
9993 if mdoc. mso . doc_type != input_descriptor. id {
@@ -152,6 +146,7 @@ fn find_match(
152146 ) ;
153147
154148 let mut requested_fields = BTreeMap :: new ( ) ;
149+ let mut missing_fields = BTreeMap :: new ( ) ;
155150
156151 let elements_json_ref = & elements_json;
157152
@@ -210,32 +205,50 @@ fn find_match(
210205 } ,
211206 ) ;
212207 }
213- None if field. is_required ( ) => bail ! (
214- "missing requested field: {}" ,
215- field. path. as_ref( ) [ 0 ] . to_string( )
216- ) ,
217- None => ( ) ,
208+ None => {
209+ let json_path = field. path . as_ref ( ) [ 0 ] . to_string ( ) ;
210+ if let Some ( ( namespace, element_identifier) ) = split_json_path ( & json_path) {
211+ missing_fields. insert ( namespace, element_identifier) ;
212+ } else {
213+ tracing:: warn!( "invalid JSON path expression: {json_path}" )
214+ }
215+ }
218216 }
219217 }
220218
221219 let mut seen_age_over_attestations = 0 ;
220+ let requested_fields = requested_fields
221+ . into_values ( )
222+ // According to the rules in ISO/IEC 18013-5 Section 7.2.5, don't respond with more
223+ // than 2 age over attestations.
224+ . filter ( |field| {
225+ if field. displayable_name . starts_with ( "age_over_" ) {
226+ seen_age_over_attestations += 1 ;
227+ seen_age_over_attestations < 3
228+ } else {
229+ true
230+ }
231+ } )
232+ . collect ( ) ;
222233
223- Ok ( (
234+ Ok ( RequestMatch180137 {
235+ credential_id : credential. id ( ) ,
224236 field_map,
225- requested_fields
226- . into_values ( )
227- // According to the rules in ISO/IEC 18013-5 Section 7.2.5, don't respond with more
228- // than 2 age over attestations.
229- . filter ( |field| {
230- if field. displayable_name . starts_with ( "age_over_" ) {
231- seen_age_over_attestations += 1 ;
232- seen_age_over_attestations < 3
233- } else {
234- true
235- }
236- } )
237- . collect ( ) ,
238- ) )
237+ requested_fields,
238+ missing_fields,
239+ } )
240+ }
241+
242+ fn split_json_path ( json_path : & str ) -> Option < ( String , String ) > {
243+ // Find the namespace between "$['" and "']['"".
244+ let ( namespace, rest) = json_path. strip_prefix ( "$['" ) ?. split_once ( "']['" ) ?;
245+ // Find the element identifier up to "']".
246+ let ( element_id, "" ) = rest. split_once ( "']" ) ? else {
247+ // Unexpected trailing characters.
248+ return None ;
249+ } ;
250+
251+ Some ( ( namespace. to_string ( ) , element_id. to_string ( ) ) )
239252}
240253
241254fn cbor_to_string ( cbor : & Cbor ) -> Option < String > {
@@ -397,13 +410,13 @@ mod test {
397410 use super :: { parse_request, reverse_mapping} ;
398411
399412 #[ rstest]
400- #[ case:: valid( "tests/examples/18013_7_presentation_definition.json" , true ) ]
401- #[ case:: invalid(
402- "tests/examples/18013_7_presentation_definition_age_over_25.json" ,
403- false
404- ) ]
413+ #[ case:: valid( "tests/examples/18013_7_presentation_definition.json" , 0 ) ]
414+ #[ case:: missing( "tests/examples/18013_7_presentation_definition_age_over_25.json" , 1 ) ]
405415 #[ tokio:: test]
406- async fn mdl_matches_presentation_definition ( #[ case] filepath : & str , #[ case] valid : bool ) {
416+ async fn mdl_matches_presentation_definition (
417+ #[ case] filepath : & str ,
418+ #[ case] missing_fields : usize ,
419+ ) {
407420 let key_manager = Arc :: new ( RustTestKeyManager :: default ( ) ) ;
408421 let key_alias = KeyAlias ( "" . to_string ( ) ) ;
409422
@@ -420,12 +433,11 @@ mod test {
420433
421434 let request = parse_request ( & presentation_definition, credentials. iter ( ) ) ;
422435
423- assert_eq ! ( request. len( ) == 1 , valid ) ;
436+ assert_eq ! ( request. len( ) , 1 ) ;
424437
425- if valid {
426- let request = & request[ 0 ] ;
427- assert_eq ! ( request. requested_fields. len( ) , 12 )
428- }
438+ let request = & request[ 0 ] ;
439+ assert_eq ! ( request. requested_fields. len( ) , 12 - missing_fields) ;
440+ assert_eq ! ( request. missing_fields. len( ) , missing_fields) ;
429441 }
430442
431443 #[ test]
@@ -444,4 +456,18 @@ mod test {
444456 _ => panic ! ( "unexpected value" ) ,
445457 } )
446458 }
459+
460+ #[ rstest]
461+ #[ case:: valid( "$['namespace']['element_id']" , true ) ]
462+ #[ case:: invalid( "$.namespace.element_id" , false ) ]
463+ #[ case:: trailing( "$['namespace']['element_id']['extra']" , false ) ]
464+ fn json_path_splitting ( #[ case] path : & str , #[ case] is_some : bool ) {
465+ let Some ( ( namespace, element_id) ) = super :: split_json_path ( path) else {
466+ assert ! ( !is_some) ;
467+ return ;
468+ } ;
469+
470+ assert_eq ! ( namespace, "namespace" ) ;
471+ assert_eq ! ( element_id, "element_id" ) ;
472+ }
447473}
0 commit comments