11//! Implementation of the PUT `/document` endpoint
22
3- use std:: { collections:: HashSet , str:: FromStr } ;
4-
5- use catalyst_signed_doc:: CatalystSignedDocument ;
63use poem_openapi:: { ApiResponse , payload:: Json } ;
74use unprocessable_content_request:: PutDocumentUnprocessableContent ;
85
96use super :: common:: { DocProvider , VerifyingKeyProvider } ;
107use crate :: {
118 db:: {
12- event:: {
13- error,
14- signed_docs:: { FullSignedDoc , SignedDocBody , StoreError } ,
15- } ,
9+ event:: { error:: NotFoundError , signed_docs:: FullSignedDoc } ,
1610 index:: session:: CassandraSessionError ,
1711 } ,
1812 service:: {
@@ -74,20 +68,43 @@ pub(crate) async fn endpoint(
7468 . into ( ) ;
7569 } ;
7670
77- // validate document signatures
78- let verifying_key_provider =
79- match VerifyingKeyProvider :: try_new ( & mut token, & doc. authors ( ) ) . await {
80- Ok ( value) => value,
81- Err ( err) if err. is :: < CassandraSessionError > ( ) => {
82- return AllResponses :: service_unavailable ( & err, RetryAfterOption :: Default ) ;
83- } ,
84- Err ( err) => {
85- return Responses :: UnprocessableContent ( Json (
86- PutDocumentUnprocessableContent :: new ( & err, None ) ,
87- ) )
88- . into ( ) ;
89- } ,
90- } ;
71+ let db_doc = match FullSignedDoc :: try_from ( & doc) {
72+ Ok ( doc) => doc,
73+ Err ( err) => return AllResponses :: handle_error ( & err) ,
74+ } ;
75+
76+ // TODO: use the `ValidationProvider::try_get_doc` method and verify the returned
77+ // document cid values.
78+ // making sure that the document was already submitted before running validation,
79+ // otherwise validation would fail, because of the assumption that this document wasn't
80+ // published yet.
81+ match FullSignedDoc :: retrieve ( db_doc. id ( ) , Some ( db_doc. ver ( ) ) ) . await {
82+ Ok ( retrieved) if db_doc != retrieved => {
83+ return Responses :: UnprocessableContent ( Json ( PutDocumentUnprocessableContent :: new (
84+ "Document with the same `id` and `ver` already exists" ,
85+ Some ( doc. problem_report ( ) ) ,
86+ ) ) )
87+ . into ( ) ;
88+ } ,
89+ Ok ( _) => return Responses :: NoContent . into ( ) ,
90+ Err ( err) if err. is :: < NotFoundError > ( ) => ( ) ,
91+ Err ( err) => return AllResponses :: handle_error ( & err) ,
92+ }
93+
94+ // validation provider
95+ let validation_provider = match VerifyingKeyProvider :: try_new ( & mut token, & doc. authors ( ) ) . await
96+ {
97+ Ok ( value) => ValidationProvider :: new ( DocProvider , value) ,
98+ Err ( err) if err. is :: < CassandraSessionError > ( ) => {
99+ return AllResponses :: service_unavailable ( & err, RetryAfterOption :: Default ) ;
100+ } ,
101+ Err ( err) => {
102+ return Responses :: UnprocessableContent ( Json ( PutDocumentUnprocessableContent :: new (
103+ & err, None ,
104+ ) ) )
105+ . into ( ) ;
106+ } ,
107+ } ;
91108
92109 match doc. is_deprecated ( ) {
93110 // apply older validation rule
@@ -113,8 +130,6 @@ pub(crate) async fn endpoint(
113130 } ,
114131 // apply newest validation rules
115132 Ok ( false ) => {
116- let validation_provider = ValidationProvider :: new ( DocProvider , verifying_key_provider) ;
117-
118133 match catalyst_signed_doc:: validator:: validate ( & doc, & validation_provider) . await {
119134 Ok ( true ) => ( ) ,
120135 Ok ( false ) => {
@@ -143,88 +158,8 @@ pub(crate) async fn endpoint(
143158 . into ( ) ;
144159 }
145160
146- match validate_against_original_doc ( & doc) . await {
147- Ok ( true ) => ( ) ,
148- Ok ( false ) => {
149- return Responses :: UnprocessableContent ( Json (
150- PutDocumentUnprocessableContent :: new ( "Failed validating document: catalyst-id or role does not match the current version." , None ) ,
151- ) )
152- . into ( ) ;
153- } ,
154- Err ( err) => return AllResponses :: handle_error ( & err) ,
155- }
156-
157- // update the document storing in the db
158- match store_document_in_db ( & doc, doc_bytes) . await {
159- Ok ( true ) => Responses :: Created . into ( ) ,
160- Ok ( false ) => Responses :: NoContent . into ( ) ,
161- Err ( err) if err. is :: < StoreError > ( ) => {
162- Responses :: UnprocessableContent ( Json ( PutDocumentUnprocessableContent :: new (
163- "Document with the same `id` and `ver` already exists" ,
164- Some ( doc. problem_report ( ) ) ,
165- ) ) )
166- . into ( )
167- } ,
161+ match db_doc. store ( ) . await {
162+ Ok ( ( ) ) => Responses :: Created . into ( ) ,
168163 Err ( err) => AllResponses :: handle_error ( & err) ,
169164 }
170165}
171-
172- /// Fetch the latest version and ensure its catalyst-id match those in the newer version.
173- async fn validate_against_original_doc ( doc : & CatalystSignedDocument ) -> anyhow:: Result < bool > {
174- let original_doc = match FullSignedDoc :: retrieve ( & doc. doc_id ( ) ?. uuid ( ) , None ) . await {
175- Ok ( doc) => doc,
176- Err ( e) if e. is :: < error:: NotFoundError > ( ) => return Ok ( true ) ,
177- Err ( e) => return Err ( e) ,
178- } ;
179-
180- let original_authors = original_doc
181- . body ( )
182- . authors ( )
183- . iter ( )
184- . map ( |author| catalyst_signed_doc:: CatalystId :: from_str ( author) )
185- . collect :: < Result < HashSet < _ > , _ > > ( ) ?;
186- let authors: HashSet < _ > = doc. authors ( ) . into_iter ( ) . collect ( ) ;
187- Ok ( authors == original_authors)
188- }
189-
190- /// Store a provided and validated document inside the db.
191- /// Returns `true` if its a new document.
192- /// Returns `false` if the same document already exists.
193- async fn store_document_in_db (
194- doc : & catalyst_signed_doc:: CatalystSignedDocument ,
195- doc_bytes : Vec < u8 > ,
196- ) -> anyhow:: Result < bool > {
197- let authors = doc
198- . authors ( )
199- . iter ( )
200- . map ( |v| v. as_short_id ( ) . to_string ( ) )
201- . collect ( ) ;
202-
203- let doc_meta_json = doc. doc_meta ( ) . to_json ( ) ?;
204-
205- let payload = if matches ! (
206- doc. doc_content_type( ) ,
207- Some ( catalyst_signed_doc:: ContentType :: Json )
208- ) {
209- match serde_json:: from_slice ( doc. decoded_content ( ) ?. as_slice ( ) ) {
210- Ok ( payload) => Some ( payload) ,
211- Err ( e) => {
212- anyhow:: bail!( "Invalid Document Content, not Json encoded: {e}" ) ;
213- } ,
214- }
215- } else {
216- None
217- } ;
218-
219- let doc_body = SignedDocBody :: new (
220- doc. doc_id ( ) ?. into ( ) ,
221- doc. doc_ver ( ) ?. into ( ) ,
222- doc. doc_type ( ) ?. uuid ( ) ,
223- authors,
224- Some ( doc_meta_json) ,
225- ) ;
226-
227- FullSignedDoc :: new ( doc_body, payload, doc_bytes)
228- . store ( )
229- . await
230- }
0 commit comments