1// SPDX-FileCopyrightText: 2020-2021 HH Partners
2//
3// SPDX-License-Identifier: MIT
4
5use serde::{Deserialize, Serialize};
6use spdx_expression::SpdxExpression;
7
8/// <https://spdx.github.io/spdx-spec/5-snippet-information/>
9#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
10pub struct Snippet {
11 /// <https://spdx.github.io/spdx-spec/5-snippet-information/#51-snippet-spdx-identifier>
12 #[serde(rename = "SPDXID")]
13 pub snippet_spdx_identifier: String,
14
15 /// <https://spdx.github.io/spdx-spec/5-snippet-information/#52-snippet-from-file-spdx-identifier>
16 #[serde(rename = "snippetFromFile")]
17 pub snippet_from_file_spdx_identifier: String,
18
19 /// <https://spdx.github.io/spdx-spec/5-snippet-information/#53-snippet-byte-range>
20 pub ranges: Vec<Range>,
21
22 /// <https://spdx.github.io/spdx-spec/5-snippet-information/#55-snippet-concluded-license>
23 #[serde(
24 rename = "licenseConcluded",
25 skip_serializing_if = "Option::is_none",
26 default
27 )]
28 pub snippet_concluded_license: Option<SpdxExpression>,
29
30 /// <https://spdx.github.io/spdx-spec/5-snippet-information/#56-license-information-in-snippet>
31 #[serde(
32 rename = "licenseInfoInSnippets",
33 skip_serializing_if = "Vec::is_empty",
34 default
35 )]
36 pub license_information_in_snippet: Vec<String>,
37
38 /// <https://spdx.github.io/spdx-spec/5-snippet-information/#57-snippet-comments-on-license>
39 #[serde(
40 rename = "licenseComments",
41 skip_serializing_if = "Option::is_none",
42 default
43 )]
44 pub snippet_comments_on_license: Option<String>,
45
46 /// <https://spdx.github.io/spdx-spec/5-snippet-information/#58-snippet-copyright-text>
47 #[serde(
48 rename = "copyrightText",
49 skip_serializing_if = "Option::is_none",
50 default
51 )]
52 pub snippet_copyright_text: Option<String>,
53
54 /// <https://spdx.github.io/spdx-spec/5-snippet-information/#59-snippet-comment>
55 #[serde(rename = "comment", skip_serializing_if = "Option::is_none", default)]
56 pub snippet_comment: Option<String>,
57
58 /// <https://spdx.github.io/spdx-spec/5-snippet-information/#510-snippet-name>
59 #[serde(rename = "name", skip_serializing_if = "Option::is_none", default)]
60 pub snippet_name: Option<String>,
61
62 /// <https://spdx.github.io/spdx-spec/5-snippet-information/#511-snippet-attribution-text>
63 #[serde(
64 rename = "attributionText",
65 skip_serializing_if = "Option::is_none",
66 default
67 )]
68 pub snippet_attribution_text: Option<String>,
69}
70
71/// <https://spdx.github.io/spdx-spec/5-snippet-information/#53-snippet-byte-range>
72#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
73#[serde(rename_all = "camelCase")]
74pub struct Range {
75 pub start_pointer: Pointer,
76 pub end_pointer: Pointer,
77}
78
79impl Range {
80 pub fn new(start_pointer: Pointer, end_pointer: Pointer) -> Self {
81 Self {
82 start_pointer,
83 end_pointer,
84 }
85 }
86}
87
88#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
89#[serde(untagged)]
90pub enum Pointer {
91 Byte {
92 reference: Option<String>,
93 offset: i32,
94 },
95 Line {
96 reference: Option<String>,
97 #[serde(rename = "lineNumber")]
98 line_number: i32,
99 },
100}
101
102impl Pointer {
103 /// Create a new [`Pointer::Byte`].
104 pub fn new_byte(reference: Option<String>, offset: i32) -> Self {
105 Self::Byte { reference, offset }
106 }
107
108 /// Create a new [`Pointer::Line`].
109 pub fn new_line(reference: Option<String>, line_number: i32) -> Self {
110 Self::Line {
111 reference,
112 line_number,
113 }
114 }
115}
116
117#[cfg(test)]
118mod test {
119 use std::fs::read_to_string;
120
121 use crate::models::SPDX;
122
123 use super::*;
124
125 #[test]
126 fn snippet_spdx_identifier() {
127 let spdx: SPDX = serde_json::from_str(
128 &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
129 )
130 .unwrap();
131 assert_eq!(
132 spdx.snippet_information[0].snippet_spdx_identifier,
133 "SPDXRef-Snippet".to_string()
134 );
135 }
136 #[test]
137 fn snippet_from_file_spdx_identifier() {
138 let spdx: SPDX = serde_json::from_str(
139 &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
140 )
141 .unwrap();
142 assert_eq!(
143 spdx.snippet_information[0].snippet_from_file_spdx_identifier,
144 "SPDXRef-DoapSource".to_string()
145 );
146 }
147 #[test]
148 fn ranges() {
149 let spdx: SPDX = serde_json::from_str(
150 &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
151 )
152 .unwrap();
153 assert_eq!(
154 spdx.snippet_information[0].ranges,
155 vec![
156 Range {
157 end_pointer: Pointer::Line {
158 line_number: 23,
159 reference: Some("SPDXRef-DoapSource".to_string()),
160 },
161 start_pointer: Pointer::Line {
162 line_number: 5,
163 reference: Some("SPDXRef-DoapSource".to_string()),
164 },
165 },
166 Range {
167 end_pointer: Pointer::Byte {
168 offset: 420,
169 reference: Some("SPDXRef-DoapSource".to_string()),
170 },
171 start_pointer: Pointer::Byte {
172 offset: 310,
173 reference: Some("SPDXRef-DoapSource".to_string()),
174 },
175 },
176 ]
177 );
178 }
179 #[test]
180 fn snippet_concluded_license() {
181 let spdx: SPDX = serde_json::from_str(
182 &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
183 )
184 .unwrap();
185 assert_eq!(
186 spdx.snippet_information[0]
187 .snippet_concluded_license
188 .as_ref()
189 .unwrap()
190 .clone(),
191 SpdxExpression::parse("GPL-2.0-only").unwrap()
192 );
193 }
194 #[test]
195 fn license_information_in_snippet() {
196 let spdx: SPDX = serde_json::from_str(
197 &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
198 )
199 .unwrap();
200 assert_eq!(
201 spdx.snippet_information[0].license_information_in_snippet,
202 vec!["GPL-2.0-only".to_string()]
203 );
204 }
205 #[test]
206 fn snippet_comments_on_license() {
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.snippet_information[0].snippet_comments_on_license,
213 Some("The concluded license was taken from package xyz, from which the snippet was copied into the current file. The concluded license information was found in the COPYING.txt file in package xyz.".to_string())
214 );
215 }
216 #[test]
217 fn snippet_copyright_text() {
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.snippet_information[0]
224 .snippet_copyright_text
225 .as_ref()
226 .unwrap()
227 .clone(),
228 "Copyright 2008-2010 John Smith".to_string()
229 );
230 }
231 #[test]
232 fn snippet_comment() {
233 let spdx: SPDX = serde_json::from_str(
234 &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
235 )
236 .unwrap();
237 assert_eq!(
238 spdx.snippet_information[0].snippet_comment,
239 Some("This snippet was identified as significant and highlighted in this Apache-2.0 file, when a commercial scanner identified it as being derived from file foo.c in package xyz which is licensed under GPL-2.0.".to_string())
240 );
241 }
242 #[test]
243 fn snippet_name() {
244 let spdx: SPDX = serde_json::from_str(
245 &read_to_string("tests/data/SPDXJSONExample-v2.2.spdx.json").unwrap(),
246 )
247 .unwrap();
248 assert_eq!(
249 spdx.snippet_information[0].snippet_name,
250 Some("from linux kernel".to_string())
251 );
252 }
253}
254