Skip to main content

crossref_xml/
body.rs

1use serde::Serialize;
2use validator::{Validate, ValidationError};
3
4use crate::journal::Journal;
5
6#[derive(Debug, Clone, Default, Serialize, Validate)]
7#[serde(rename = "body")]
8pub struct Body {
9    // TODO: Ensure it is one of the following only
10    #[serde(default, skip_serializing_if = "Vec::is_empty")]
11    #[validate(nested)]
12    pub journal: Vec<Journal>,
13    // #[serde(default, skip_serializing_if = "Vec::is_empty")]
14    // book: Vec<Book>,
15    // #[serde(default, skip_serializing_if = "Vec::is_empty")]
16    // conference: Vec<conference>,
17    // #[serde(default, skip_serializing_if = "Vec::is_empty")]
18    // sa_component: Vec<sa_component>,
19    // #[serde(default, skip_serializing_if = "Vec::is_empty")]
20    // dissertation: Vec<dissertation>,
21    // #[serde(default, skip_serializing_if = "Vec::is_empty")]
22    // report-paper: Vec<report>,
23    // #[serde(default, skip_serializing_if = "Vec::is_empty")]
24    // standard: Vec<standard>,
25    // #[serde(default, skip_serializing_if = "Vec::is_empty")]
26    // database: Vec<database>,
27    // #[serde(default, skip_serializing_if = "Vec::is_empty")]
28    // peer_review: Vec<peer_review>,
29    // #[serde(default, skip_serializing_if = "Vec::is_empty")]
30    // pending_publication: Vec<pending_publication>,
31    // #[serde(default, skip_serializing_if = "Vec::is_empty")]
32    // posted_content: Vec<posted_content>
33}
34
35impl Body {
36    pub fn is_empty(&self) -> bool {
37        self.journal.is_empty()
38        // && self.book.is_empty()
39        // && self.conference.is_empty()
40        // && self.sa_component.is_empty()
41        // && self.dissertation.is_empty()
42        // && self.report_paper.is_empty()
43        // && self.standard.is_empty()
44        // && self.database.is_empty()
45        // && self.peer_review.is_empty()
46        // && self.pending_publication.is_empty()
47        // && self.posted_content.is_empty()
48    }
49}
50
51#[allow(dead_code)]
52fn validate_body_has_exactly_one(body: &Body) -> Result<(), ValidationError> {
53    let count = [
54        !body.journal.is_empty(),
55        // !body.book.is_empty(),
56        // !body.conference.is_empty(),
57        // !body.sa_component.is_empty(),
58        // !body.dissertation.is_empty(),
59        // !body.report_paper.is_empty(),
60        // !body.standard.is_empty(),
61        // !body.database.is_empty(),
62        // !body.peer_review.is_empty(),
63        // !body.pending_publication.is_empty(),
64        // !body.posted_content.is_empty(),
65    ]
66    .iter()
67    .filter(|&&x| x)
68    .count();
69
70    match count {
71        0 => Err(ValidationError::new("body_empty")
72            .with_message("Body must contain exactly one content type".into())),
73        1 => Ok(()),
74        _ => Err(ValidationError::new("body_multiple")
75            .with_message("Body must contain only one content type, found multiple".into())),
76    }
77}
78
79#[cfg(test)]
80mod unit {
81    use super::*;
82    use crate::enums::{ContentVersion, Iso639_1, MediaType};
83    use crate::journal::Journal;
84    use crate::journal::metadata::{ArchiveLocations, DoiData, Issn, JournalMetadata, Resource};
85
86    fn valid_journal() -> Journal {
87        Journal {
88            journal_metadata: JournalMetadata {
89                lang: Iso639_1::En,
90                full_title: "Example Journal".to_string(),
91                abbrev_title: Some("Ex. J.".to_string()),
92                issn: Some(Issn {
93                    media_type: MediaType::Electronic,
94                    value: "1234-5678".to_string(),
95                }),
96                coden: None,
97                archive_locations: ArchiveLocations::default(),
98                doi_data: DoiData {
99                    doi: "10.1234/journal".to_string(),
100                    timestamp: (),
101                    resource: Resource {
102                        value: "https://example.com/journal".to_string(),
103                        mime_type: None,
104                        content_version: ContentVersion::Vor,
105                    },
106                },
107            },
108            journal_issue: None,
109            journal_article: vec![],
110        }
111    }
112
113    #[test]
114    fn empty_body_passes() {
115        let body = Body::default();
116        assert!(body.validate().is_ok());
117        assert!(body.is_empty());
118    }
119
120    #[test]
121    fn body_with_journal_passes() {
122        let body = Body {
123            journal: vec![valid_journal()],
124        };
125        assert!(body.validate().is_ok());
126        assert!(!body.is_empty());
127    }
128
129    #[test]
130    fn body_with_multiple_journals_passes() {
131        let body = Body {
132            journal: vec![valid_journal(), valid_journal()],
133        };
134        assert!(body.validate().is_ok());
135    }
136
137    #[test]
138    fn body_with_invalid_journal_fails() {
139        let mut journal = valid_journal();
140        journal.journal_metadata.full_title = "".to_string(); // Invalid: too short
141        let body = Body {
142            journal: vec![journal],
143        };
144        assert!(body.validate().is_err());
145    }
146
147    mod validate_body_has_exactly_one_tests {
148        use super::*;
149
150        #[test]
151        fn empty_body_fails() {
152            let body = Body::default();
153            assert!(validate_body_has_exactly_one(&body).is_err());
154        }
155
156        #[test]
157        fn body_with_one_content_type_passes() {
158            let body = Body {
159                journal: vec![valid_journal()],
160            };
161            assert!(validate_body_has_exactly_one(&body).is_ok());
162        }
163
164        #[test]
165        fn body_with_multiple_content_types_fails() {
166            // TODO
167        }
168    }
169}