cat_gateway/db/event/signed_docs/full_signed_doc.rs
1//! `FullSignedDoc` struct implementation.
2
3use super::SignedDocBody;
4use crate::{
5 db::event::EventDB,
6 jinja::{get_template, JinjaTemplateSource},
7};
8
9/// Insert sql query
10const INSERT_SIGNED_DOCS: &str = include_str!("./sql/insert_signed_documents.sql");
11
12/// Select sql query jinja template
13pub(crate) const SELECT_SIGNED_DOCS_TEMPLATE: JinjaTemplateSource = JinjaTemplateSource {
14 name: "select_signed_documents.jinja.template",
15 source: include_str!("./sql/select_signed_documents.sql.jinja"),
16};
17
18/// `FullSignedDoc::store` method error.
19#[derive(thiserror::Error, Debug)]
20#[error("Document with the same `id` and `ver` already exists")]
21pub(crate) struct StoreError;
22
23/// Full signed doc event db struct
24#[derive(Debug, Clone, PartialEq)]
25pub(crate) struct FullSignedDoc {
26 /// Signed doc body
27 body: SignedDocBody,
28 /// `signed_doc` table `payload` field
29 payload: Option<serde_json::Value>,
30 /// `signed_doc` table `raw` field
31 raw: Vec<u8>,
32}
33
34impl FullSignedDoc {
35 /// Creates a `FullSignedDoc` instance.
36 pub(crate) fn new(
37 body: SignedDocBody, payload: Option<serde_json::Value>, raw: Vec<u8>,
38 ) -> Self {
39 Self { body, payload, raw }
40 }
41
42 /// Returns the document id.
43 pub(crate) fn id(&self) -> &uuid::Uuid {
44 self.body.id()
45 }
46
47 /// Returns the document version.
48 pub(crate) fn ver(&self) -> &uuid::Uuid {
49 self.body.ver()
50 }
51
52 /// Returns the document metadata.
53 #[allow(dead_code)]
54 pub(crate) fn metadata(&self) -> Option<&serde_json::Value> {
55 self.body.metadata()
56 }
57
58 /// Returns the `SignedDocBody`.
59 #[allow(dead_code)]
60 pub(crate) fn body(&self) -> &SignedDocBody {
61 &self.body
62 }
63
64 /// Returns the document raw bytes.
65 pub(crate) fn raw(&self) -> &Vec<u8> {
66 &self.raw
67 }
68
69 /// Uploads a `FullSignedDoc` to the event db.
70 /// Returns `true` if document was added into the db, `false` if it was already added
71 /// previously.
72 ///
73 /// Make an insert query into the `event-db` by adding data into the `signed_docs`
74 /// table.
75 ///
76 /// * IF the record primary key (id,ver) does not exist, then add the new record.
77 /// Return success.
78 /// * IF the record does exist, but all values are the same as stored, return Success.
79 /// * Otherwise return an error. (Can not over-write an existing record with new
80 /// data).
81 ///
82 /// # Arguments:
83 /// - `id` is a UUID v7
84 /// - `ver` is a UUID v7
85 /// - `doc_type` is a UUID v4
86 pub(crate) async fn store(&self) -> anyhow::Result<bool> {
87 // Perform insert before checking if the document already exists.
88 // This should prevent race condition.
89 match EventDB::modify(INSERT_SIGNED_DOCS, &self.postgres_db_fields()).await {
90 // Able to insert, no conflict
91 Ok(()) => Ok(true),
92 Err(_) => {
93 // Attempt to retrieve the document now that we failed to insert
94 match Self::retrieve(self.id(), Some(self.ver())).await {
95 Ok(res_doc) => {
96 anyhow::ensure!(&res_doc == self, StoreError);
97 // Document already exists and matches, return false
98 Ok(false)
99 },
100 Err(e) => Err(e),
101 }
102 },
103 }
104 }
105
106 /// Loads a `FullSignedDoc` from the event db.
107 ///
108 /// Make a select query into the `event-db` by getting data from the `signed_docs`
109 /// table.
110 ///
111 /// * This returns a single document. All data from the document is returned,
112 /// including the `payload` and `raw` fields.
113 /// * `ver` should be able to be optional, in which case get the latest ver of the
114 /// given `id`.
115 ///
116 /// # Arguments:
117 /// - `id` is a UUID v7
118 /// - `ver` is a UUID v7
119 pub(crate) async fn retrieve(
120 id: &uuid::Uuid, ver: Option<&uuid::Uuid>,
121 ) -> anyhow::Result<Self> {
122 let query_template = get_template(&SELECT_SIGNED_DOCS_TEMPLATE)?;
123 let query = query_template.render(serde_json::json!({
124 "id": id,
125 "ver": ver,
126 }))?;
127 let row = EventDB::query_one(&query, &[]).await?;
128
129 Self::from_row(id, ver, &row)
130 }
131
132 /// Returns all signed document fields for the event db queries
133 fn postgres_db_fields(&self) -> [&(dyn tokio_postgres::types::ToSql + Sync); 7] {
134 let body_fields = self.body.postgres_db_fields();
135 [
136 body_fields[0],
137 body_fields[1],
138 body_fields[2],
139 body_fields[3],
140 body_fields[4],
141 &self.payload,
142 &self.raw,
143 ]
144 }
145
146 /// Creates a `FullSignedDoc` from postgresql row object.
147 fn from_row(
148 id: &uuid::Uuid, ver: Option<&uuid::Uuid>, row: &tokio_postgres::Row,
149 ) -> anyhow::Result<Self> {
150 let ver = if let Some(ver) = ver {
151 *ver
152 } else {
153 row.try_get("ver")?
154 };
155
156 Ok(FullSignedDoc {
157 body: SignedDocBody::new(
158 *id,
159 ver,
160 row.try_get("type")?,
161 row.try_get("authors")?,
162 row.try_get("metadata")?,
163 ),
164 payload: row.try_get("payload")?,
165 raw: row.try_get("raw")?,
166 })
167 }
168}