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}