1
use std::collections::HashMap;
2

            
3
use finance::split::Split;
4
use finance::transaction::Transaction;
5

            
6
use crate::format::{
7
    AccountData, BASE_OFFSET, CommodityData, ContextType, ENTITY_HEADER_SIZE, EntityFlags,
8
    EntityHeader, EntityType, GLOBAL_HEADER_SIZE, GlobalHeader, OUTPUT_HEADER_SIZE, Operation,
9
    OutputHeader, SplitData, TAG_DATA_SIZE, TagData, TransactionData,
10
};
11

            
12
114
fn transaction_to_data(tx: &Transaction) -> TransactionData {
13
114
    TransactionData {
14
114
        post_date: tx.post_date.timestamp_millis(),
15
114
        enter_date: tx.enter_date.timestamp_millis(),
16
114
        split_count: 0,
17
114
        tag_count: 0,
18
114
        is_multi_currency: 0,
19
114
        reserved: [0; 23],
20
114
    }
21
114
}
22

            
23
114
fn split_to_data(split: &Split) -> SplitData {
24
    SplitData {
25
114
        account_id: *split.account_id.as_bytes(),
26
114
        commodity_id: *split.commodity_id.as_bytes(),
27
114
        value_num: split.value_num,
28
114
        value_denom: split.value_denom,
29
114
        reconcile_state: split.reconcile_state.map_or(0, u8::from),
30
114
        reserved: [0; 7],
31
114
        reconcile_date: split
32
114
            .reconcile_date
33
114
            .map_or(0, |d: chrono::DateTime<chrono::Utc>| d.timestamp_millis()),
34
    }
35
114
}
36

            
37
pub struct MemorySerializer {
38
    context_type: ContextType,
39
    primary_entity_type: EntityType,
40
    primary_entity_idx: u32,
41
    entities: Vec<SerializedEntity>,
42
    strings_pool: Vec<u8>,
43
    string_cache: HashMap<String, (u32, u16)>,
44
}
45

            
46
struct SerializedEntity {
47
    header: EntityHeader,
48
    data: Vec<u8>,
49
}
50

            
51
impl Default for MemorySerializer {
52
    fn default() -> Self {
53
        Self::new()
54
    }
55
}
56

            
57
impl MemorySerializer {
58
    #[must_use]
59
3175
    pub fn new() -> Self {
60
3175
        Self {
61
3175
            context_type: ContextType::EntityCreate,
62
3175
            primary_entity_type: EntityType::Transaction,
63
3175
            primary_entity_idx: 0,
64
3175
            entities: Vec::new(),
65
3175
            strings_pool: Vec::new(),
66
3175
            string_cache: HashMap::new(),
67
3175
        }
68
3175
    }
69

            
70
3174
    pub fn set_context(&mut self, context_type: ContextType, primary_entity_type: EntityType) {
71
3174
        self.context_type = context_type;
72
3174
        self.primary_entity_type = primary_entity_type;
73
3174
    }
74

            
75
172
    pub fn set_primary(&mut self, entity_idx: u32) {
76
172
        self.primary_entity_idx = entity_idx;
77
172
    }
78

            
79
271
    pub fn add_string(&mut self, s: &str) -> (u32, u16) {
80
271
        if let Some(&cached) = self.string_cache.get(s) {
81
1
            return cached;
82
270
        }
83
270
        let offset = self.strings_pool.len() as u32;
84
270
        let len = s.len() as u16;
85
270
        self.strings_pool.extend_from_slice(s.as_bytes());
86
270
        self.string_cache.insert(s.to_string(), (offset, len));
87
270
        (offset, len)
88
271
    }
89

            
90
39
    pub fn add_transaction(
91
39
        &mut self,
92
39
        id: [u8; 16],
93
39
        parent_idx: i32,
94
39
        is_primary: bool,
95
39
        is_context: bool,
96
39
        post_date: i64,
97
39
        enter_date: i64,
98
39
        split_count: u32,
99
39
        tag_count: u32,
100
39
        is_multi_currency: bool,
101
39
    ) -> u32 {
102
39
        let flags = EntityFlags::make(is_primary, is_context);
103
39
        let data = TransactionData {
104
39
            post_date,
105
39
            enter_date,
106
39
            split_count,
107
39
            tag_count,
108
39
            is_multi_currency: u8::from(is_multi_currency),
109
39
            reserved: [0; 23],
110
39
        };
111
39
        let header = EntityHeader::new(
112
39
            EntityType::Transaction,
113
39
            Operation::Nop,
114
39
            flags,
115
39
            id,
116
39
            parent_idx,
117
            0,
118
39
            data.to_bytes().len() as u32,
119
        );
120
39
        let idx = self.entities.len() as u32;
121
39
        self.entities.push(SerializedEntity {
122
39
            header,
123
39
            data: data.to_bytes().to_vec(),
124
39
        });
125
39
        idx
126
39
    }
127

            
128
77
    pub fn add_split(
129
77
        &mut self,
130
77
        id: [u8; 16],
131
77
        parent_idx: i32,
132
77
        is_primary: bool,
133
77
        is_context: bool,
134
77
        account_id: [u8; 16],
135
77
        commodity_id: [u8; 16],
136
77
        value_num: i64,
137
77
        value_denom: i64,
138
77
        reconcile_state: u8,
139
77
        reconcile_date: i64,
140
77
    ) -> u32 {
141
77
        let flags = EntityFlags::make(is_primary, is_context);
142
77
        let data = SplitData {
143
77
            account_id,
144
77
            commodity_id,
145
77
            value_num,
146
77
            value_denom,
147
77
            reconcile_state,
148
77
            reserved: [0; 7],
149
77
            reconcile_date,
150
77
        };
151
77
        let header = EntityHeader::new(
152
77
            EntityType::Split,
153
77
            Operation::Nop,
154
77
            flags,
155
77
            id,
156
77
            parent_idx,
157
            0,
158
77
            data.to_bytes().len() as u32,
159
        );
160
77
        let idx = self.entities.len() as u32;
161
77
        self.entities.push(SerializedEntity {
162
77
            header,
163
77
            data: data.to_bytes().to_vec(),
164
77
        });
165
77
        idx
166
77
    }
167

            
168
115
    pub fn add_tag(
169
115
        &mut self,
170
115
        id: [u8; 16],
171
115
        parent_idx: i32,
172
115
        is_primary: bool,
173
115
        is_context: bool,
174
115
        name: &str,
175
115
        value: &str,
176
115
    ) -> u32 {
177
115
        let flags = EntityFlags::make(is_primary, is_context);
178
115
        let (name_offset, name_len) = self.add_string(name);
179
115
        let (value_offset, value_len) = self.add_string(value);
180
115
        let data = TagData {
181
115
            name_offset,
182
115
            value_offset,
183
115
            name_len,
184
115
            value_len,
185
115
            reserved: [0; 4],
186
115
        };
187
115
        let header = EntityHeader::new(
188
115
            EntityType::Tag,
189
115
            Operation::Nop,
190
115
            flags,
191
115
            id,
192
115
            parent_idx,
193
            0,
194
115
            TAG_DATA_SIZE as u32,
195
        );
196
115
        let idx = self.entities.len() as u32;
197
115
        self.entities.push(SerializedEntity {
198
115
            header,
199
115
            data: data.to_bytes().to_vec(),
200
115
        });
201
115
        idx
202
115
    }
203

            
204
19
    pub fn add_account(
205
19
        &mut self,
206
19
        id: [u8; 16],
207
19
        parent_idx: i32,
208
19
        is_primary: bool,
209
19
        is_context: bool,
210
19
        parent_account_id: [u8; 16],
211
19
        name: &str,
212
19
        path: &str,
213
19
        tag_count: u32,
214
19
    ) -> u32 {
215
19
        let flags = EntityFlags::make(is_primary, is_context);
216
19
        let (name_offset, name_len) = self.add_string(name);
217
19
        let (path_offset, path_len) = self.add_string(path);
218
19
        let data = AccountData {
219
19
            parent_account_id,
220
19
            name_offset,
221
19
            path_offset,
222
19
            tag_count,
223
19
            name_len,
224
19
            path_len,
225
19
            reserved: [0; 16],
226
19
        };
227
19
        let header = EntityHeader::new(
228
19
            EntityType::Account,
229
19
            Operation::Nop,
230
19
            flags,
231
19
            id,
232
19
            parent_idx,
233
            0,
234
19
            data.to_bytes().len() as u32,
235
        );
236
19
        let idx = self.entities.len() as u32;
237
19
        self.entities.push(SerializedEntity {
238
19
            header,
239
19
            data: data.to_bytes().to_vec(),
240
19
        });
241
19
        idx
242
19
    }
243

            
244
    pub fn add_commodity(
245
        &mut self,
246
        id: [u8; 16],
247
        parent_idx: i32,
248
        is_primary: bool,
249
        is_context: bool,
250
        symbol: &str,
251
        name: &str,
252
        tag_count: u32,
253
    ) -> u32 {
254
        let flags = EntityFlags::make(is_primary, is_context);
255
        let (symbol_offset, symbol_len) = self.add_string(symbol);
256
        let (name_offset, name_len) = self.add_string(name);
257
        let data = CommodityData {
258
            symbol_offset,
259
            name_offset,
260
            tag_count,
261
            symbol_len,
262
            name_len,
263
            reserved: [0; 16],
264
        };
265
        let header = EntityHeader::new(
266
            EntityType::Commodity,
267
            Operation::Nop,
268
            flags,
269
            id,
270
            parent_idx,
271
            0,
272
            data.to_bytes().len() as u32,
273
        );
274
        let idx = self.entities.len() as u32;
275
        self.entities.push(SerializedEntity {
276
            header,
277
            data: data.to_bytes().to_vec(),
278
        });
279
        idx
280
    }
281

            
282
    #[must_use]
283
1
    pub fn entity_count(&self) -> u32 {
284
1
        self.entities.len() as u32
285
1
    }
286

            
287
114
    pub fn add_transaction_from(
288
114
        &mut self,
289
114
        tx: &Transaction,
290
114
        is_primary: bool,
291
114
        split_count: u32,
292
114
        tag_count: u32,
293
114
        is_multi_currency: bool,
294
114
    ) -> u32 {
295
114
        let flags = EntityFlags::make(is_primary, false);
296
114
        let mut data = transaction_to_data(tx);
297
114
        data.split_count = split_count;
298
114
        data.tag_count = tag_count;
299
114
        data.is_multi_currency = u8::from(is_multi_currency);
300
114
        let header = EntityHeader::new(
301
114
            EntityType::Transaction,
302
114
            Operation::Nop,
303
114
            flags,
304
114
            *tx.id.as_bytes(),
305
            -1,
306
            0,
307
114
            data.to_bytes().len() as u32,
308
        );
309
114
        let idx = self.entities.len() as u32;
310
114
        self.entities.push(SerializedEntity {
311
114
            header,
312
114
            data: data.to_bytes().to_vec(),
313
114
        });
314
114
        idx
315
114
    }
316

            
317
114
    pub fn add_split_from(&mut self, split: &Split, parent_idx: i32) -> u32 {
318
114
        let data = split_to_data(split);
319
114
        let header = EntityHeader::new(
320
114
            EntityType::Split,
321
114
            Operation::Nop,
322
            0,
323
114
            *split.id.as_bytes(),
324
114
            parent_idx,
325
            0,
326
114
            data.to_bytes().len() as u32,
327
        );
328
114
        let idx = self.entities.len() as u32;
329
114
        self.entities.push(SerializedEntity {
330
114
            header,
331
114
            data: data.to_bytes().to_vec(),
332
114
        });
333
114
        idx
334
114
    }
335

            
336
    #[must_use]
337
3174
    pub fn finalize(mut self, output_size: u32) -> Vec<u8> {
338
3174
        let entity_count = self.entities.len() as u32;
339
3174
        let entities_offset = BASE_OFFSET + GLOBAL_HEADER_SIZE as u32;
340

            
341
3174
        let mut entities_total_size = 0u32;
342
3176
        for entity in &self.entities {
343
478
            entities_total_size += ENTITY_HEADER_SIZE as u32 + entity.data.len() as u32;
344
478
        }
345

            
346
3174
        let strings_pool_offset = entities_offset + entities_total_size;
347
3174
        let strings_pool_size = self.strings_pool.len() as u32;
348
3174
        let output_offset = strings_pool_offset + strings_pool_size;
349

            
350
3174
        let output_header = OutputHeader::new(entity_count);
351

            
352
3174
        let mut global_header = GlobalHeader::new(
353
3174
            self.context_type,
354
3174
            self.primary_entity_type,
355
3174
            entity_count,
356
3174
            self.primary_entity_idx,
357
        );
358
3174
        global_header.entities_offset = entities_offset;
359
3174
        global_header.strings_pool_offset = strings_pool_offset;
360
3174
        global_header.strings_pool_size = strings_pool_size;
361
3174
        global_header.output_offset = output_offset;
362
3174
        global_header.output_size = output_size;
363

            
364
3174
        let total_size = GLOBAL_HEADER_SIZE
365
3174
            + entities_total_size as usize
366
3174
            + strings_pool_size as usize
367
3174
            + output_size as usize;
368
3174
        let mut buffer = vec![0u8; total_size];
369

            
370
3174
        buffer[..GLOBAL_HEADER_SIZE].copy_from_slice(global_header.as_bytes());
371

            
372
3174
        let mut current_offset = entities_offset;
373
3174
        let mut write_pos = GLOBAL_HEADER_SIZE;
374

            
375
3176
        for entity in &mut self.entities {
376
478
            entity.header.data_offset = current_offset + ENTITY_HEADER_SIZE as u32;
377
478
            let header_bytes = entity.header.to_bytes();
378
478
            buffer[write_pos..write_pos + ENTITY_HEADER_SIZE].copy_from_slice(&header_bytes);
379
478
            write_pos += ENTITY_HEADER_SIZE;
380
478
            buffer[write_pos..write_pos + entity.data.len()].copy_from_slice(&entity.data);
381
478
            write_pos += entity.data.len();
382
478
            current_offset += ENTITY_HEADER_SIZE as u32 + entity.data.len() as u32;
383
478
        }
384

            
385
3174
        buffer[write_pos..write_pos + self.strings_pool.len()].copy_from_slice(&self.strings_pool);
386
3174
        write_pos += self.strings_pool.len();
387

            
388
3174
        buffer[write_pos..write_pos + OUTPUT_HEADER_SIZE]
389
3174
            .copy_from_slice(&output_header.to_bytes());
390

            
391
3174
        buffer
392
3174
    }
393
}
394

            
395
#[cfg(test)]
396
mod tests {
397
    use super::*;
398
    use crate::format::MAGIC_NOMI;
399

            
400
    #[test]
401
1
    fn test_serializer_basic() {
402
1
        let mut serializer = MemorySerializer::new();
403
1
        serializer.set_context(ContextType::EntityCreate, EntityType::Transaction);
404

            
405
1
        let tx_id = [1u8; 16];
406
1
        let tx_idx = serializer.add_transaction(tx_id, -1, true, false, 1000, 2000, 2, 1, false);
407
1
        serializer.set_primary(tx_idx);
408

            
409
1
        let split_id = [2u8; 16];
410
1
        let account_id = [3u8; 16];
411
1
        let commodity_id = [4u8; 16];
412
1
        serializer.add_split(
413
1
            split_id,
414
1
            tx_idx as i32,
415
            false,
416
            false,
417
1
            account_id,
418
1
            commodity_id,
419
            -5000,
420
            100,
421
            0,
422
            0,
423
        );
424

            
425
1
        let tag_id = [5u8; 16];
426
1
        serializer.add_tag(
427
1
            tag_id,
428
1
            tx_idx as i32,
429
            false,
430
            false,
431
1
            "note",
432
1
            "test transaction",
433
        );
434

            
435
1
        assert_eq!(serializer.entity_count(), 3);
436

            
437
1
        let buffer = serializer.finalize(1024);
438

            
439
1
        let header = GlobalHeader::from_bytes(&buffer).unwrap();
440
1
        assert_eq!(header.magic, MAGIC_NOMI);
441
1
        assert_eq!(header.input_entity_count, 3);
442
1
        assert_eq!(header.context_type, ContextType::EntityCreate as u8);
443
1
        assert_eq!(header.primary_entity_type, EntityType::Transaction as u8);
444
1
    }
445

            
446
    #[test]
447
1
    fn test_string_deduplication() {
448
1
        let mut serializer = MemorySerializer::new();
449
1
        let (offset1, len1) = serializer.add_string("test");
450
1
        let (offset2, len2) = serializer.add_string("test");
451
1
        let (offset3, _) = serializer.add_string("other");
452

            
453
1
        assert_eq!(offset1, offset2);
454
1
        assert_eq!(len1, len2);
455
1
        assert_ne!(offset1, offset3);
456
1
    }
457
}