1
use derive_more::From;
2
use finance::{
3
    account::Account, commodity::Commodity, error::FinanceError, price::Price, split::Split,
4
    tag::Tag, transaction::Transaction,
5
};
6
use num_rational::Rational64;
7
use sqlx::{
8
    types::Uuid,
9
    types::chrono::{DateTime, Utc},
10
};
11
use std::{
12
    collections::HashMap,
13
    fmt::{self, Debug},
14
};
15
use thiserror::Error;
16

            
17
use crate::{config::ConfigError, error::ServerError};
18

            
19
pub mod account;
20
pub mod commodity;
21
pub mod config;
22
pub mod split;
23
pub mod transaction;
24

            
25
#[derive(Debug, Clone)]
26
pub struct CommodityInfo {
27
    pub commodity_id: Uuid,
28
    pub symbol: String,
29
    pub name: String,
30
}
31

            
32
#[derive(Debug, Clone)]
33
pub struct PaginationInfo {
34
    pub total_count: i64,
35
    pub limit: i64,
36
    pub offset: i64,
37
    pub has_more: bool,
38
}
39

            
40
#[derive(Debug, From)]
41
pub enum FinanceEntity {
42
    Commodity(Commodity),
43
    Tag(Tag),
44
    Split(Split),
45
    Transaction(Transaction),
46
    Price(Price),
47
    Account(Account),
48
}
49

            
50
#[derive(Debug, From)]
51
pub enum Argument {
52
    String(String),
53
    Rational(Rational64),
54
    Uuid(Uuid),
55
    Data(Vec<u8>),
56
    FinanceEntity(FinanceEntity),
57
    FinanceEntities(Vec<FinanceEntity>),
58
    DateTime(DateTime<Utc>),
59
}
60

            
61
impl From<Argument> for String {
62
    fn from(arg: Argument) -> Self {
63
        match arg {
64
            Argument::String(s) => s,
65
            _ => panic!("Cannot convert {arg:?} to String"),
66
        }
67
    }
68
}
69

            
70
impl From<Argument> for Rational64 {
71
    fn from(arg: Argument) -> Self {
72
        match arg {
73
            Argument::Rational(r) => r,
74
            _ => panic!("Cannot convert {arg:?} to Rational64"),
75
        }
76
    }
77
}
78

            
79
impl From<Argument> for Vec<u8> {
80
    fn from(arg: Argument) -> Self {
81
        match arg {
82
            Argument::Data(d) => d,
83
            _ => panic!("Cannot convert {arg:?} to Vec<u8>"),
84
        }
85
    }
86
}
87

            
88
#[derive(Debug, Error)]
89
pub enum CmdError {
90
    #[error("Wrong arguments: {0}")]
91
    Args(String),
92
    #[error("Config: {0}")]
93
    Config(#[from] ConfigError),
94
    #[error("Database: {0}")]
95
    DB(#[from] sqlx::Error),
96
    #[error("Server: {0}")]
97
    Server(#[from] ServerError),
98
    #[error("Finance: {0}")]
99
    Finance(#[from] FinanceError),
100
    #[error("Script execution failed: {0}")]
101
    Script(String),
102
}
103

            
104
// Implementing CmdResult as an enum with String and Rational returning options
105
#[derive(Debug)]
106
pub enum CmdResult {
107
    String(String),
108
    Rational(Rational64),
109
    Data(Vec<u8>),
110
    Lines(Vec<String>),
111
    Entity(FinanceEntity),
112
    Entities(Vec<FinanceEntity>),
113
    TaggedEntities {
114
        entities: Vec<(FinanceEntity, HashMap<String, FinanceEntity>)>,
115
        pagination: Option<PaginationInfo>,
116
    },
117
    CommodityInfoList(Vec<CommodityInfo>),
118
    MultiCurrencyBalance(Vec<(Commodity, Rational64)>),
119
}
120

            
121
impl From<String> for CmdResult {
122
34
    fn from(s: String) -> Self {
123
34
        CmdResult::String(s)
124
34
    }
125
}
126

            
127
pub struct LinesView<'view>(&'view CmdResult);
128
pub struct LinesViewMut<'view>(&'view mut CmdResult);
129

            
130
impl std::ops::Deref for LinesView<'_> {
131
    type Target = Vec<String>;
132

            
133
    fn deref(&self) -> &Self::Target {
134
        match self.0 {
135
            CmdResult::Lines(lines) => lines,
136
            _ => panic!("Attempted to use Lines view on non-Lines variant"),
137
        }
138
    }
139
}
140

            
141
impl std::ops::Deref for LinesViewMut<'_> {
142
    type Target = Vec<String>;
143

            
144
    fn deref(&self) -> &Self::Target {
145
        match self.0 {
146
            &mut CmdResult::Lines(ref lines) => lines,
147
            _ => panic!("Attempted to use Lines view on non-Lines variant"),
148
        }
149
    }
150
}
151

            
152
impl std::ops::DerefMut for LinesViewMut<'_> {
153
    fn deref_mut(&mut self) -> &mut Self::Target {
154
        match self.0 {
155
            &mut CmdResult::Lines(ref mut lines) => lines,
156
            _ => panic!("Attempted to use Lines view on non-Lines variant"),
157
        }
158
    }
159
}
160

            
161
impl CmdResult {
162
    #[must_use]
163
    pub fn as_lines(&self) -> LinesView<'_> {
164
        LinesView(self)
165
    }
166

            
167
    pub fn as_lines_mut(&mut self) -> LinesViewMut<'_> {
168
        LinesViewMut(self)
169
    }
170
}
171

            
172
impl fmt::Display for CmdResult {
173
1
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174
1
        match self {
175
1
            CmdResult::String(s) => write!(f, "{s}"),
176
            CmdResult::Rational(r) => write!(f, "{r}"),
177
            CmdResult::Data(d) => write!(f, "CmdResult<Data>: \"{}\" bytes", d.len()),
178
            CmdResult::Lines(l) => {
179
                // Find the maximum width for alignment
180
                let max_width = l.iter().map(std::string::String::len).max().unwrap_or(0);
181

            
182
                // Write header
183
                writeln!(f, "CmdResult<Lines>: {} items", l.len())?;
184

            
185
                // Write each item in a column format
186
                for (i, item) in l.iter().enumerate() {
187
                    writeln!(f, "{:>4}. {:<width$}", i + 1, item, width = max_width)?;
188
                }
189
                Ok(())
190
            }
191
            CmdResult::Entity(e) => write!(f, "CmdResult<FinanceEntity>: \"{e:?}\""),
192
            CmdResult::Entities(e) => write!(f, "CmdResult<FinanceEntities>: \"{}\"", e.len()),
193
            CmdResult::TaggedEntities {
194
                entities,
195
                pagination,
196
            } => match pagination {
197
                Some(p) => write!(
198
                    f,
199
                    "CmdResult<TaggedEntities>: {} of {} (offset: {})",
200
                    entities.len(),
201
                    p.total_count,
202
                    p.offset
203
                ),
204
                None => write!(f, "CmdResult<TaggedEntities>: \"{}\"", entities.len()),
205
            },
206
            CmdResult::CommodityInfoList(e) => {
207
                write!(f, "CmdResult<CommodityInfoList>: \"{}\"", e.len())
208
            }
209
            CmdResult::MultiCurrencyBalance(e) => {
210
                write!(f, "CmdResult<MultiCurrencyBalance>: \"{}\"", e.len())
211
            }
212
        }
213
1
    }
214
}