11//! Catalyst Signed Document Additional Fields.
22
33use anyhow:: anyhow;
4+ use coset:: { cbor:: Value , Label , ProtectedHeader } ;
45
5- use super :: { cose_protected_header_find, DocumentRef } ;
6+ use super :: { cose_protected_header_find, DocumentRef , UuidV4 } ;
67
78/// Additional Metadata Fields.
89///
910/// These values are extracted from the COSE Sign protected header labels.
1011#[ derive( Default , Debug , serde:: Serialize , serde:: Deserialize ) ]
11- pub ( super ) struct AdditionalFields {
12+ pub struct AdditionalFields {
1213 /// Reference to the latest document.
1314 #[ serde( rename = "ref" ) ]
1415 pub ( super ) doc_ref : Option < DocumentRef > ,
@@ -18,18 +19,90 @@ pub(super) struct AdditionalFields {
1819 pub ( super ) reply : Option < DocumentRef > ,
1920 /// Reference to the document section.
2021 pub ( super ) section : Option < String > ,
22+ /// Reference to the document collaborators. Collaborator type is TBD.
23+ pub ( super ) collabs : Option < Vec < String > > ,
24+ /// Unique identifier for the brand that is running the voting.
25+ pub ( super ) brand_id : Option < UuidV4 > ,
26+ /// Unique identifier for the campaign of voting.
27+ pub ( super ) campaign_id : Option < UuidV4 > ,
28+ /// Unique identifier for the election.
29+ pub ( super ) election_id : Option < UuidV4 > ,
30+ /// Unique identifier for the voting category as a collection of proposals.
31+ pub ( super ) category_id : Option < UuidV4 > ,
2132}
2233
23- impl TryFrom < & coset:: ProtectedHeader > for AdditionalFields {
34+ impl From < & AdditionalFields > for Vec < ( Label , Value ) > {
35+ fn from ( fields : & AdditionalFields ) -> Self {
36+ let mut vec = Vec :: new ( ) ;
37+
38+ if let Some ( doc_ref) = & fields. doc_ref {
39+ vec. push ( ( Label :: Text ( "ref" . to_string ( ) ) , doc_ref. into ( ) ) ) ;
40+ }
41+
42+ if let Some ( template) = & fields. template {
43+ vec. push ( ( Label :: Text ( "template" . to_string ( ) ) , template. into ( ) ) ) ;
44+ }
45+
46+ if let Some ( reply) = & fields. reply {
47+ vec. push ( ( Label :: Text ( "reply" . to_string ( ) ) , reply. into ( ) ) ) ;
48+ }
49+
50+ if let Some ( section) = & fields. section {
51+ vec. push ( (
52+ Label :: Text ( "section" . to_string ( ) ) ,
53+ Value :: Text ( section. clone ( ) ) ,
54+ ) ) ;
55+ }
56+
57+ if let Some ( collabs) = & fields. collabs {
58+ if !collabs. is_empty ( ) {
59+ vec. push ( (
60+ Label :: Text ( "collabs" . to_string ( ) ) ,
61+ Value :: Array ( collabs. iter ( ) . cloned ( ) . map ( Value :: Text ) . collect ( ) ) ,
62+ ) ) ;
63+ }
64+ }
65+
66+ if let Some ( brand_id) = & fields. brand_id {
67+ vec. push ( ( Label :: Text ( "brand_id" . to_string ( ) ) , Value :: from ( * brand_id) ) ) ;
68+ }
69+
70+ if let Some ( campaign_id) = & fields. campaign_id {
71+ vec. push ( (
72+ Label :: Text ( "campaign_id" . to_string ( ) ) ,
73+ Value :: from ( * campaign_id) ,
74+ ) ) ;
75+ }
76+
77+ if let Some ( election_id) = & fields. election_id {
78+ vec. push ( (
79+ Label :: Text ( "election_id" . to_string ( ) ) ,
80+ Value :: from ( * election_id) ,
81+ ) ) ;
82+ }
83+
84+ if let Some ( category_id) = & fields. category_id {
85+ vec. push ( (
86+ Label :: Text ( "category_id" . to_string ( ) ) ,
87+ Value :: from ( * category_id) ,
88+ ) ) ;
89+ }
90+
91+ vec
92+ }
93+ }
94+
95+ impl TryFrom < & ProtectedHeader > for AdditionalFields {
2496 type Error = Vec < anyhow:: Error > ;
2597
26- fn try_from ( protected : & coset:: ProtectedHeader ) -> Result < Self , Self :: Error > {
98+ #[ allow( clippy:: too_many_lines) ]
99+ fn try_from ( protected : & ProtectedHeader ) -> Result < Self , Self :: Error > {
27100 let mut extra = AdditionalFields :: default ( ) ;
28101 let mut errors = Vec :: new ( ) ;
29102
30- if let Some ( cbor_doc_ref) = cose_protected_header_find ( protected , |key| {
31- key == & coset :: Label :: Text ( "ref" . to_string ( ) )
32- } ) {
103+ if let Some ( cbor_doc_ref) =
104+ cose_protected_header_find ( protected , | key| key == & Label :: Text ( "ref" . to_string ( ) ) )
105+ {
33106 match DocumentRef :: try_from ( cbor_doc_ref) {
34107 Ok ( doc_ref) => {
35108 extra. doc_ref = Some ( doc_ref) ;
@@ -42,9 +115,9 @@ impl TryFrom<&coset::ProtectedHeader> for AdditionalFields {
42115 }
43116 }
44117
45- if let Some ( cbor_doc_template) = cose_protected_header_find ( protected , |key| {
46- key == & coset :: Label :: Text ( "template" . to_string ( ) )
47- } ) {
118+ if let Some ( cbor_doc_template) =
119+ cose_protected_header_find ( protected , | key| key == & Label :: Text ( "template" . to_string ( ) ) )
120+ {
48121 match DocumentRef :: try_from ( cbor_doc_template) {
49122 Ok ( doc_template) => {
50123 extra. template = Some ( doc_template) ;
@@ -57,9 +130,9 @@ impl TryFrom<&coset::ProtectedHeader> for AdditionalFields {
57130 }
58131 }
59132
60- if let Some ( cbor_doc_reply) = cose_protected_header_find ( protected , |key| {
61- key == & coset :: Label :: Text ( "reply" . to_string ( ) )
62- } ) {
133+ if let Some ( cbor_doc_reply) =
134+ cose_protected_header_find ( protected , | key| key == & Label :: Text ( "reply" . to_string ( ) ) )
135+ {
63136 match DocumentRef :: try_from ( cbor_doc_reply) {
64137 Ok ( doc_reply) => {
65138 extra. reply = Some ( doc_reply) ;
@@ -72,9 +145,9 @@ impl TryFrom<&coset::ProtectedHeader> for AdditionalFields {
72145 }
73146 }
74147
75- if let Some ( cbor_doc_section) = cose_protected_header_find ( protected , |key| {
76- key == & coset :: Label :: Text ( "section" . to_string ( ) )
77- } ) {
148+ if let Some ( cbor_doc_section) =
149+ cose_protected_header_find ( protected , | key| key == & Label :: Text ( "section" . to_string ( ) ) )
150+ {
78151 match cbor_doc_section. clone ( ) . into_text ( ) {
79152 Ok ( doc_section) => {
80153 extra. section = Some ( doc_section) ;
@@ -87,6 +160,97 @@ impl TryFrom<&coset::ProtectedHeader> for AdditionalFields {
87160 }
88161 }
89162
163+ if let Some ( cbor_doc_collabs) =
164+ cose_protected_header_find ( protected, |key| key == & Label :: Text ( "collabs" . to_string ( ) ) )
165+ {
166+ match cbor_doc_collabs. clone ( ) . into_array ( ) {
167+ Ok ( collabs) => {
168+ let mut c = Vec :: new ( ) ;
169+ for ( ids, collaborator) in collabs. iter ( ) . cloned ( ) . enumerate ( ) {
170+ match collaborator. into_text ( ) {
171+ Ok ( collaborator) => {
172+ c. push ( collaborator) ;
173+ } ,
174+ Err ( e) => {
175+ errors. push ( anyhow ! (
176+ "Invalid Collaborator at index {ids} of COSE protected header `collabs` field, err: {e:?}"
177+ ) ) ;
178+ } ,
179+ }
180+ }
181+
182+ if !c. is_empty ( ) {
183+ extra. collabs = Some ( c) ;
184+ }
185+ } ,
186+ Err ( e) => {
187+ errors. push ( anyhow ! (
188+ "Invalid COSE protected header `collabs` field, err: {e:?}"
189+ ) ) ;
190+ } ,
191+ }
192+ }
193+
194+ if let Some ( cbor_doc_brand_id) =
195+ cose_protected_header_find ( protected, |key| key == & Label :: Text ( "brand_id" . to_string ( ) ) )
196+ {
197+ match UuidV4 :: try_from ( cbor_doc_brand_id) {
198+ Ok ( brand_id) => {
199+ extra. brand_id = Some ( brand_id) ;
200+ } ,
201+ Err ( e) => {
202+ errors. push ( anyhow ! (
203+ "Invalid COSE protected header `brand_id` field, err: {e}"
204+ ) ) ;
205+ } ,
206+ }
207+ }
208+
209+ if let Some ( cbor_doc_campaign_id) = cose_protected_header_find ( protected, |key| {
210+ key == & Label :: Text ( "campaign_id" . to_string ( ) )
211+ } ) {
212+ match UuidV4 :: try_from ( cbor_doc_campaign_id) {
213+ Ok ( campaign_id) => {
214+ extra. campaign_id = Some ( campaign_id) ;
215+ } ,
216+ Err ( e) => {
217+ errors. push ( anyhow ! (
218+ "Invalid COSE protected header `campaign_id` field, err: {e}"
219+ ) ) ;
220+ } ,
221+ }
222+ }
223+
224+ if let Some ( cbor_doc_election_id) = cose_protected_header_find ( protected, |key| {
225+ key == & Label :: Text ( "election_id" . to_string ( ) )
226+ } ) {
227+ match UuidV4 :: try_from ( cbor_doc_election_id) {
228+ Ok ( election_id) => {
229+ extra. election_id = Some ( election_id) ;
230+ } ,
231+ Err ( e) => {
232+ errors. push ( anyhow ! (
233+ "Invalid COSE protected header `election_id` field, err: {e}"
234+ ) ) ;
235+ } ,
236+ }
237+ }
238+
239+ if let Some ( cbor_doc_category_id) = cose_protected_header_find ( protected, |key| {
240+ key == & Label :: Text ( "category_id" . to_string ( ) )
241+ } ) {
242+ match UuidV4 :: try_from ( cbor_doc_category_id) {
243+ Ok ( category_id) => {
244+ extra. category_id = Some ( category_id) ;
245+ } ,
246+ Err ( e) => {
247+ errors. push ( anyhow ! (
248+ "Invalid COSE protected header `category_id` field, err: {e}"
249+ ) ) ;
250+ } ,
251+ }
252+ }
253+
90254 if errors. is_empty ( ) {
91255 Ok ( extra)
92256 } else {
0 commit comments