33use anyhow:: ensure;
44use catalyst_signed_doc_spec:: {
55 is_required:: IsRequired ,
6- metadata:: { chain:: Chain , collaborators:: Collaborators } ,
6+ metadata:: { chain:: Chain as ChainSpec , collaborators:: Collaborators } ,
77} ;
88
9- use crate :: { CatalystSignedDocument , providers:: CatalystSignedDocumentProvider } ;
9+ use crate :: {
10+ CatalystSignedDocument , Chain , providers:: CatalystSignedDocumentProvider ,
11+ validator:: rules:: doc_ref:: doc_refs_check,
12+ } ;
1013
1114#[ cfg( test) ]
1215mod tests;
@@ -26,7 +29,7 @@ pub(crate) enum ChainRule {
2629impl ChainRule {
2730 /// Generating `ChainRule` from specs
2831 pub ( crate ) fn new (
29- spec : & Chain ,
32+ spec : & ChainSpec ,
3033 collaborators_spec : & Collaborators ,
3134 ) -> anyhow:: Result < Self > {
3235 let optional = match spec. required {
@@ -56,9 +59,6 @@ impl ChainRule {
5659 {
5760 let chain = doc. doc_meta ( ) . chain ( ) ;
5861
59- // TODO: the current implementation is only for the direct chained doc,
60- // make it recursively checks the entire chain with the same `id` docs.
61-
6262 if let Self :: Specified { optional } = self {
6363 if chain. is_none ( ) && !optional {
6464 doc. report ( )
@@ -68,85 +68,7 @@ impl ChainRule {
6868
6969 // perform integrity validation
7070 if let Some ( doc_chain) = chain {
71- if doc_chain. document_ref ( ) . is_none ( ) && doc_chain. height ( ) != 0 {
72- doc. report ( ) . functional_validation (
73- "The chain height must be zero when there is no chained doc" ,
74- "Chained Documents validation" ,
75- ) ;
76- return Ok ( false ) ;
77- }
78- if doc_chain. height ( ) == 0 && doc_chain. document_ref ( ) . is_some ( ) {
79- doc. report ( ) . functional_validation (
80- "The next Chained Document must not exist while the height is zero" ,
81- "Chained Documents validation" ,
82- ) ;
83- return Ok ( false ) ;
84- }
85-
86- if let Some ( chained_ref) = doc_chain. document_ref ( ) {
87- let Some ( chained_doc) = provider. try_get_doc ( chained_ref) . await ? else {
88- doc. report ( ) . other (
89- & format ! (
90- "Cannot find the Chained Document ({chained_ref}) from the provider"
91- ) ,
92- "Chained Documents validation" ,
93- ) ;
94- return Ok ( false ) ;
95- } ;
96-
97- // have the same id as the document being chained to.
98- if chained_doc. doc_id ( ) ? != doc. doc_id ( ) ? {
99- doc. report ( ) . functional_validation (
100- "Must have the same id as the document being chained to" ,
101- "Chained Documents validation" ,
102- ) ;
103- return Ok ( false ) ;
104- }
105-
106- // have a ver that is greater than the ver being chained to.
107- if chained_doc. doc_ver ( ) ? > doc. doc_ver ( ) ? {
108- doc. report ( ) . functional_validation (
109- "Must have a ver that is greater than the ver being chained to" ,
110- "Chained Documents validation" ,
111- ) ;
112- return Ok ( false ) ;
113- }
114-
115- // have the same type as the chained document.
116- if chained_doc. doc_type ( ) ? != doc. doc_type ( ) ? {
117- doc. report ( ) . functional_validation (
118- "Must have the same type as the chained document" ,
119- "Chained Documents validation" ,
120- ) ;
121- return Ok ( false ) ;
122- }
123-
124- if let Some ( chained_height) =
125- chained_doc. doc_meta ( ) . chain ( ) . map ( crate :: Chain :: height)
126- {
127- // chain doc must not be negative
128- if chained_height < 0 {
129- doc. report ( ) . functional_validation (
130- "The height of the document being chained to must be positive number" ,
131- "Chained Documents validation" ,
132- ) ;
133- return Ok ( false ) ;
134- }
135-
136- // have its absolute height exactly one more than the height of the
137- // document being chained to.
138- if !matches ! (
139- i32 :: abs( doc_chain. height( ) ) . checked_sub( i32 :: abs( chained_height) ) ,
140- Some ( 1 )
141- ) {
142- doc. report ( ) . functional_validation (
143- "Must have its absolute height exactly one more than the height of the document being chained to" ,
144- "Chained Documents validation" ,
145- ) ;
146- return Ok ( false ) ;
147- }
148- }
149- }
71+ return Self :: chain_check ( doc_chain, doc, provider) . await ;
15072 }
15173 }
15274 if let Self :: NotSpecified = self
@@ -167,4 +89,122 @@ impl ChainRule {
16789
16890 Ok ( true )
16991 }
92+
93+ /// `chain` metadata field checks
94+ async fn chain_check < Provider > (
95+ doc_chain : & Chain ,
96+ doc : & CatalystSignedDocument ,
97+ provider : & Provider ,
98+ ) -> anyhow:: Result < bool >
99+ where
100+ Provider : CatalystSignedDocumentProvider ,
101+ {
102+ const CONTEXT : & str = "Chained Documents validation" ;
103+
104+ if doc_chain. document_ref ( ) . is_none ( ) && doc_chain. height ( ) != 0 {
105+ doc. report ( ) . functional_validation (
106+ "The chain height must be zero when there is no chained doc" ,
107+ CONTEXT ,
108+ ) ;
109+ return Ok ( false ) ;
110+ }
111+ if doc_chain. height ( ) == 0 && doc_chain. document_ref ( ) . is_some ( ) {
112+ doc. report ( ) . functional_validation (
113+ "The next Chained Document must not exist while the height is zero" ,
114+ CONTEXT ,
115+ ) ;
116+ return Ok ( false ) ;
117+ }
118+
119+ if let Some ( chained_ref) = doc_chain. document_ref ( ) {
120+ let Ok ( expected_doc_type) = doc. doc_type ( ) else {
121+ doc. report ( ) . missing_field ( "type" , CONTEXT ) ;
122+ return Ok ( false ) ;
123+ } ;
124+
125+ let chain_validator = |chained_doc : & CatalystSignedDocument | {
126+ let Ok ( doc_id) = doc. doc_id ( ) else {
127+ doc. report ( )
128+ . missing_field ( "id" , "Missing id field in the document" ) ;
129+ return false ;
130+ } ;
131+ let Ok ( chained_id) = chained_doc. doc_id ( ) else {
132+ doc. report ( )
133+ . missing_field ( "id" , "Missing id field in the chained document" ) ;
134+ return false ;
135+ } ;
136+ // have the same id as the document being chained to.
137+ if chained_id != doc_id {
138+ doc. report ( ) . functional_validation (
139+ "Must have the same id as the document being chained to" ,
140+ CONTEXT ,
141+ ) ;
142+ return false ;
143+ }
144+
145+ let Ok ( doc_ver) = doc. doc_ver ( ) else {
146+ doc. report ( )
147+ . missing_field ( "ver" , "Missing ver field in the document" ) ;
148+ return false ;
149+ } ;
150+ let Ok ( chained_ver) = chained_doc. doc_ver ( ) else {
151+ doc. report ( )
152+ . missing_field ( "ver" , "Missing ver field in the chained document" ) ;
153+ return false ;
154+ } ;
155+ // have a ver that is greater than the ver being chained to.
156+ if chained_ver > doc_ver {
157+ doc. report ( ) . functional_validation (
158+ "Must have a ver that is greater than the ver being chained to" ,
159+ CONTEXT ,
160+ ) ;
161+ return false ;
162+ }
163+
164+ let Some ( chained_height) = chained_doc. doc_meta ( ) . chain ( ) . map ( crate :: Chain :: height)
165+ else {
166+ doc. report ( )
167+ . missing_field ( "chain" , "Missing chain field in the chained document" ) ;
168+ return false ;
169+ } ;
170+
171+ // chain doc must not be negative
172+ if chained_height < 0 {
173+ doc. report ( ) . functional_validation (
174+ "The height of the document being chained to must be positive number" ,
175+ CONTEXT ,
176+ ) ;
177+ return false ;
178+ }
179+
180+ // have its absolute height exactly one more than the height of the
181+ // document being chained to.
182+ if !matches ! (
183+ i32 :: abs( doc_chain. height( ) ) . checked_sub( i32 :: abs( chained_height) ) ,
184+ Some ( 1 )
185+ ) {
186+ doc. report ( ) . functional_validation (
187+ "Must have its absolute height exactly one more than the height of the document being chained to" ,
188+ CONTEXT ,
189+ ) ;
190+ return false ;
191+ }
192+
193+ true
194+ } ;
195+
196+ return doc_refs_check (
197+ & vec ! [ chained_ref. clone( ) ] . into ( ) ,
198+ std:: slice:: from_ref ( expected_doc_type) ,
199+ false ,
200+ "chain" ,
201+ provider,
202+ doc. report ( ) ,
203+ chain_validator,
204+ )
205+ . await ;
206+ }
207+
208+ Ok ( true )
209+ }
170210}
0 commit comments