@@ -63,65 +63,6 @@ const CATEGORY_ID_KEY: &str = "category_id";
63
63
#[ derive( Clone , Debug , PartialEq , Default ) ]
64
64
pub struct Metadata ( BTreeMap < SupportedLabel , SupportedField > ) ;
65
65
66
- /// An actual representation of all metadata fields.
67
- // TODO: this is maintained as an implementation of `serde` and `coset` for `Metadata`
68
- // and should be removed in case `serde` and `coset` are deprecated completely.
69
- #[ derive( Clone , Debug , PartialEq , serde:: Deserialize , Default ) ]
70
- pub ( crate ) struct InnerMetadata {
71
- /// Document Type, list of `UUIDv4`.
72
- #[ serde( rename = "type" ) ]
73
- doc_type : Option < DocType > ,
74
- /// Document ID `UUIDv7`.
75
- id : Option < UuidV7 > ,
76
- /// Document Version `UUIDv7`.
77
- ver : Option < UuidV7 > ,
78
- /// Document Payload Content Type.
79
- #[ serde( rename = "content-type" ) ]
80
- content_type : Option < ContentType > ,
81
- /// Document Payload Content Encoding.
82
- #[ serde( rename = "content-encoding" ) ]
83
- content_encoding : Option < ContentEncoding > ,
84
- /// Reference to the latest document.
85
- #[ serde( rename = "ref" , skip_serializing_if = "Option::is_none" ) ]
86
- doc_ref : Option < DocumentRef > ,
87
- /// Reference to the document template.
88
- #[ serde( skip_serializing_if = "Option::is_none" ) ]
89
- template : Option < DocumentRef > ,
90
- /// Reference to the document reply.
91
- #[ serde( skip_serializing_if = "Option::is_none" ) ]
92
- reply : Option < DocumentRef > ,
93
- /// Reference to the document section.
94
- #[ serde( skip_serializing_if = "Option::is_none" ) ]
95
- section : Option < Section > ,
96
- /// Reference to the document collaborators. Collaborator type is TBD.
97
- #[ serde( default = "Vec::new" , skip_serializing_if = "Vec::is_empty" ) ]
98
- collabs : Vec < String > ,
99
- /// Reference to the parameters document.
100
- #[ serde( skip_serializing_if = "Option::is_none" ) ]
101
- parameters : Option < DocumentRef > ,
102
- }
103
-
104
- impl InnerMetadata {
105
- /// Converts into an iterator over present fields fields.
106
- fn into_iter ( self ) -> impl Iterator < Item = SupportedField > {
107
- [
108
- self . doc_type . map ( SupportedField :: Type ) ,
109
- self . id . map ( SupportedField :: Id ) ,
110
- self . ver . map ( SupportedField :: Ver ) ,
111
- self . content_type . map ( SupportedField :: ContentType ) ,
112
- self . content_encoding . map ( SupportedField :: ContentEncoding ) ,
113
- self . doc_ref . map ( SupportedField :: Ref ) ,
114
- self . template . map ( SupportedField :: Template ) ,
115
- self . reply . map ( SupportedField :: Reply ) ,
116
- self . section . map ( SupportedField :: Section ) ,
117
- ( !self . collabs . is_empty ( ) ) . then_some ( SupportedField :: Collabs ( self . collabs ) ) ,
118
- self . parameters . map ( SupportedField :: Parameters ) ,
119
- ]
120
- . into_iter ( )
121
- . flatten ( )
122
- }
123
- }
124
-
125
66
impl Metadata {
126
67
/// Return Document Type `DocType` - a list of `UUIDv4`.
127
68
///
@@ -233,42 +174,52 @@ impl Metadata {
233
174
}
234
175
235
176
/// Build `Metadata` object from the metadata fields, doing all necessary validation.
236
- pub ( crate ) fn from_metadata_fields ( metadata : InnerMetadata , report : & ProblemReport ) -> Self {
237
- if metadata. doc_type . is_none ( ) {
238
- report. missing_field ( "type" , "Missing type field in COSE protected header" ) ;
177
+ pub ( crate ) fn from_fields ( fields : Vec < SupportedField > , report : & ProblemReport ) -> Self {
178
+ const REPORT_CONTEXT : & str = "Metadata building" ;
179
+
180
+ let mut metadata = Metadata ( BTreeMap :: new ( ) ) ;
181
+ for v in fields {
182
+ let k = v. discriminant ( ) ;
183
+ if metadata. 0 . insert ( k, v) . is_some ( ) {
184
+ report. duplicate_field (
185
+ & k. to_string ( ) ,
186
+ "Duplicate metadata fields are not allowed" ,
187
+ REPORT_CONTEXT ,
188
+ ) ;
189
+ }
239
190
}
240
- if metadata. id . is_none ( ) {
241
- report. missing_field ( "id" , "Missing id field in COSE protected header" ) ;
191
+
192
+ if metadata. doc_type ( ) . is_err ( ) {
193
+ report. missing_field ( "type" , REPORT_CONTEXT ) ;
242
194
}
243
- if metadata. ver . is_none ( ) {
244
- report. missing_field ( "ver " , "Missing ver field in COSE protected header" ) ;
195
+ if metadata. doc_id ( ) . is_err ( ) {
196
+ report. missing_field ( "id " , REPORT_CONTEXT ) ;
245
197
}
246
-
247
- if metadata. content_type . is_none ( ) {
248
- report. missing_field (
249
- "content type" ,
250
- "Missing content_type field in COSE protected header" ,
251
- ) ;
198
+ if metadata. doc_ver ( ) . is_err ( ) {
199
+ report. missing_field ( "ver" , REPORT_CONTEXT ) ;
200
+ }
201
+ if metadata. content_type ( ) . is_err ( ) {
202
+ report. missing_field ( "content-type" , REPORT_CONTEXT ) ;
252
203
}
253
204
254
- Self (
255
- metadata
256
- . into_iter ( )
257
- . map ( |field| ( field. discriminant ( ) , field) )
258
- . collect ( ) ,
259
- )
205
+ metadata
260
206
}
261
207
262
- /// Converting COSE Protected Header to Metadata.
263
- pub ( crate ) fn from_protected_header (
264
- protected : & coset:: ProtectedHeader , context : & mut DecodeContext ,
265
- ) -> Self {
266
- let metadata = InnerMetadata :: from_protected_header ( protected, context) ;
267
- Self :: from_metadata_fields ( metadata, context. report )
208
+ /// Build `Metadata` object from the metadata fields, doing all necessary validation.
209
+ pub ( crate ) fn from_json ( fields : serde_json:: Value , report : & ProblemReport ) -> Self {
210
+ let fields = serde:: Deserializer :: deserialize_map ( fields, MetadataDeserializeVisitor )
211
+ . inspect_err ( |err| {
212
+ report. other (
213
+ & format ! ( "Unable to deserialize json: {err}" ) ,
214
+ "Metadata building from json" ,
215
+ ) ;
216
+ } )
217
+ . unwrap_or_default ( ) ;
218
+ Self :: from_fields ( fields, report)
268
219
}
269
220
}
270
221
271
- impl InnerMetadata {
222
+ impl Metadata {
272
223
/// Converting COSE Protected Header to Metadata fields, collecting decoding report
273
224
/// issues.
274
225
#[ allow(
@@ -282,11 +233,11 @@ impl InnerMetadata {
282
233
/// header.
283
234
const COSE_DECODING_CONTEXT : & str = "COSE Protected Header to Metadata" ;
284
235
285
- let mut metadata = Self :: default ( ) ;
236
+ let mut metadata_fields = vec ! [ ] ;
286
237
287
238
if let Some ( value) = protected. header . content_type . as_ref ( ) {
288
239
match ContentType :: try_from ( value) {
289
- Ok ( ct) => metadata . content_type = Some ( ct ) ,
240
+ Ok ( ct) => metadata_fields . push ( SupportedField :: ContentType ( ct ) ) ,
290
241
Err ( e) => {
291
242
context. report . conversion_error (
292
243
"COSE protected header content type" ,
@@ -303,7 +254,7 @@ impl InnerMetadata {
303
254
|key| matches ! ( key, coset:: Label :: Text ( label) if label. eq_ignore_ascii_case( CONTENT_ENCODING_KEY ) ) ,
304
255
) {
305
256
match ContentEncoding :: try_from ( value) {
306
- Ok ( ce) => metadata . content_encoding = Some ( ce ) ,
257
+ Ok ( ce) => metadata_fields . push ( SupportedField :: ContentEncoding ( ce ) ) ,
307
258
Err ( e) => {
308
259
context. report . conversion_error (
309
260
"COSE protected header content encoding" ,
@@ -315,7 +266,7 @@ impl InnerMetadata {
315
266
}
316
267
}
317
268
318
- metadata . doc_type = cose_protected_header_find (
269
+ if let Some ( value ) = cose_protected_header_find (
319
270
protected,
320
271
|key| matches ! ( key, coset:: Label :: Text ( label) if label. eq_ignore_ascii_case( TYPE_KEY ) ) ,
321
272
)
@@ -325,48 +276,64 @@ impl InnerMetadata {
325
276
context,
326
277
)
327
278
. ok ( )
328
- } ) ;
279
+ } ) {
280
+ metadata_fields. push ( SupportedField :: Type ( value) ) ;
281
+ }
329
282
330
- metadata . id = decode_document_field_from_protected_header :: < CborUuidV7 > (
283
+ if let Some ( value ) = decode_document_field_from_protected_header :: < CborUuidV7 > (
331
284
protected,
332
285
ID_KEY ,
333
286
COSE_DECODING_CONTEXT ,
334
287
context. report ,
335
288
)
336
- . map ( |v| v. 0 ) ;
289
+ . map ( |v| v. 0 )
290
+ {
291
+ metadata_fields. push ( SupportedField :: Id ( value) ) ;
292
+ }
337
293
338
- metadata . ver = decode_document_field_from_protected_header :: < CborUuidV7 > (
294
+ if let Some ( value ) = decode_document_field_from_protected_header :: < CborUuidV7 > (
339
295
protected,
340
296
VER_KEY ,
341
297
COSE_DECODING_CONTEXT ,
342
298
context. report ,
343
299
)
344
- . map ( |v| v. 0 ) ;
300
+ . map ( |v| v. 0 )
301
+ {
302
+ metadata_fields. push ( SupportedField :: Ver ( value) ) ;
303
+ }
345
304
346
- metadata . doc_ref = decode_document_field_from_protected_header (
305
+ if let Some ( value ) = decode_document_field_from_protected_header (
347
306
protected,
348
307
REF_KEY ,
349
308
COSE_DECODING_CONTEXT ,
350
309
context. report ,
351
- ) ;
352
- metadata. template = decode_document_field_from_protected_header (
310
+ ) {
311
+ metadata_fields. push ( SupportedField :: Ref ( value) ) ;
312
+ }
313
+ if let Some ( value) = decode_document_field_from_protected_header (
353
314
protected,
354
315
TEMPLATE_KEY ,
355
316
COSE_DECODING_CONTEXT ,
356
317
context. report ,
357
- ) ;
358
- metadata. reply = decode_document_field_from_protected_header (
318
+ ) {
319
+ metadata_fields. push ( SupportedField :: Template ( value) ) ;
320
+ }
321
+ if let Some ( value) = decode_document_field_from_protected_header (
359
322
protected,
360
323
REPLY_KEY ,
361
324
COSE_DECODING_CONTEXT ,
362
325
context. report ,
363
- ) ;
364
- metadata. section = decode_document_field_from_protected_header (
326
+ ) {
327
+ metadata_fields. push ( SupportedField :: Reply ( value) ) ;
328
+ }
329
+ if let Some ( value) = decode_document_field_from_protected_header (
365
330
protected,
366
331
SECTION_KEY ,
367
332
COSE_DECODING_CONTEXT ,
368
333
context. report ,
369
- ) ;
334
+ ) {
335
+ metadata_fields. push ( SupportedField :: Section ( value) ) ;
336
+ }
370
337
371
338
// process `parameters` field and all its aliases
372
339
let ( parameters, has_multiple_fields) = [
@@ -392,7 +359,9 @@ impl InnerMetadata {
392
359
"Validation of parameters field aliases"
393
360
) ;
394
361
}
395
- metadata. parameters = parameters;
362
+ if let Some ( value) = parameters {
363
+ metadata_fields. push ( SupportedField :: Parameters ( value) ) ;
364
+ }
396
365
397
366
if let Some ( cbor_doc_collabs) = cose_protected_header_find ( protected, |key| {
398
367
key == & coset:: Label :: Text ( COLLABS_KEY . to_string ( ) )
@@ -416,7 +385,9 @@ impl InnerMetadata {
416
385
} ,
417
386
}
418
387
}
419
- metadata. collabs = c;
388
+ if !c. is_empty ( ) {
389
+ metadata_fields. push ( SupportedField :: Collabs ( c) ) ;
390
+ }
420
391
} else {
421
392
context. report . conversion_error (
422
393
"CBOR COSE protected header collaborators" ,
@@ -427,7 +398,7 @@ impl InnerMetadata {
427
398
} ;
428
399
}
429
400
430
- metadata
401
+ Self :: from_fields ( metadata_fields , context . report )
431
402
}
432
403
}
433
404
@@ -560,3 +531,24 @@ impl minicbor::Decode<'_, crate::decode_context::DecodeContext<'_>> for Metadata
560
531
first_err. map_or ( Ok ( Self ( metadata_map) ) , Err )
561
532
}
562
533
}
534
+
535
+ /// Implements [`serde::de::Visitor`], so that [`Metadata`] can be
536
+ /// deserialized by [`serde::Deserializer::deserialize_map`].
537
+ struct MetadataDeserializeVisitor ;
538
+
539
+ impl < ' de > serde:: de:: Visitor < ' de > for MetadataDeserializeVisitor {
540
+ type Value = Vec < SupportedField > ;
541
+
542
+ fn expecting ( & self , f : & mut std:: fmt:: Formatter ) -> std:: fmt:: Result {
543
+ f. write_str ( "Catalyst Signed Document metadata key-value pairs" )
544
+ }
545
+
546
+ fn visit_map < A : serde:: de:: MapAccess < ' de > > ( self , mut d : A ) -> Result < Self :: Value , A :: Error > {
547
+ let mut res = Vec :: with_capacity ( d. size_hint ( ) . unwrap_or ( 0 ) ) ;
548
+ while let Some ( k) = d. next_key :: < SupportedLabel > ( ) ? {
549
+ let v = d. next_value_seed ( k) ?;
550
+ res. push ( v) ;
551
+ }
552
+ Ok ( res)
553
+ }
554
+ }
0 commit comments