spdx_rs/models/
file_information.rs

1// SPDX-FileCopyrightText: 2020-2021 HH Partners
2//
3// SPDX-License-Identifier: MIT
4
5use serde::{Deserialize, Serialize};
6use spdx_expression::SpdxExpression;
7
8use super::{Algorithm, Checksum};
9
10/// ## File Information
11///
12/// SPDX's [File Information](https://spdx.github.io/spdx-spec/4-file-information/)
13#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
14#[serde(rename_all = "camelCase")]
15pub struct FileInformation {
16    /// <https://spdx.github.io/spdx-spec/4-file-information/#41-file-name>
17    pub file_name: String,
18
19    /// <https://spdx.github.io/spdx-spec/4-file-information/#42-file-spdx-identifier>
20    #[serde(rename = "SPDXID")]
21    pub file_spdx_identifier: String,
22
23    /// <https://spdx.github.io/spdx-spec/4-file-information/#43-file-type>
24    #[serde(rename = "fileTypes", skip_serializing_if = "Vec::is_empty", default)]
25    pub file_type: Vec<FileType>,
26
27    /// <https://spdx.github.io/spdx-spec/4-file-information/#44-file-checksum>
28    #[serde(rename = "checksums")]
29    pub file_checksum: Vec<Checksum>,
30
31    /// <https://spdx.github.io/spdx-spec/4-file-information/#45-concluded-license>
32    #[serde(
33        rename = "licenseConcluded",
34        skip_serializing_if = "Option::is_none",
35        default
36    )]
37    pub concluded_license: Option<SpdxExpression>,
38
39    /// <https://spdx.github.io/spdx-spec/4-file-information/#46-license-information-in-file>
40    #[serde(
41        rename = "licenseInfoInFiles",
42        skip_serializing_if = "Vec::is_empty",
43        default
44    )]
45    pub license_information_in_file: Vec<SpdxExpression>,
46
47    /// <https://spdx.github.io/spdx-spec/4-file-information/#47-comments-on-license>
48    #[serde(
49        rename = "licenseComments",
50        skip_serializing_if = "Option::is_none",
51        default
52    )]
53    pub comments_on_license: Option<String>,
54
55    /// <https://spdx.github.io/spdx-spec/4-file-information/#48-copyright-text>
56    #[serde(
57        rename = "copyrightText",
58        skip_serializing_if = "Option::is_none",
59        default
60    )]
61    pub copyright_text: Option<String>,
62
63    /// <https://spdx.github.io/spdx-spec/4-file-information/#412-file-comment>
64    #[serde(rename = "comment", skip_serializing_if = "Option::is_none", default)]
65    pub file_comment: Option<String>,
66
67    /// <https://spdx.github.io/spdx-spec/4-file-information/#413-file-notice>
68    #[serde(
69        rename = "noticeText",
70        skip_serializing_if = "Option::is_none",
71        default
72    )]
73    pub file_notice: Option<String>,
74
75    /// <https://spdx.github.io/spdx-spec/4-file-information/#414-file-contributor>
76    #[serde(
77        rename = "fileContributors",
78        skip_serializing_if = "Vec::is_empty",
79        default
80    )]
81    pub file_contributor: Vec<String>,
82
83    /// <https://spdx.github.io/spdx-spec/4-file-information/#415-file-attribution-text>
84    #[serde(skip_serializing_if = "Option::is_none", default)]
85    pub file_attribution_text: Option<Vec<String>>,
86    // TODO: Snippet Information.
87}
88
89impl Default for FileInformation {
90    fn default() -> Self {
91        Self {
92            file_name: "NOASSERTION".to_string(),
93            file_spdx_identifier: "NOASSERTION".to_string(),
94            file_type: Vec::new(),
95            file_checksum: Vec::new(),
96            concluded_license: None,
97            license_information_in_file: Vec::new(),
98            comments_on_license: None,
99            copyright_text: None,
100            file_comment: None,
101            file_notice: None,
102            file_contributor: Vec::new(),
103            file_attribution_text: None,
104        }
105    }
106}
107
108impl FileInformation {
109    /// Create new file.
110    pub fn new(name: &str, id: &mut i32) -> Self {
111        *id += 1;
112        Self {
113            file_name: name.to_string(),
114            file_spdx_identifier: format!("SPDXRef-{id}"),
115            ..Self::default()
116        }
117    }
118
119    /// Check if hash equals.
120    pub fn equal_by_hash(&self, algorithm: Algorithm, value: &str) -> bool {
121        let checksum = self
122            .file_checksum
123            .iter()
124            .find(|&checksum| checksum.algorithm == algorithm);
125
126        checksum.map_or(false, |checksum| {
127            checksum.value.to_ascii_lowercase() == value.to_ascii_lowercase()
128        })
129    }
130
131    /// Get checksum
132    pub fn checksum(&self, algorithm: Algorithm) -> Option<&str> {
133        let checksum = self
134            .file_checksum
135            .iter()
136            .find(|&checksum| checksum.algorithm == algorithm);
137
138        checksum.map(|checksum| checksum.value.as_str())
139    }
140}
141
142/// <https://spdx.github.io/spdx-spec/4-file-information/#43-file-type>
143#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Clone, Copy)]
144#[serde(rename_all = "UPPERCASE")]
145pub enum FileType {
146    Source,
147    Binary,
148    Archive,
149    Application,
150    Audio,
151    Image,
152    Text,
153    Video,
154    Documentation,
155    SPDX,
156    Other,
157}
158
159#[cfg(test)]
160mod test {
161    use std::fs::read_to_string;
162
163    use super::*;
164    use crate::models::{Checksum, FileType, SPDX};
165
166    #[test]
167    fn checksum_equality() {
168        let mut id = 1;
169        let mut file_sha256 = FileInformation::new("sha256", &mut id);
170        file_sha256
171            .file_checksum
172            .push(Checksum::new(Algorithm::SHA256, "test"));
173
174        assert!(file_sha256.equal_by_hash(Algorithm::SHA256, "test"));
175        assert!(!file_sha256.equal_by_hash(Algorithm::SHA256, "no_test"));
176
177        let mut file_md5 = FileInformation::new("md5", &mut id);
178        file_md5
179            .file_checksum
180            .push(Checksum::new(Algorithm::MD5, "test"));
181        assert!(file_md5.equal_by_hash(Algorithm::MD5, "test"));
182        assert!(!file_md5.equal_by_hash(Algorithm::MD5, "no_test"));
183        assert!(!file_md5.equal_by_hash(Algorithm::SHA1, "test"));
184    }
185
186    #[test]
187    fn get_checksum() {
188        let mut id = 1;
189        let mut file_sha256 = FileInformation::new("sha256", &mut id);
190        file_sha256
191            .file_checksum
192            .push(Checksum::new(Algorithm::SHA256, "test"));
193
194        assert_eq!(file_sha256.checksum(Algorithm::SHA256), Some("test"));
195        assert_eq!(file_sha256.checksum(Algorithm::MD2), None);
196
197        let mut file_md5 = FileInformation::new("md5", &mut id);
198        file_md5
199            .file_checksum
200            .push(Checksum::new(Algorithm::MD5, "test"));
201
202        assert_eq!(file_md5.checksum(Algorithm::MD5), Some("test"));
203    }
204
205    #[test]
206    fn file_name() {
207        let spdx: SPDX = serde_json::from_str(
208            &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
209        )
210        .unwrap();
211        assert_eq!(
212            spdx.file_information[0].file_name,
213            "./src/org/spdx/parser/DOAPProject.java"
214        );
215    }
216    #[test]
217    fn file_spdx_identifier() {
218        let spdx: SPDX = serde_json::from_str(
219            &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
220        )
221        .unwrap();
222        assert_eq!(
223            spdx.file_information[0].file_spdx_identifier,
224            "SPDXRef-DoapSource"
225        );
226    }
227    #[test]
228    fn file_type() {
229        let spdx: SPDX = serde_json::from_str(
230            &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
231        )
232        .unwrap();
233        assert_eq!(spdx.file_information[0].file_type, vec![FileType::Source]);
234    }
235    #[test]
236    fn file_checksum() {
237        let spdx: SPDX = serde_json::from_str(
238            &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
239        )
240        .unwrap();
241        assert_eq!(
242            spdx.file_information[0].file_checksum,
243            vec![Checksum {
244                algorithm: Algorithm::SHA1,
245                value: "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12".to_string()
246            }]
247        );
248    }
249    #[test]
250    fn concluded_license() {
251        let spdx: SPDX = serde_json::from_str(
252            &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
253        )
254        .unwrap();
255        assert_eq!(
256            spdx.file_information[0].concluded_license,
257            Some(SpdxExpression::parse("Apache-2.0").unwrap())
258        );
259    }
260    #[test]
261    fn license_information_in_file() {
262        let spdx: SPDX = serde_json::from_str(
263            &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
264        )
265        .unwrap();
266        assert_eq!(
267            spdx.file_information[0].license_information_in_file,
268            vec![SpdxExpression::parse("Apache-2.0").unwrap()]
269        );
270    }
271    #[test]
272    fn comments_on_license() {
273        let spdx: SPDX = serde_json::from_str(
274            &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
275        )
276        .unwrap();
277        assert_eq!(
278            spdx.file_information[2].comments_on_license,
279            Some("This license is used by Jena".to_string())
280        );
281    }
282    #[test]
283    fn copyright_text() {
284        let spdx: SPDX = serde_json::from_str(
285            &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
286        )
287        .unwrap();
288        assert_eq!(
289            spdx.file_information[0]
290                .copyright_text
291                .as_ref()
292                .unwrap()
293                .clone(),
294            "Copyright 2010, 2011 Source Auditor Inc.".to_string()
295        );
296    }
297    #[test]
298    fn file_comment() {
299        let spdx: SPDX = serde_json::from_str(
300            &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
301        )
302        .unwrap();
303        assert_eq!(
304            spdx.file_information[1].file_comment,
305            Some("This file is used by Jena".to_string())
306        );
307    }
308    #[test]
309    fn file_notice() {
310        let spdx: SPDX = serde_json::from_str(
311            &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
312        )
313        .unwrap();
314        assert_eq!(
315                    spdx.file_information[1].file_notice,
316                    Some("Apache Commons Lang\nCopyright 2001-2011 The Apache Software Foundation\n\nThis product includes software developed by\nThe Apache Software Foundation (http://www.apache.org/).\n\nThis product includes software from the Spring Framework,\nunder the Apache License 2.0 (see: StringUtils.containsWhitespace())".to_string())
317                );
318    }
319    #[test]
320    fn file_contributor() {
321        let spdx: SPDX = serde_json::from_str(
322            &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
323        )
324        .unwrap();
325        assert_eq!(
326            spdx.file_information[1].file_contributor,
327            vec!["Apache Software Foundation".to_string()]
328        );
329    }
330}