Skip to main content

ged_io/
debug.rs

1//! Improved Debug trait implementations for GEDCOM data structures.
2//!
3//! This module provides enhanced Debug implementations for core GEDCOM types,
4//! offering more concise and readable debug output compared to the default
5//! derived implementations.
6//!
7//! The improvements focus on:
8//! - Hiding empty collections and None values
9//! - Showing only the most relevant fields
10//! - Using more compact representations for nested structures
11
12use std::fmt;
13
14use crate::types::{
15    family::Family,
16    header::Header,
17    individual::{name::Name, Individual},
18    multimedia::Multimedia,
19    note::Note,
20    repository::Repository,
21    source::Source,
22    submission::Submission,
23    submitter::Submitter,
24    GedcomData,
25};
26
27/// A wrapper type that provides improved Debug output for `GedcomData`.
28///
29/// This wrapper hides empty collections and provides a more concise summary.
30pub struct GedcomDataDebug<'a>(pub &'a GedcomData);
31
32impl fmt::Debug for GedcomDataDebug<'_> {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        let mut debug = f.debug_struct("GedcomData");
35
36        if self.0.header.is_some() {
37            debug.field("header", &"Some(...)");
38        }
39
40        if !self.0.individuals.is_empty() {
41            debug.field(
42                "individuals",
43                &format!("[{} records]", self.0.individuals.len()),
44            );
45        }
46
47        if !self.0.families.is_empty() {
48            debug.field("families", &format!("[{} records]", self.0.families.len()));
49        }
50
51        if !self.0.sources.is_empty() {
52            debug.field("sources", &format!("[{} records]", self.0.sources.len()));
53        }
54
55        if !self.0.repositories.is_empty() {
56            debug.field(
57                "repositories",
58                &format!("[{} records]", self.0.repositories.len()),
59            );
60        }
61
62        if !self.0.multimedia.is_empty() {
63            debug.field(
64                "multimedia",
65                &format!("[{} records]", self.0.multimedia.len()),
66            );
67        }
68
69        if !self.0.submitters.is_empty() {
70            debug.field(
71                "submitters",
72                &format!("[{} records]", self.0.submitters.len()),
73            );
74        }
75
76        if !self.0.submissions.is_empty() {
77            debug.field(
78                "submissions",
79                &format!("[{} records]", self.0.submissions.len()),
80            );
81        }
82
83        if !self.0.custom_data.is_empty() {
84            debug.field(
85                "custom_data",
86                &format!("[{} records]", self.0.custom_data.len()),
87            );
88        }
89
90        debug.finish()
91    }
92}
93
94/// A wrapper type that provides improved Debug output for Individual.
95pub struct IndividualDebug<'a>(pub &'a Individual);
96
97impl fmt::Debug for IndividualDebug<'_> {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        let mut debug = f.debug_struct("Individual");
100
101        if let Some(ref xref) = self.0.xref {
102            debug.field("xref", xref);
103        }
104
105        if let Some(ref name) = self.0.name {
106            if let Some(ref value) = name.value {
107                debug.field("name", value);
108            }
109        }
110
111        if let Some(ref sex) = self.0.sex {
112            debug.field("sex", &format!("{}", sex.value));
113        }
114
115        if !self.0.events.is_empty() {
116            debug.field("events", &format!("[{} events]", self.0.events.len()));
117        }
118
119        if !self.0.families.is_empty() {
120            debug.field("families", &format!("[{} links]", self.0.families.len()));
121        }
122
123        if !self.0.attributes.is_empty() {
124            debug.field(
125                "attributes",
126                &format!("[{} attrs]", self.0.attributes.len()),
127            );
128        }
129
130        debug.finish_non_exhaustive()
131    }
132}
133
134/// A wrapper type that provides improved Debug output for Family.
135pub struct FamilyDebug<'a>(pub &'a Family);
136
137impl fmt::Debug for FamilyDebug<'_> {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        let mut debug = f.debug_struct("Family");
140
141        if let Some(ref xref) = self.0.xref {
142            debug.field("xref", xref);
143        }
144
145        if let Some(ref ind1) = self.0.individual1 {
146            debug.field("individual1", ind1);
147        }
148
149        if let Some(ref ind2) = self.0.individual2 {
150            debug.field("individual2", ind2);
151        }
152
153        if !self.0.children.is_empty() {
154            debug.field("children", &format!("[{} children]", self.0.children.len()));
155        }
156
157        if !self.0.events.is_empty() {
158            debug.field("events", &format!("[{} events]", self.0.events.len()));
159        }
160
161        debug.finish_non_exhaustive()
162    }
163}
164
165/// A wrapper type that provides improved Debug output for Source.
166pub struct SourceDebug<'a>(pub &'a Source);
167
168impl fmt::Debug for SourceDebug<'_> {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        let mut debug = f.debug_struct("Source");
171
172        if let Some(ref xref) = self.0.xref {
173            debug.field("xref", xref);
174        }
175
176        if let Some(ref title) = self.0.title {
177            debug.field("title", title);
178        }
179
180        if let Some(ref author) = self.0.author {
181            debug.field("author", author);
182        }
183
184        if let Some(ref abbr) = self.0.abbreviation {
185            debug.field("abbreviation", abbr);
186        }
187
188        debug.finish_non_exhaustive()
189    }
190}
191
192/// A wrapper type that provides improved Debug output for Repository.
193pub struct RepositoryDebug<'a>(pub &'a Repository);
194
195impl fmt::Debug for RepositoryDebug<'_> {
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        let mut debug = f.debug_struct("Repository");
198
199        if let Some(ref xref) = self.0.xref {
200            debug.field("xref", xref);
201        }
202
203        if let Some(ref name) = self.0.name {
204            debug.field("name", name);
205        }
206
207        if self.0.address.is_some() {
208            debug.field("address", &"Some(...)");
209        }
210
211        debug.finish_non_exhaustive()
212    }
213}
214
215/// A wrapper type that provides improved Debug output for Multimedia.
216pub struct MultimediaDebug<'a>(pub &'a Multimedia);
217
218impl fmt::Debug for MultimediaDebug<'_> {
219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220        let mut debug = f.debug_struct("Multimedia");
221
222        if let Some(ref xref) = self.0.xref {
223            debug.field("xref", xref);
224        }
225
226        if let Some(ref title) = self.0.title {
227            debug.field("title", title);
228        }
229
230        if let Some(ref file) = self.0.file {
231            if let Some(ref value) = file.value {
232                debug.field("file", value);
233            }
234        }
235
236        if let Some(ref form) = self.0.form {
237            if let Some(ref value) = form.value {
238                debug.field("format", value);
239            }
240        }
241
242        debug.finish_non_exhaustive()
243    }
244}
245
246/// A wrapper type that provides improved Debug output for Header.
247pub struct HeaderDebug<'a>(pub &'a Header);
248
249impl fmt::Debug for HeaderDebug<'_> {
250    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251        let mut debug = f.debug_struct("Header");
252
253        if let Some(ref gedcom) = self.0.gedcom {
254            if let Some(ref version) = gedcom.version {
255                debug.field("gedcom_version", version);
256            }
257        }
258
259        if let Some(ref source) = self.0.source {
260            if let Some(ref name) = source.name {
261                debug.field("source", name);
262            }
263        }
264
265        if let Some(ref encoding) = self.0.encoding {
266            if let Some(ref value) = encoding.value {
267                debug.field("encoding", value);
268            }
269        }
270
271        if let Some(ref date) = self.0.date {
272            if let Some(ref value) = date.value {
273                debug.field("date", value);
274            }
275        }
276
277        debug.finish_non_exhaustive()
278    }
279}
280
281/// A wrapper type that provides improved Debug output for Submitter.
282pub struct SubmitterDebug<'a>(pub &'a Submitter);
283
284impl fmt::Debug for SubmitterDebug<'_> {
285    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286        let mut debug = f.debug_struct("Submitter");
287
288        if let Some(ref xref) = self.0.xref {
289            debug.field("xref", xref);
290        }
291
292        if let Some(ref name) = self.0.name {
293            debug.field("name", name);
294        }
295
296        debug.finish_non_exhaustive()
297    }
298}
299
300/// A wrapper type that provides improved Debug output for Submission.
301pub struct SubmissionDebug<'a>(pub &'a Submission);
302
303impl fmt::Debug for SubmissionDebug<'_> {
304    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305        let mut debug = f.debug_struct("Submission");
306
307        if let Some(ref xref) = self.0.xref {
308            debug.field("xref", xref);
309        }
310
311        if let Some(ref family_file) = self.0.family_file_name {
312            debug.field("family_file", family_file);
313        }
314
315        if let Some(ref submitter_ref) = self.0.submitter_ref {
316            debug.field("submitter_ref", submitter_ref);
317        }
318
319        debug.finish_non_exhaustive()
320    }
321}
322
323/// A wrapper type that provides improved Debug output for Name.
324pub struct NameDebug<'a>(pub &'a Name);
325
326impl fmt::Debug for NameDebug<'_> {
327    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328        let mut debug = f.debug_struct("Name");
329
330        if let Some(ref value) = self.0.value {
331            debug.field("value", value);
332        }
333
334        if let Some(ref given) = self.0.given {
335            debug.field("given", given);
336        }
337
338        if let Some(ref surname) = self.0.surname {
339            debug.field("surname", surname);
340        }
341
342        if let Some(ref prefix) = self.0.prefix {
343            debug.field("prefix", prefix);
344        }
345
346        if let Some(ref suffix) = self.0.suffix {
347            debug.field("suffix", suffix);
348        }
349
350        debug.finish_non_exhaustive()
351    }
352}
353
354/// A wrapper type that provides improved Debug output for Note.
355pub struct NoteDebug<'a>(pub &'a Note);
356
357impl fmt::Debug for NoteDebug<'_> {
358    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359        let mut debug = f.debug_struct("Note");
360
361        if let Some(ref value) = self.0.value {
362            // Truncate long notes in debug output
363            const MAX_LEN: usize = 50;
364            if value.len() > MAX_LEN {
365                debug.field("value", &format!("{}...", &value[..MAX_LEN]));
366            } else {
367                debug.field("value", value);
368            }
369        }
370
371        if let Some(ref mime) = self.0.mime {
372            debug.field("mime", mime);
373        }
374
375        if let Some(ref lang) = self.0.language {
376            debug.field("language", lang);
377        }
378
379        debug.finish_non_exhaustive()
380    }
381}
382
383/// Extension trait for `GedcomData` to get improved debug output.
384pub trait ImprovedDebug {
385    /// Returns a wrapper that provides improved Debug output.
386    fn debug(&self) -> impl fmt::Debug;
387}
388
389impl ImprovedDebug for GedcomData {
390    fn debug(&self) -> impl fmt::Debug {
391        GedcomDataDebug(self)
392    }
393}
394
395impl ImprovedDebug for Individual {
396    fn debug(&self) -> impl fmt::Debug {
397        IndividualDebug(self)
398    }
399}
400
401impl ImprovedDebug for Family {
402    fn debug(&self) -> impl fmt::Debug {
403        FamilyDebug(self)
404    }
405}
406
407impl ImprovedDebug for Source {
408    fn debug(&self) -> impl fmt::Debug {
409        SourceDebug(self)
410    }
411}
412
413impl ImprovedDebug for Repository {
414    fn debug(&self) -> impl fmt::Debug {
415        RepositoryDebug(self)
416    }
417}
418
419impl ImprovedDebug for Multimedia {
420    fn debug(&self) -> impl fmt::Debug {
421        MultimediaDebug(self)
422    }
423}
424
425impl ImprovedDebug for Header {
426    fn debug(&self) -> impl fmt::Debug {
427        HeaderDebug(self)
428    }
429}
430
431impl ImprovedDebug for Submitter {
432    fn debug(&self) -> impl fmt::Debug {
433        SubmitterDebug(self)
434    }
435}
436
437impl ImprovedDebug for Submission {
438    fn debug(&self) -> impl fmt::Debug {
439        SubmissionDebug(self)
440    }
441}
442
443impl ImprovedDebug for Name {
444    fn debug(&self) -> impl fmt::Debug {
445        NameDebug(self)
446    }
447}
448
449impl ImprovedDebug for Note {
450    fn debug(&self) -> impl fmt::Debug {
451        NoteDebug(self)
452    }
453}
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458    use crate::Gedcom;
459
460    #[test]
461    fn test_gedcom_data_improved_debug() {
462        let sample = "\
463            0 HEAD\n\
464            1 GEDC\n\
465            2 VERS 5.5\n\
466            0 @I1@ INDI\n\
467            1 NAME John /Doe/\n\
468            0 @I2@ INDI\n\
469            1 NAME Jane /Doe/\n\
470            0 @F1@ FAM\n\
471            1 HUSB @I1@\n\
472            1 WIFE @I2@\n\
473            0 TRLR";
474
475        let mut gedcom = Gedcom::new(sample.chars()).unwrap();
476        let data = gedcom.parse_data().unwrap();
477
478        let debug_output = format!("{:?}", data.debug());
479        assert!(debug_output.contains("GedcomData"));
480        assert!(debug_output.contains("[2 records]")); // individuals
481        assert!(debug_output.contains("[1 records]")); // families
482    }
483
484    #[test]
485    fn test_individual_improved_debug() {
486        let sample = "\
487            0 HEAD\n\
488            1 GEDC\n\
489            2 VERS 5.5\n\
490            0 @I1@ INDI\n\
491            1 NAME John /Doe/\n\
492            1 SEX M\n\
493            1 BIRT\n\
494            2 DATE 1 JAN 1900\n\
495            0 TRLR";
496
497        let mut gedcom = Gedcom::new(sample.chars()).unwrap();
498        let data = gedcom.parse_data().unwrap();
499
500        let debug_output = format!("{:?}", data.individuals[0].debug());
501        assert!(debug_output.contains("Individual"));
502        assert!(debug_output.contains("@I1@"));
503        assert!(debug_output.contains("John /Doe/"));
504        assert!(debug_output.contains("Male"));
505        assert!(debug_output.contains("[1 events]"));
506    }
507
508    #[test]
509    fn test_family_improved_debug() {
510        let sample = "\
511            0 HEAD\n\
512            1 GEDC\n\
513            2 VERS 5.5\n\
514            0 @F1@ FAM\n\
515            1 HUSB @I1@\n\
516            1 WIFE @I2@\n\
517            1 CHIL @I3@\n\
518            1 CHIL @I4@\n\
519            0 TRLR";
520
521        let mut gedcom = Gedcom::new(sample.chars()).unwrap();
522        let data = gedcom.parse_data().unwrap();
523
524        let debug_output = format!("{:?}", data.families[0].debug());
525        assert!(debug_output.contains("Family"));
526        assert!(debug_output.contains("@F1@"));
527        assert!(debug_output.contains("@I1@"));
528        assert!(debug_output.contains("@I2@"));
529        assert!(debug_output.contains("[2 children]"));
530    }
531
532    #[test]
533    fn test_source_improved_debug() {
534        let sample = "\
535            0 HEAD\n\
536            1 GEDC\n\
537            2 VERS 5.5\n\
538            0 @S1@ SOUR\n\
539            1 TITL Census Records\n\
540            1 AUTH Government\n\
541            0 TRLR";
542
543        let mut gedcom = Gedcom::new(sample.chars()).unwrap();
544        let data = gedcom.parse_data().unwrap();
545
546        let debug_output = format!("{:?}", data.sources[0].debug());
547        assert!(debug_output.contains("Source"));
548        assert!(debug_output.contains("@S1@"));
549        assert!(debug_output.contains("Census Records"));
550        assert!(debug_output.contains("Government"));
551    }
552
553    #[test]
554    fn test_header_improved_debug() {
555        let sample = "\
556            0 HEAD\n\
557            1 GEDC\n\
558            2 VERS 5.5\n\
559            1 SOUR TestApp\n\
560            2 NAME Test Application\n\
561            1 CHAR UTF-8\n\
562            0 TRLR";
563
564        let mut gedcom = Gedcom::new(sample.chars()).unwrap();
565        let data = gedcom.parse_data().unwrap();
566
567        let header = data.header.as_ref().unwrap();
568        let debug_output = format!("{:?}", header.debug());
569        assert!(debug_output.contains("Header"));
570        assert!(debug_output.contains("5.5"));
571        assert!(debug_output.contains("UTF-8"));
572    }
573
574    #[test]
575    fn test_note_truncation_in_debug() {
576        let long_content = "A".repeat(100);
577        let note = Note {
578            value: Some(long_content),
579            mime: None,
580            translation: None,
581            citation: None,
582            language: None,
583        };
584
585        let debug_output = format!("{:?}", note.debug());
586        assert!(debug_output.contains("..."));
587        // Should be truncated to 50 chars + "..."
588        assert!(!debug_output.contains(&"A".repeat(100)));
589    }
590
591    #[test]
592    fn test_name_improved_debug() {
593        let name = Name {
594            value: Some("John /Doe/".to_string()),
595            given: Some("John".to_string()),
596            surname: Some("Doe".to_string()),
597            prefix: None,
598            surname_prefix: None,
599            note: None,
600            suffix: Some("Jr.".to_string()),
601            nickname: None,
602            source: Vec::new(),
603            name_type: None,
604            phonetic: Vec::new(),
605            romanized: Vec::new(),
606            custom_data: Vec::new(),
607        };
608
609        let debug_output = format!("{:?}", name.debug());
610        assert!(debug_output.contains("Name"));
611        assert!(debug_output.contains("John /Doe/"));
612        assert!(debug_output.contains("given"));
613        assert!(debug_output.contains("surname"));
614        assert!(debug_output.contains("Jr."));
615    }
616}