1
use crate::error::{FinanceError, TagError};
2
use sqlx::{Connection, types::Uuid};
3
use supp_macro::Builder;
4

            
5
#[derive(Debug, sqlx::FromRow, Builder)]
6
#[builder(error_kind = "TagError")]
7
pub struct Tag {
8
    pub id: Uuid,
9
    pub tag_name: String,
10
    pub tag_value: String,
11
    pub description: Option<String>,
12
}
13

            
14
impl Tag {
15
    /// Upsert this tag and return the canonical row id.
16
    ///
17
    /// Tag identity is `(tag_name, tag_value)`. When a row with the same pair
18
    /// already exists, its id is returned and the description is refreshed
19
    /// only if a non-NULL one was supplied. The returned id may differ from
20
    /// `self.id`; callers linking the tag to an entity must use the returned
21
    /// canonical id.
22
72
    pub async fn commit<E>(&self, conn: &mut E) -> Result<Uuid, FinanceError>
23
72
    where
24
72
        E: Connection<Database = sqlx::Postgres>,
25
72
    {
26
72
        let mut tr = conn.begin().await?;
27

            
28
72
        let row = sqlx::query_file!(
29
72
            "sql/tag_insert.sql",
30
            &self.id,
31
            &self.tag_name,
32
            &self.tag_value,
33
            self.description
34
        )
35
72
        .fetch_one(&mut *tr)
36
72
        .await?;
37
72
        tr.commit().await?;
38

            
39
72
        Ok(row.id)
40
72
    }
41
}
42

            
43
#[cfg(test)]
44
mod tag_tests {
45
    use super::*;
46
    #[cfg(feature = "testlog")]
47
    use env_logger;
48
    #[cfg(feature = "testlog")]
49
    use log;
50
    use sqlx::PgPool;
51
    use tokio::sync::OnceCell;
52

            
53
    /// Context for keeping environment intact
54
    static CONTEXT: OnceCell<()> = OnceCell::const_new();
55

            
56
2
    async fn setup() {
57
2
        CONTEXT
58
2
            .get_or_init(|| async {
59
                #[cfg(feature = "testlog")]
60
1
                let _ = env_logger::builder()
61
1
                    .is_test(true)
62
1
                    .filter_level(log::LevelFilter::Trace)
63
1
                    .try_init();
64
2
            })
65
2
            .await;
66
2
    }
67

            
68
    #[sqlx::test(migrations = "../migrations")]
69
    async fn test_tag_store(pool: PgPool) -> anyhow::Result<()> {
70
        setup().await;
71
        let mut conn = pool.acquire().await?;
72

            
73
        let tag = Tag {
74
            id: Uuid::new_v4(),
75
            tag_name: "Category".to_string(),
76
            tag_value: "test".to_string(),
77
            description: None,
78
        };
79

            
80
        sqlx::query!(
81
            "INSERT INTO tags (id, tag_name, tag_value, description) \
82
		      VALUES ($1, $2, $3, $4)",
83
            &tag.id,
84
            &tag.tag_name,
85
            &tag.tag_value,
86
            tag.description
87
        )
88
        .execute(&mut *conn)
89
        .await?;
90

            
91
        let result = sqlx::query!("SELECT id, tag_value FROM tags WHERE tag_name = 'Category'")
92
            .fetch_one(&mut *conn)
93
            .await?;
94

            
95
        assert_eq!(tag.id, result.id);
96
        assert_eq!(tag.tag_value, "test".to_string());
97

            
98
        let tag2 = Tag {
99
            id: Uuid::new_v4(),
100
            tag_name: "Cat2".to_string(),
101
            ..tag
102
        };
103
        let mut conn = pool.acquire().await?;
104
        tag2.commit(&mut *conn).await?;
105
        let mut conn = pool.acquire().await?;
106
        let result = sqlx::query!("SELECT id, tag_value FROM tags WHERE tag_name = 'Cat2'")
107
            .fetch_one(&mut *conn)
108
            .await?;
109

            
110
        assert_eq!(tag2.id, result.id);
111
        assert_eq!(tag2.tag_value, "test".to_string());
112

            
113
        Ok(())
114
    }
115

            
116
    #[tokio::test]
117
1
    async fn test_tag_builder() -> anyhow::Result<()> {
118
1
        setup().await;
119
1
        let build = Tag::builder().id(Uuid::new_v4()).build();
120
1
        assert!(build.is_err());
121
1
        let build = Tag::builder()
122
1
            .id(Uuid::new_v4())
123
1
            .tag_name("name")
124
1
            .tag_value("type")
125
1
            .build();
126

            
127
1
        assert!(build.is_ok());
128
2
        Ok(())
129
1
    }
130
}