@@ -63,65 +63,6 @@ const CATEGORY_ID_KEY: &str = "category_id";
6363#[ derive( Clone , Debug , PartialEq , Default ) ]
6464pub struct Metadata ( BTreeMap < SupportedLabel , SupportedField > ) ;
6565
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-
12566impl Metadata {
12667 /// Return Document Type `DocType` - a list of `UUIDv4`.
12768 ///
@@ -233,42 +174,52 @@ impl Metadata {
233174 }
234175
235176 /// 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+ }
239190 }
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 ) ;
242194 }
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 ) ;
245197 }
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 ) ;
252203 }
253204
254- Self (
255- metadata
256- . into_iter ( )
257- . map ( |field| ( field. discriminant ( ) , field) )
258- . collect ( ) ,
259- )
205+ metadata
260206 }
261207
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)
268219 }
269220}
270221
271- impl InnerMetadata {
222+ impl Metadata {
272223 /// Converting COSE Protected Header to Metadata fields, collecting decoding report
273224 /// issues.
274225 #[ allow(
@@ -282,11 +233,11 @@ impl InnerMetadata {
282233 /// header.
283234 const COSE_DECODING_CONTEXT : & str = "COSE Protected Header to Metadata" ;
284235
285- let mut metadata = Self :: default ( ) ;
236+ let mut metadata_fields = vec ! [ ] ;
286237
287238 if let Some ( value) = protected. header . content_type . as_ref ( ) {
288239 match ContentType :: try_from ( value) {
289- Ok ( ct) => metadata . content_type = Some ( ct ) ,
240+ Ok ( ct) => metadata_fields . push ( SupportedField :: ContentType ( ct ) ) ,
290241 Err ( e) => {
291242 context. report . conversion_error (
292243 "COSE protected header content type" ,
@@ -303,7 +254,7 @@ impl InnerMetadata {
303254 |key| matches ! ( key, coset:: Label :: Text ( label) if label. eq_ignore_ascii_case( CONTENT_ENCODING_KEY ) ) ,
304255 ) {
305256 match ContentEncoding :: try_from ( value) {
306- Ok ( ce) => metadata . content_encoding = Some ( ce ) ,
257+ Ok ( ce) => metadata_fields . push ( SupportedField :: ContentEncoding ( ce ) ) ,
307258 Err ( e) => {
308259 context. report . conversion_error (
309260 "COSE protected header content encoding" ,
@@ -315,7 +266,7 @@ impl InnerMetadata {
315266 }
316267 }
317268
318- metadata . doc_type = cose_protected_header_find (
269+ if let Some ( value ) = cose_protected_header_find (
319270 protected,
320271 |key| matches ! ( key, coset:: Label :: Text ( label) if label. eq_ignore_ascii_case( TYPE_KEY ) ) ,
321272 )
@@ -325,48 +276,64 @@ impl InnerMetadata {
325276 context,
326277 )
327278 . ok ( )
328- } ) ;
279+ } ) {
280+ metadata_fields. push ( SupportedField :: Type ( value) ) ;
281+ }
329282
330- metadata . id = decode_document_field_from_protected_header :: < CborUuidV7 > (
283+ if let Some ( value ) = decode_document_field_from_protected_header :: < CborUuidV7 > (
331284 protected,
332285 ID_KEY ,
333286 COSE_DECODING_CONTEXT ,
334287 context. report ,
335288 )
336- . map ( |v| v. 0 ) ;
289+ . map ( |v| v. 0 )
290+ {
291+ metadata_fields. push ( SupportedField :: Id ( value) ) ;
292+ }
337293
338- metadata . ver = decode_document_field_from_protected_header :: < CborUuidV7 > (
294+ if let Some ( value ) = decode_document_field_from_protected_header :: < CborUuidV7 > (
339295 protected,
340296 VER_KEY ,
341297 COSE_DECODING_CONTEXT ,
342298 context. report ,
343299 )
344- . map ( |v| v. 0 ) ;
300+ . map ( |v| v. 0 )
301+ {
302+ metadata_fields. push ( SupportedField :: Ver ( value) ) ;
303+ }
345304
346- metadata . doc_ref = decode_document_field_from_protected_header (
305+ if let Some ( value ) = decode_document_field_from_protected_header (
347306 protected,
348307 REF_KEY ,
349308 COSE_DECODING_CONTEXT ,
350309 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 (
353314 protected,
354315 TEMPLATE_KEY ,
355316 COSE_DECODING_CONTEXT ,
356317 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 (
359322 protected,
360323 REPLY_KEY ,
361324 COSE_DECODING_CONTEXT ,
362325 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 (
365330 protected,
366331 SECTION_KEY ,
367332 COSE_DECODING_CONTEXT ,
368333 context. report ,
369- ) ;
334+ ) {
335+ metadata_fields. push ( SupportedField :: Section ( value) ) ;
336+ }
370337
371338 // process `parameters` field and all its aliases
372339 let ( parameters, has_multiple_fields) = [
@@ -392,7 +359,9 @@ impl InnerMetadata {
392359 "Validation of parameters field aliases"
393360 ) ;
394361 }
395- metadata. parameters = parameters;
362+ if let Some ( value) = parameters {
363+ metadata_fields. push ( SupportedField :: Parameters ( value) ) ;
364+ }
396365
397366 if let Some ( cbor_doc_collabs) = cose_protected_header_find ( protected, |key| {
398367 key == & coset:: Label :: Text ( COLLABS_KEY . to_string ( ) )
@@ -416,7 +385,9 @@ impl InnerMetadata {
416385 } ,
417386 }
418387 }
419- metadata. collabs = c;
388+ if !c. is_empty ( ) {
389+ metadata_fields. push ( SupportedField :: Collabs ( c) ) ;
390+ }
420391 } else {
421392 context. report . conversion_error (
422393 "CBOR COSE protected header collaborators" ,
@@ -427,7 +398,7 @@ impl InnerMetadata {
427398 } ;
428399 }
429400
430- metadata
401+ Self :: from_fields ( metadata_fields , context . report )
431402 }
432403}
433404
@@ -560,3 +531,24 @@ impl minicbor::Decode<'_, crate::decode_context::DecodeContext<'_>> for Metadata
560531 first_err. map_or ( Ok ( Self ( metadata_map) ) , Err )
561532 }
562533}
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