1
use crate::error::{Error, Result};
2
use scripting_format::{
3
    ACCOUNT_DATA_SIZE, AccountData, COMMODITY_DATA_SIZE, CommodityData, EntityHeader, EntityType,
4
    Operation, SPLIT_DATA_SIZE, SplitData, TAG_DATA_SIZE, TRANSACTION_DATA_SIZE, TagData,
5
    TransactionData,
6
};
7

            
8
pub struct EntityRef<'a> {
9
    pub header: EntityHeader,
10
    pub data: &'a [u8],
11
    pub strings_pool: &'a [u8],
12
}
13

            
14
impl<'a> EntityRef<'a> {
15
    pub fn entity_type(&self) -> Result<EntityType> {
16
        EntityType::try_from(self.header.entity_type).map_err(|()| Error::InvalidEntityType)
17
    }
18

            
19
    pub fn operation(&self) -> Result<Operation> {
20
        Operation::try_from(self.header.operation).map_err(|()| Error::InvalidOperation)
21
    }
22

            
23
    #[must_use]
24
    pub fn id(&self) -> [u8; 16] {
25
        self.header.id
26
    }
27

            
28
    #[must_use]
29
    pub fn parent_idx(&self) -> i32 {
30
        self.header.parent_idx
31
    }
32

            
33
    pub fn as_transaction(&self) -> Result<Transaction> {
34
        if self.entity_type()? != EntityType::Transaction {
35
            return Err(Error::InvalidEntityType);
36
        }
37
        if self.data.len() < TRANSACTION_DATA_SIZE {
38
            return Err(Error::OutOfBounds);
39
        }
40
        let data = TransactionData::from_bytes(self.data).ok_or(Error::InvalidHeader)?;
41
        Ok(Transaction {
42
            header: self.header,
43
            data,
44
        })
45
    }
46

            
47
    pub fn as_split(&self) -> Result<Split> {
48
        if self.entity_type()? != EntityType::Split {
49
            return Err(Error::InvalidEntityType);
50
        }
51
        if self.data.len() < SPLIT_DATA_SIZE {
52
            return Err(Error::OutOfBounds);
53
        }
54
        let data = SplitData::from_bytes(self.data).ok_or(Error::InvalidHeader)?;
55
        Ok(Split {
56
            header: self.header,
57
            data,
58
        })
59
    }
60

            
61
    pub fn as_tag(&self) -> Result<Tag<'a>> {
62
        if self.entity_type()? != EntityType::Tag {
63
            return Err(Error::InvalidEntityType);
64
        }
65
        if self.data.len() < TAG_DATA_SIZE {
66
            return Err(Error::OutOfBounds);
67
        }
68
        let data = TagData::from_bytes(self.data).ok_or(Error::InvalidHeader)?;
69
        Ok(Tag {
70
            header: self.header,
71
            data,
72
            strings_pool: self.strings_pool,
73
        })
74
    }
75

            
76
    pub fn as_account(&self) -> Result<Account<'a>> {
77
        if self.entity_type()? != EntityType::Account {
78
            return Err(Error::InvalidEntityType);
79
        }
80
        if self.data.len() < ACCOUNT_DATA_SIZE {
81
            return Err(Error::OutOfBounds);
82
        }
83
        let data = AccountData::from_bytes(self.data).ok_or(Error::InvalidHeader)?;
84
        Ok(Account {
85
            header: self.header,
86
            data,
87
            strings_pool: self.strings_pool,
88
        })
89
    }
90

            
91
    pub fn as_commodity(&self) -> Result<Commodity<'a>> {
92
        if self.entity_type()? != EntityType::Commodity {
93
            return Err(Error::InvalidEntityType);
94
        }
95
        if self.data.len() < COMMODITY_DATA_SIZE {
96
            return Err(Error::OutOfBounds);
97
        }
98
        let data = CommodityData::from_bytes(self.data).ok_or(Error::InvalidHeader)?;
99
        Ok(Commodity {
100
            header: self.header,
101
            data,
102
            strings_pool: self.strings_pool,
103
        })
104
    }
105
}
106

            
107
pub struct Transaction {
108
    pub header: EntityHeader,
109
    pub data: TransactionData,
110
}
111

            
112
impl Transaction {
113
    #[must_use]
114
    pub fn id(&self) -> [u8; 16] {
115
        self.header.id
116
    }
117

            
118
    #[must_use]
119
    pub fn post_date(&self) -> i64 {
120
        self.data.post_date
121
    }
122

            
123
    #[must_use]
124
    pub fn enter_date(&self) -> i64 {
125
        self.data.enter_date
126
    }
127

            
128
    #[must_use]
129
    pub fn split_count(&self) -> u32 {
130
        self.data.split_count
131
    }
132

            
133
    #[must_use]
134
    pub fn tag_count(&self) -> u32 {
135
        self.data.tag_count
136
    }
137

            
138
    #[must_use]
139
    pub fn is_multi_currency(&self) -> bool {
140
        self.data.is_multi_currency != 0
141
    }
142
}
143

            
144
pub struct Split {
145
    pub header: EntityHeader,
146
    pub data: SplitData,
147
}
148

            
149
impl Split {
150
    #[must_use]
151
    pub fn id(&self) -> [u8; 16] {
152
        self.header.id
153
    }
154

            
155
    #[must_use]
156
    pub fn account_id(&self) -> [u8; 16] {
157
        self.data.account_id
158
    }
159

            
160
    #[must_use]
161
    pub fn commodity_id(&self) -> [u8; 16] {
162
        self.data.commodity_id
163
    }
164

            
165
    #[must_use]
166
    pub fn value_num(&self) -> i64 {
167
        self.data.value_num
168
    }
169

            
170
    #[must_use]
171
    pub fn value_denom(&self) -> i64 {
172
        self.data.value_denom
173
    }
174

            
175
    #[must_use]
176
    pub fn reconcile_state(&self) -> u8 {
177
        self.data.reconcile_state
178
    }
179

            
180
    #[must_use]
181
    pub fn reconcile_date(&self) -> i64 {
182
        self.data.reconcile_date
183
    }
184

            
185
    #[must_use]
186
    pub fn parent_idx(&self) -> i32 {
187
        self.header.parent_idx
188
    }
189
}
190

            
191
pub struct Tag<'a> {
192
    pub header: EntityHeader,
