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
22
    pub fn entity_type(&self) -> Result<EntityType> {
16
22
        EntityType::try_from(self.header.entity_type).map_err(|()| Error::InvalidEntityType)
17
22
    }
18

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
138
    #[must_use]
139
1
    pub fn is_multi_currency(&self) -> bool {
140
1
        self.data.is_multi_currency != 0
141
1
    }
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
1
    pub fn account_id(&self) -> [u8; 16] {
157
1
        self.data.account_id
158
1
    }
159

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

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

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

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

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

            
185
    #[must_use]
186
1
    pub fn parent_idx(&self) -> i32 {
187
1
        self.header.parent_idx
188
1
    }
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
3
    pub fn name(&self) -> Result<&'a str> {
204
3
        let start = self.data.name_offset as usize;
205
3
        let end = start + self.data.name_len as usize;
206
3
        if end > self.strings_pool.len() {
207
1
            return Err(Error::OutOfBounds);
208
2
        }
209
2
        core::str::from_utf8(&self.strings_pool[start..end]).map_err(|_| Error::Utf8Error)
210
3
    }
211

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

            
221
    #[must_use]
222
1
    pub fn parent_idx(&self) -> i32 {
223
1
        self.header.parent_idx
224
1
    }
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
1
    pub fn parent_account_id(&self) -> [u8; 16] {
241
1
        self.data.parent_account_id
242
1
    }
243

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

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

            
262
    #[must_use]
263
1
    pub fn tag_count(&self) -> u32 {
264
1
        self.data.tag_count
265
1
    }
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
2
    pub fn symbol(&self) -> Result<&'a str> {
281
2
        let start = self.data.symbol_offset as usize;
282
2
        let end = start + self.data.symbol_len as usize;
283
2
        if end > self.strings_pool.len() {
284
1
            return Err(Error::OutOfBounds);
285
1
        }
286
1
        core::str::from_utf8(&self.strings_pool[start..end]).map_err(|_| Error::Utf8Error)
287
2
    }
288

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

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