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, From)]
33
pub enum FinanceEntity {
34
    Commodity(Commodity),
35
    Tag(Tag),
36
    Split(Split),
37
    Transaction(Transaction),
38
    Price(Price),
39
    Account(Account),
40
}
41

            
42
#[derive(Debug, From)]
43
pub enum Argument {
44
    String(String),
45
    Rational(Rational64),
46
    Uuid(Uuid),
47
    Data(Vec<u8>),
48
    FinanceEntity(FinanceEntity),
49
    FinanceEntities(Vec<FinanceEntity>),
50
    DateTime(DateTime<Utc>),
51
}
52

            
53
impl From<Argument> for String {
54
    fn from(arg: Argument) -> Self {
55
        match arg {
56
            Argument::String(s) => s,
57
            _ => panic!("Cannot convert {arg:?} to String"),
58
        }
59
    }
60
}
61

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

            
71
impl From<Argument> for Vec<u8> {
72
    fn from(arg: Argument) -> Self {
73
        match arg {
74
            Argument::Data(d) => d,
75
            _ => panic!("Cannot convert {arg:?} to Vec<u8>"),
76
        }
77
    }
78
}
79

            
80
#[derive(Debug, Error)]
81
pub enum CmdError {
82
    #[error("Wrong arguments: {0}")]
83
    Args(String),
84
    #[error("Config: {0}")]
85
    Config(#[from] ConfigError),
86
    #[error("Database: {0}")]
87
    DB(#[from] sqlx::Error),
88
    #[error("Server: {0}")]
89
    Server(#[from] ServerError),
90
    #[error("Finance: {0}")]
91
    Finance(#[from] FinanceError),
92
}
93

            
94
// Implementing CmdResult as an enum with String and Rational returning options
95
#[derive(Debug, From)]
96
pub enum CmdResult {
97
    String(String),
98
    Rational(Rational64),
99
    Data(Vec<u8>),
100
    Lines(Vec<String>),
101
    Entity(FinanceEntity),
102
    Entities(Vec<FinanceEntity>),
103
    TaggedEntities(Vec<(FinanceEntity, HashMap<String, FinanceEntity>)>),
104
    CommodityInfoList(Vec<CommodityInfo>),
105
    MultiCurrencyBalance(Vec<(Commodity, Rational64)>),
106
}
107

            
108
pub struct LinesView<'view>(&'view CmdResult);
109
pub struct LinesViewMut<'view>(&'view mut CmdResult);
110

            
111
impl std::ops::Deref for LinesView<'_> {
112
    type Target = Vec<String>;
113

            
114
    fn deref(&self) -> &Self::Target {
115
        match self.0 {
116
            CmdResult::Lines(lines) => lines,
117
            _ => panic!("Attempted to use Lines view on non-Lines variant"),
118
        }
119
    }
120
}
121

            
122
impl std::ops::Deref for LinesViewMut<'_> {
123
    type Target = Vec<String>;
124

            
125
    fn deref(&self) -> &Self::Target {
126
        match self.0 {
127
            &mut CmdResult::Lines(ref lines) => lines,
128
            _ => panic!("Attempted to use Lines view on non-Lines variant"),
129
        }
130
    }
131
}
132

            
133
impl std::ops::DerefMut for LinesViewMut<'_> {
134
    fn deref_mut(&mut self) -> &mut Self::Target {
135
        match self.0 {
136
            &mut CmdResult::Lines(ref mut lines) => lines,
137
            _ => panic!("Attempted to use Lines view on non-Lines variant"),
138
        }
139
    }
140
}
141

            
142
impl CmdResult {
143
    #[must_use]
144
    pub fn as_lines(&self) -> LinesView<'_> {
145
        LinesView(self)
146
    }
147

            
148
    pub fn as_lines_mut(&mut self) -> LinesViewMut<'_> {
149
        LinesViewMut(self)
150
    }
151
}
152

            
153
impl fmt::Display for CmdResult {
154
2
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155
2
        match self {
156
2
            CmdResult::String(s) => write!(f, "{s}"),
157
            CmdResult::Rational(r) => write!(f, "{r}"),
158
            CmdResult::Data(d) => write!(f, "CmdResult<Data>: \"{}\" bytes", d.len()),
159
            CmdResult::Lines(l) => {
160
                // Find the maximum width for alignment
161
                let max_width = l.iter().map(std::string::String::len).max().unwrap_or(0);
162

            
163
                // Write header
164
                writeln!(f, "CmdResult<Lines>: {} items", l.len())?;
165

            
166
                // Write each item in a column format
167
                for (i, item) in l.iter().enumerate() {
168
                    writeln!(f, "{:>4}. {:<width$}", i + 1, item, width = max_width)?;
169
                }
170
                Ok(())
171
            }
172
            CmdResult::Entity(e) => write!(f, "CmdResult<FinanceEntity>: \"{e:?}\""),
173
            CmdResult::Entities(e) => write!(f, "CmdResult<FinanceEntities>: \"{}\"", e.len()),
174
            CmdResult::TaggedEntities(e) => write!(f, "CmdResult<TaggedEntities>: \"{}\"", e.len()),
175
            CmdResult::CommodityInfoList(e) => {
176
                write!(f, "CmdResult<CommodityInfoList>: \"{}\"", e.len())
177
            }
178
            CmdResult::MultiCurrencyBalance(e) => {
179
                write!(f, "CmdResult<MultiCurrencyBalance>: \"{}\"", e.len())
180
            }
181
        }
182
2
    }
183
}