193
    pub data: TagData,
194
    pub strings_pool: &'a [u8],
195
}
196

            
197
impl<'a> Tag<'a> {
198
    #[must_use]
199
    pub fn id(&self) -> [u8; 16] {
200
        self.header.id
201
    }
202

            
203
    pub fn name(&self) -> Result<&'a str> {
204
        let start = self.data.name_offset as usize;
205
        let end = start + self.data.name_len as usize;
206
        if end > self.strings_pool.len() {
207
            return Err(Error::OutOfBounds);
208
        }
209
        core::str::from_utf8(&self.strings_pool[start..end]).map_err(|_| Error::Utf8Error)
210
    }
211

            
212
    pub fn value(&self) -> Result<&'a str> {
213
        let start = self.data.value_offset as usize;
214
        let end = start + self.data.value_len as usize;
215
        if end > self.strings_pool.len() {
216
            return Err(Error::OutOfBounds);
217
        }
218
        core::str::from_utf8(&self.strings_pool[start..end]).map_err(|_| Error::Utf8Error)
219
    }
220

            
221
    #[must_use]
222
    pub fn parent_idx(&self) -> i32 {
223
        self.header.parent_idx
224
    }
225
}
226

            
227
pub struct Account<'a> {
228
    pub header: EntityHeader,
229
    pub data: AccountData,
230
    pub strings_pool: &'a [u8],
231
}
232

            
233
impl<'a> Account<'a> {
234
    #[must_use]
235
    pub fn id(&self) -> [u8; 16] {
236
        self.header.id
237
    }
238

            
239
    #[must_use]
240
    pub fn parent_account_id(&self) -> [u8; 16] {
241
        self.data.parent_account_id
242
    }
243

            
244
    pub fn name(&self) -> Result<&'a str> {
245
        let start = self.data.name_offset as usize;
246
        let end = start + self.data.name_len as usize;
247
        if end > self.strings_pool.len() {
248
            return Err(Error::OutOfBounds);
249
        }
250
        core::str::from_utf8(&self.strings_pool[start..end]).map_err(|_| Error::Utf8Error)
251
    }
252

            
253
    pub fn path(&self) -> Result<&'a str> {
254
        let start = self.data.path_offset as usize;
255
        let end = start + self.data.path_len as usize;
256
        if end > self.strings_pool.len() {
257
            return Err(Error::OutOfBounds);
258
        }
259
        core::str::from_utf8(&self.strings_pool[start..end]).map_err(|_| Error::Utf8Error)
260
    }
261

            
262
    #[must_use]
263
    pub fn tag_count(&self) -> u32 {
264
        self.data.tag_count
265
    }
266
}
267

            
268
pub struct Commodity<'a> {
269
    pub header: EntityHeader,
270
    pub data: CommodityData,
271
    pub strings_pool: &'a [u8],
272
}
273

            
274
impl<'a> Commodity<'a> {
275
    #[must_use]
276
    pub fn id(&self) -> [u8; 16] {
277
        self.header.id
278
    }
279

            
280
    pub fn symbol(&self) -> Result<&'a str> {
281
        let start = self.data.symbol_offset as usize;
282
        let end = start + self.data.symbol_len as usize;
283
        if end > self.strings_pool.len() {
284
            return Err(Error::OutOfBounds);
285
        }
286
        core::str::from_utf8(&self.strings_pool[start..end]).map_err(|_| Error::Utf8Error)
287
    }
288

            
289
    pub fn name(&self) -> Result<&'a str> {
290
        let start = self.data.name_offset as usize;
291
        let end = start + self.data.name_len as usize;
292
        if end > self.strings_pool.len() {
293
            return Err(Error::OutOfBounds);
294
        }
295
        core::str::from_utf8(&self.strings_pool[start..end]).map_err(|_| Error::Utf8Error)
296
    }
297

            
298
    #[must_use]
299
    pub fn fraction(&self) -> u32 {
300
        self.data.fraction
301
    }
302

            
303
    #[must_use]
304
    pub fn tag_count(&self) -> u32 {
305
        self.data.tag_count
306
    }
307
}