1// SPDX-FileCopyrightText: 2021 HH Partners
2//
3// SPDX-License-Identifier: MIT
4
5use std::{num::ParseIntError, str::FromStr};
6
7use nom::{
8 branch::alt,
9 bytes::complete::{tag, take_until, take_while},
10 character::complete::{alphanumeric0, char, digit1, multispace0, not_line_ending},
11 combinator::{map, map_res, opt},
12 error::{ParseError, VerboseError},
13 multi::many0,
14 sequence::{delimited, preceded, separated_pair, tuple},
15 AsChar, IResult,
16};
17
18use crate::models::{
19 Algorithm, AnnotationType, Checksum, ExternalDocumentReference, ExternalPackageReference,
20 ExternalPackageReferenceCategory, FileType, PackageVerificationCode, Relationship,
21 RelationshipType,
22};
23
24#[derive(Debug, Clone, PartialEq)]
25#[allow(clippy::upper_case_acronyms)]
26pub(super) enum Atom {
27 // Document Creation Information
28 SpdxVersion(String),
29 DataLicense(String),
30 SPDXID(String),
31 DocumentName(String),
32 DocumentNamespace(String),
33 ExternalDocumentRef(ExternalDocumentReference),
34 LicenseListVersion(String),
35 Creator(String),
36 Created(String),
37 CreatorComment(String),
38 DocumentComment(String),
39
40 // Package Information
41 PackageName(String),
42 PackageVersion(String),
43 PackageFileName(String),
44 PackageSupplier(String),
45 PackageOriginator(String),
46 PackageDownloadLocation(String),
47 FilesAnalyzed(String),
48 PackageVerificationCode(PackageVerificationCode),
49 PackageChecksum(Checksum),
50 PackageHomePage(String),
51 PackageSourceInfo(String),
52 PackageLicenseConcluded(String),
53 PackageLicenseInfoFromFiles(String),
54 PackageLicenseDeclared(String),
55 PackageLicenseComments(String),
56 PackageCopyrightText(String),
57 PackageSummary(String),
58 PackageDescription(String),
59 PackageComment(String),
60 ExternalRef(ExternalPackageReference),
61 ExternalRefComment(String),
62 PackageAttributionText(String),
63 PrimaryPackagePurpose(String),
64 BuiltDate(String),
65 ReleaseDate(String),
66 ValidUntilDate(String),
67
68 // File Information
69 FileName(String),
70 FileType(FileType),
71 FileChecksum(Checksum),
72 LicenseConcluded(String),
73 LicenseInfoInFile(String),
74 LicenseComments(String),
75 FileCopyrightText(String),
76 FileComment(String),
77 FileNotice(String),
78 FileContributor(String),
79 FileAttributionText(String),
80
81 // Snippet Information
82 SnippetSPDXID(String),
83 SnippetFromFileSPDXID(String),
84 SnippetByteRange((i32, i32)),
85 SnippetLineRange((i32, i32)),
86 SnippetLicenseConcluded(String),
87 LicenseInfoInSnippet(String),
88 SnippetLicenseComments(String),
89 SnippetCopyrightText(String),
90 SnippetComment(String),
91 SnippetName(String),
92 SnippetAttributionText(String),
93
94 // Other Licensing Information Detected
95 LicenseID(String),
96 ExtractedText(String),
97 LicenseName(String),
98 LicenseCrossReference(String),
99 LicenseComment(String),
100
101 // Relationship
102 Relationship(Relationship),
103 RelationshipComment(String),
104
105 // Annotation
106 Annotator(String),
107 AnnotationDate(String),
108 AnnotationType(AnnotationType),
109 SPDXREF(String),
110 AnnotationComment(String),
111
112 /// Comment in the document. Not part of the final SPDX.
113 TVComment(String),
114}
115
116pub(super) fn atoms(i: &str) -> IResult<&str, Vec<Atom>, VerboseError<&str>> {
117 many0(alt((ws(inner:tv_comment), ws(inner:tag_value_to_atom))))(i)
118}
119
120fn tag_value_to_atom(i: &str) -> IResult<&str, Atom, VerboseError<&str>> {
121 let (i, key_value) = tag_value(i)?;
122 match key_value.0 {
123 // Document Creation Information
124 "SPDXVersion" => Ok((i, Atom::SpdxVersion(key_value.1.to_string()))),
125 "DataLicense" => Ok((i, Atom::DataLicense(key_value.1.to_string()))),
126 "SPDXID" => Ok((i, Atom::SPDXID(key_value.1.to_string()))),
127 "DocumentName" => Ok((i, Atom::DocumentName(key_value.1.to_string()))),
128 "DocumentNamespace" => Ok((i, Atom::DocumentNamespace(key_value.1.to_string()))),
129 "ExternalDocumentRef" => {
130 let (_, value) = external_document_reference(key_value.1)?;
131 Ok((i, Atom::ExternalDocumentRef(value)))
132 }
133 "LicenseListVersion" => Ok((i, Atom::LicenseListVersion(key_value.1.to_string()))),
134 "Creator" => Ok((i, Atom::Creator(key_value.1.to_string()))),
135 "Created" => Ok((i, Atom::Created(key_value.1.to_string()))),
136 "CreatorComment" => Ok((i, Atom::CreatorComment(key_value.1.to_string()))),
137 "DocumentComment" => Ok((i, Atom::DocumentComment(key_value.1.to_string()))),
138
139 // Package Information
140 "PackageName" => Ok((i, Atom::PackageName(key_value.1.to_string()))),
141 "PackageVersion" => Ok((i, Atom::PackageVersion(key_value.1.to_string()))),
142 "PackageFileName" => Ok((i, Atom::PackageFileName(key_value.1.to_string()))),
143 "PackageSupplier" => Ok((i, Atom::PackageSupplier(key_value.1.to_string()))),
144 "PackageOriginator" => Ok((i, Atom::PackageOriginator(key_value.1.to_string()))),
145 "PackageDownloadLocation" => {
146 Ok((i, Atom::PackageDownloadLocation(key_value.1.to_string())))
147 }
148 "FilesAnalyzed" => Ok((i, Atom::FilesAnalyzed(key_value.1.to_string()))),
149 "PackageVerificationCode" => {
150 let (_, value) = package_verification_code(key_value.1)?;
151 Ok((i, Atom::PackageVerificationCode(value)))
152 }
153 "PackageChecksum" => Ok((i, Atom::PackageChecksum(checksum(key_value.1)?.1))),
154 "PackageHomePage" => Ok((i, Atom::PackageHomePage(key_value.1.to_string()))),
155 "PackageSourceInfo" => Ok((i, Atom::PackageSourceInfo(key_value.1.to_string()))),
156 "PackageLicenseConcluded" => {
157 Ok((i, Atom::PackageLicenseConcluded(key_value.1.to_string())))
158 }
159 "PackageLicenseInfoFromFiles" => Ok((
160 i,
161 Atom::PackageLicenseInfoFromFiles(key_value.1.to_string()),
162 )),
163 "PackageLicenseDeclared" => Ok((i, Atom::PackageLicenseDeclared(key_value.1.to_string()))),
164 "PackageLicenseComments" => Ok((i, Atom::PackageLicenseComments(key_value.1.to_string()))),
165 "PackageCopyrightText" => Ok((i, Atom::PackageCopyrightText(key_value.1.to_string()))),
166 "PackageSummary" => Ok((i, Atom::PackageSummary(key_value.1.to_string()))),
167 "PackageDescription" => Ok((i, Atom::PackageDescription(key_value.1.to_string()))),
168 "PackageComment" => Ok((i, Atom::PackageComment(key_value.1.to_string()))),
169 "ExternalRef" => Ok((
170 i,
171 Atom::ExternalRef(external_package_reference(key_value.1)?.1),
172 )),
173 "ExternalRefComment" => Ok((i, Atom::ExternalRefComment(key_value.1.to_string()))),
174 "PackageAttributionText" => Ok((i, Atom::PackageAttributionText(key_value.1.to_string()))),
175 "PrimaryPackagePurpose" => Ok((i, Atom::PrimaryPackagePurpose(key_value.1.to_string()))),
176 "BuiltDate" => Ok((i, Atom::BuiltDate(key_value.1.to_string()))),
177 "ReleaseDate" => Ok((i, Atom::ReleaseDate(key_value.1.to_string()))),
178 "ValidUntilDate" => Ok((i, Atom::ValidUntilDate(key_value.1.to_string()))),
179
180 // File Information
181 "FileName" => Ok((i, Atom::FileName(key_value.1.to_string()))),
182 "FileType" => Ok((i, Atom::FileType(file_type(key_value.1)?.1))),
183 "FileChecksum" => Ok((i, Atom::FileChecksum(checksum(key_value.1)?.1))),
184 "LicenseConcluded" => Ok((i, Atom::LicenseConcluded(key_value.1.to_string()))),
185 "LicenseInfoInFile" => Ok((i, Atom::LicenseInfoInFile(key_value.1.to_string()))),
186 "LicenseComments" => Ok((i, Atom::LicenseComments(key_value.1.to_string()))),
187 "FileCopyrightText" => Ok((i, Atom::FileCopyrightText(key_value.1.to_string()))),
188 "FileComment" => Ok((i, Atom::FileComment(key_value.1.to_string()))),
189 "FileNotice" => Ok((i, Atom::FileNotice(key_value.1.to_string()))),
190 "FileContributor" => Ok((i, Atom::FileContributor(key_value.1.to_string()))),
191 "FileAttributionText" => Ok((i, Atom::FileAttributionText(key_value.1.to_string()))),
192
193 // Snippet Information
194 "SnippetSPDXID" => Ok((i, Atom::SnippetSPDXID(key_value.1.to_string()))),
195 "SnippetFromFileSPDXID" => Ok((i, Atom::SnippetFromFileSPDXID(key_value.1.to_string()))),
196 "SnippetByteRange" => Ok((i, Atom::SnippetByteRange(range(key_value.1)?.1))),
197 "SnippetLineRange" => Ok((i, Atom::SnippetLineRange(range(key_value.1)?.1))),
198 "SnippetLicenseConcluded" => {
199 Ok((i, Atom::SnippetLicenseConcluded(key_value.1.to_string())))
200 }
201 "LicenseInfoInSnippet" => Ok((i, Atom::LicenseInfoInSnippet(key_value.1.to_string()))),
202 "SnippetLicenseComments" => Ok((i, Atom::SnippetLicenseComments(key_value.1.to_string()))),
203 "SnippetCopyrightText" => Ok((i, Atom::SnippetCopyrightText(key_value.1.to_string()))),
204 "SnippetComment" => Ok((i, Atom::SnippetComment(key_value.1.to_string()))),
205 "SnippetName" => Ok((i, Atom::SnippetName(key_value.1.to_string()))),
206 "SnippetAttributionText" => Ok((i, Atom::SnippetAttributionText(key_value.1.to_string()))),
207
208 // Other Licensing Information Detected
209 "LicenseID" => Ok((i, Atom::LicenseID(key_value.1.to_string()))),
210 "ExtractedText" => Ok((i, Atom::ExtractedText(key_value.1.to_string()))),
211 "LicenseName" => Ok((i, Atom::LicenseName(key_value.1.to_string()))),
212 "LicenseCrossReference" => Ok((i, Atom::LicenseCrossReference(key_value.1.to_string()))),
213 "LicenseComment" => Ok((i, Atom::LicenseComment(key_value.1.to_string()))),
214
215 // Relationship
216 "Relationship" => Ok((i, Atom::Relationship(relationship(key_value.1)?.1))),
217 "RelationshipComment" => Ok((i, Atom::RelationshipComment(key_value.1.to_string()))),
218
219 // Annotation
220 "Annotator" => Ok((i, Atom::Annotator(key_value.1.to_string()))),
221 "AnnotationDate" => Ok((i, Atom::AnnotationDate(key_value.1.to_string()))),
222 "AnnotationType" => Ok((i, Atom::AnnotationType(annotation_type(key_value.1)?.1))),
223 "SPDXREF" => Ok((i, Atom::SPDXREF(key_value.1.to_string()))),
224 "AnnotationComment" => Ok((i, Atom::AnnotationComment(key_value.1.to_string()))),
225 v => {
226 dbg!(v);
227 unimplemented!()
228 }
229 }
230}
231
232fn external_document_reference(
233 i: &str,
234) -> IResult<&str, ExternalDocumentReference, VerboseError<&str>> {
235 map(
236 parser:tuple((
237 document_ref,
238 ws(take_while(|c: char| !c.is_whitespace())),
239 ws(checksum),
240 )),
241 |(id_string: &str, spdx_document_uri: &str, checksum: Checksum)| {
242 ExternalDocumentReference::new(
243 id_string:id_string.to_string(),
244 spdx_document_uri:spdx_document_uri.to_string(),
245 checksum,
246 )
247 },
248 )(i)
249}
250
251fn annotation_type(i: &str) -> IResult<&str, AnnotationType, VerboseError<&str>> {
252 match ws(inner:not_line_ending)(i) {
253 Ok((i: &str, value: &str)) => match value {
254 "REVIEW" => Ok((i, AnnotationType::Review)),
255 "OTHER" => Ok((i, AnnotationType::Other)),
256 // Proper error
257 _ => todo!(),
258 },
259 Err(err: Err>) => Err(err),
260 }
261}
262
263fn file_type(i: &str) -> IResult<&str, FileType, VerboseError<&str>> {
264 match ws(inner:not_line_ending)(i) {
265 Ok((i: &str, value: &str)) => match value {
266 "SOURCE" => Ok((i, FileType::Source)),
267 "BINARY" => Ok((i, FileType::Binary)),
268 "ARCHIVE" => Ok((i, FileType::Archive)),
269 "APPLICATION" => Ok((i, FileType::Application)),
270 "AUDIO" => Ok((i, FileType::Audio)),
271 "IMAGE" => Ok((i, FileType::Image)),
272 "TEXT" => Ok((i, FileType::Text)),
273 "VIDEO" => Ok((i, FileType::Video)),
274 "DOCUMENTATION" => Ok((i, FileType::Documentation)),
275 "SPDX" => Ok((i, FileType::SPDX)),
276 "OTHER" => Ok((i, FileType::Other)),
277 // Proper error
278 _ => todo!(),
279 },
280 Err(err: Err>) => Err(err),
281 }
282}
283
284fn document_ref(i: &str) -> IResult<&str, &str, VerboseError<&str>> {
285 preceded(first:tag("DocumentRef-"), second:ws(inner:idstring))(i)
286}
287
288fn relationship(i: &str) -> IResult<&str, Relationship, VerboseError<&str>> {
289 map(
290 tuple((
291 ws(take_while(|c: char| !c.is_whitespace())),
292 ws(take_while(|c: char| !c.is_whitespace())),
293 ws(not_line_ending),
294 )),
295 |(item1, relationship_type, item2)| {
296 let relationship_type = relationship_type.to_uppercase();
297 let relationship_type = match relationship_type.as_str() {
298 "DESCRIBES" => RelationshipType::Describes,
299 "DESCRIBED_BY" => RelationshipType::DescribedBy,
300 "CONTAINS" => RelationshipType::Contains,
301 "CONTAINED_BY" => RelationshipType::ContainedBy,
302 "DEPENDS_ON" => RelationshipType::DependsOn,
303 "DEPENDENCY_OF" => RelationshipType::DependencyOf,
304 "DEPENDENCY_MANIFEST_OF" => RelationshipType::DependencyManifestOf,
305 "BUILD_DEPENDENCY_OF" => RelationshipType::BuildDependencyOf,
306 "DEV_DEPENDENCY_OF" => RelationshipType::DevDependencyOf,
307 "OPTIONAL_DEPENDENCY_OF" => RelationshipType::OptionalDependencyOf,
308 "PROVIDED_DEPENDENCY_OF" => RelationshipType::ProvidedDependencyOf,
309 "TEST_DEPENDENCY_OF" => RelationshipType::TestDependencyOf,
310 "RUNTIME_DEPENDENCY_OF" => RelationshipType::RuntimeDependencyOf,
311 "EXAMPLE_OF" => RelationshipType::ExampleOf,
312 "GENERATES" => RelationshipType::Generates,
313 "GENERATED_FROM" => RelationshipType::GeneratedFrom,
314 "ANCESTOR_OF" => RelationshipType::AncestorOf,
315 "DESCENDANT_OF" => RelationshipType::DescendantOf,
316 "VARIANT_OF" => RelationshipType::VariantOf,
317 "DISTRIBUTION_ARTIFACT" => RelationshipType::DistributionArtifact,
318 "PATCH_FOR" => RelationshipType::PatchFor,
319 "PATCH_APPLIED" => RelationshipType::PatchApplied,
320 "COPY_OF" => RelationshipType::CopyOf,
321 "FILE_ADDED" => RelationshipType::FileAdded,
322 "FILE_DELETED" => RelationshipType::FileDeleted,
323 "FILE_MODIFIED" => RelationshipType::FileModified,
324 "EXPANDED_FROM_ARCHIVE" => RelationshipType::ExpandedFromArchive,
325 "DYNAMIC_LINK" => RelationshipType::DynamicLink,
326 "STATIC_LINK" => RelationshipType::StaticLink,
327 "DATA_FILE_OF" => RelationshipType::DataFileOf,
328 "TEST_CASE_OF" => RelationshipType::TestCaseOf,
329 "BUILD_TOOL_OF" => RelationshipType::BuildToolOf,
330 "DEV_TOOL_OF" => RelationshipType::DevToolOf,
331 "TEST_OF" => RelationshipType::TestOf,
332 "TEST_TOOL_OF" => RelationshipType::TestToolOf,
333 "DOCUMENTATION_OF" => RelationshipType::DocumentationOf,
334 "OPTIONAL_COMPONENT_OF" => RelationshipType::OptionalComponentOf,
335 "METAFILE_OF" => RelationshipType::MetafileOf,
336 "PACKAGE_OF" => RelationshipType::PackageOf,
337 "AMENDS" => RelationshipType::Amends,
338 "PREREQUISITE_FOR" => RelationshipType::PrerequisiteFor,
339 "HAS_PREREQUISITE" => RelationshipType::HasPrerequisite,
340 "SPECIFICATION_FOR" => RelationshipType::SpecificationFor,
341 "REQUIREMENT_DESCRIPTION_FOR" => RelationshipType::RequirementDescriptionFor,
342 "OTHER" => RelationshipType::Other,
343 // TODO: Proper error.
344 _ => {
345 dbg!(relationship_type);
346 todo!()
347 }
348 };
349 Relationship::new(item1, item2, relationship_type, None)
350 },
351 )(i)
352}
353
354fn external_package_reference(
355 i: &str,
356) -> IResult<&str, ExternalPackageReference, VerboseError<&str>> {
357 map(
358 parser:tuple((
359 ws(take_while(|c: char| !c.is_whitespace())),
360 ws(take_while(|c: char| !c.is_whitespace())),
361 ws(not_line_ending),
362 )),
363 |(category: &str, ref_type: &str, locator: &str)| {
364 let category: ExternalPackageReferenceCategory = match category {
365 "SECURITY" => ExternalPackageReferenceCategory::Security,
366 "PACKAGE-MANAGER" => ExternalPackageReferenceCategory::PackageManager,
367 "PERSISTENT-ID" => ExternalPackageReferenceCategory::PersistentID,
368 "OTHER" => ExternalPackageReferenceCategory::Other,
369 // TODO: Proper error handling
370 _ => todo!(),
371 };
372 ExternalPackageReference::new(reference_category:category, reference_type:ref_type.to_string(), reference_locator:locator.to_string(), reference_comment:None)
373 },
374 )(i)
375}
376
377fn package_verification_code(
378 i: &str,
379) -> IResult<&str, PackageVerificationCode, VerboseError<&str>> {
380 map(
381 parser:alt((
382 separated_pair(
383 ws(take_until("(excludes:")),
384 ws(tag("(excludes:")),
385 opt(take_until(")")),
386 ),
387 map(ws(not_line_ending), |v| (v, None)),
388 )),
389 |(value: &str, exclude: Option<&str>)| {
390 #[allow(clippy::option_if_let_else)]
391 let excludes: Vec = if let Some(exclude: &str) = exclude {
392 vec![exclude.to_string()]
393 } else {
394 Vec::new()
395 };
396 PackageVerificationCode::new(value:value.to_string(), excludes)
397 },
398 )(i)
399}
400
401fn range(i: &str) -> IResult<&str, (i32, i32), VerboseError<&str>> {
402 map_res::<_, _, _, _, ParseIntError, _, _>(
403 parser:separated_pair(digit1, char(':'), digit1),
404 |(left: &str, right: &str)| Ok((i32::from_str(left)?, i32::from_str(right)?)),
405 )(i)
406}
407
408fn idstring(i: &str) -> IResult<&str, &str, VerboseError<&str>> {
409 take_while(|c: char| c.is_alphanum() || c == '.' || c == '-' || c == '+')(i)
410}
411
412fn checksum(i: &str) -> IResult<&str, Checksum, VerboseError<&str>> {
413 map(
414 separated_pair(ws(take_until(":")), char(':'), ws(not_line_ending)),
415 |(algorithm, value)| {
416 let checksum_algorithm = match algorithm {
417 "SHA1" => Algorithm::SHA1,
418 "SHA224" => Algorithm::SHA224,
419 "SHA256" => Algorithm::SHA256,
420 "SHA384" => Algorithm::SHA384,
421 "SHA512" => Algorithm::SHA512,
422 "MD2" => Algorithm::MD2,
423 "MD4" => Algorithm::MD4,
424 "MD5" => Algorithm::MD5,
425 "MD6" => Algorithm::MD6,
426 "SHA3-256" => Algorithm::SHA3256,
427 "SHA3-384" => Algorithm::SHA3384,
428 "SHA3-512" => Algorithm::SHA3512,
429 "BLAKE2b-256" => Algorithm::BLAKE2B256,
430 "BLAKE2b-384" => Algorithm::BLAKE2B384,
431 "BLAKE2b-512" => Algorithm::BLAKE2B512,
432 "BLAKE3" => Algorithm::BLAKE3,
433 "ADLER32" => Algorithm::ADLER32,
434 // TODO: Use proper error.
435 _ => todo!(),
436 };
437 Checksum::new(checksum_algorithm, value)
438 },
439 )(i)
440}
441
442fn tv_comment(i: &str) -> IResult<&str, Atom, VerboseError<&str>> {
443 map(parser:preceded(ws(tag("#")), ws(not_line_ending)), |v: &str| {
444 Atom::TVComment(v.to_string())
445 })(i)
446}
447
448fn tag_value(i: &str) -> IResult<&str, (&str, &str), VerboseError<&str>> {
449 separated_pair(
450 first:ws(alphanumeric0),
451 sep:tag(":"),
452 second:alt((ws(inner:multiline_text), ws(inner:not_line_ending))),
453 )(i)
454}
455
456fn multiline_text(i: &str) -> IResult<&str, &str, VerboseError<&str>> {
457 delimited(first:tag("<text>"), second:take_until("</text>"), third:tag("</text>"))(i)
458}
459
460/// A combinator that takes a parser `inner` and produces a parser that also consumes both leading and
461/// trailing whitespace, returning the output of `inner`.
462fn ws<'a, F, O, E: ParseError<&'a str>>(inner: F) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
463where
464 F: Fn(&'a str) -> IResult<&'a str, O, E> + 'a,
465{
466 delimited(first:multispace0, second:inner, third:multispace0)
467}
468
469#[cfg(test)]
470mod tests {
471 use std::fs::read_to_string;
472
473 use crate::{
474 models::{Algorithm, AnnotationType, ExternalPackageReferenceCategory, Relationship},
475 parsers::tag_value::{
476 annotation_type, checksum, document_ref, external_document_reference,
477 external_package_reference, package_verification_code, range, relationship,
478 },
479 };
480
481 use super::{atoms, tag_value, tag_value_to_atom, Atom};
482
483 #[test]
484 fn version_can_be_parsed() {
485 let (_, value) = tag_value_to_atom("SPDXVersion: SPDX-1.2").unwrap();
486 assert_eq!(value, Atom::SpdxVersion("SPDX-1.2".to_string()));
487 }
488
489 #[test]
490 fn range_can_be_parsed() {
491 let (_, value) = range("310:420").unwrap();
492 assert_eq!(value, (310, 420));
493 }
494
495 #[test]
496 fn annotation_type_can_be_parsed() {
497 let (_, value) = annotation_type("REVIEW").unwrap();
498 assert_eq!(value, AnnotationType::Review);
499 let (_, value) = annotation_type("OTHER").unwrap();
500 assert_eq!(value, AnnotationType::Other);
501 }
502
503 #[test]
504 fn relationship_can_be_parsed() {
505 let (_, value) = relationship("SPDXRef-JenaLib CONTAINS SPDXRef-Package").unwrap();
506 let expected = Relationship::new(
507 "SPDXRef-JenaLib",
508 "SPDXRef-Package",
509 crate::models::RelationshipType::Contains,
510 None,
511 );
512 assert_eq!(value, expected);
513 }
514
515 #[test]
516 fn data_license_can_be_parsed() {
517 let (_, value) = tag_value_to_atom("DataLicense: CC0-1.0").unwrap();
518 assert_eq!(value, Atom::DataLicense("CC0-1.0".to_string()));
519 }
520
521 #[test]
522 fn package_verification_code_can_be_parsed() {
523 let (_, value) = package_verification_code(
524 "d6a770ba38583ed4bb4525bd96e50461655d2758(excludes: ./package.spdx)",
525 )
526 .unwrap();
527 assert_eq!(value.value, "d6a770ba38583ed4bb4525bd96e50461655d2758");
528 assert_eq!(value.excludes, vec!["./package.spdx"]);
529 }
530
531 #[test]
532 fn package_verification_code_without_excludes_can_be_parsed() {
533 let (_, value) =
534 package_verification_code("d6a770ba38583ed4bb4525bd96e50461655d2758").unwrap();
535 assert_eq!(value.value, "d6a770ba38583ed4bb4525bd96e50461655d2758");
536 let expected: Vec<String> = Vec::new();
537 assert_eq!(value.excludes, expected);
538 }
539
540 #[test]
541 fn external_package_ref_can_be_parsed() {
542 let (_, value) = external_package_reference(
543 "SECURITY cpe23Type cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*",
544 )
545 .unwrap();
546 assert_eq!(
547 value.reference_category,
548 ExternalPackageReferenceCategory::Security
549 );
550 assert_eq!(value.reference_type, "cpe23Type");
551 assert_eq!(
552 value.reference_locator,
553 "cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*"
554 );
555 }
556
557 #[test]
558 fn external_document_reference_can_be_parsed() {
559 let (_, value) = external_document_reference("DocumentRef-spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 SHA1: d6a770ba38583ed4bb4525bd96e50461655d2759").unwrap();
560 assert_eq!(value.id_string, "spdx-tool-1.2");
561 assert_eq!(
562 value.spdx_document_uri,
563 "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301"
564 );
565 assert_eq!(value.checksum.algorithm, Algorithm::SHA1);
566 assert_eq!(
567 value.checksum.value,
568 "d6a770ba38583ed4bb4525bd96e50461655d2759"
569 );
570 }
571
572 #[test]
573 fn document_ref_can_be_parsed() {
574 let (_, value) = document_ref("DocumentRef-spdx-tool-1.2").unwrap();
575 assert_eq!(value, "spdx-tool-1.2");
576 }
577
578 #[test]
579 fn checksum_can_be_parsed() {
580 let (_, value) = checksum("SHA1: d6a770ba38583ed4bb4525bd96e50461655d2759").unwrap();
581 assert_eq!(value.algorithm, Algorithm::SHA1);
582 assert_eq!(value.value, "d6a770ba38583ed4bb4525bd96e50461655d2759");
583 }
584
585 #[test]
586 fn document_comment_can_be_parsed() {
587 let (_, value) = tag_value_to_atom("DocumentComment: <text>Sample Comment</text>").unwrap();
588 assert_eq!(value, Atom::DocumentComment("Sample Comment".to_string()));
589 }
590
591 #[test]
592 fn multiline_document_comment_can_be_parsed() {
593 let (_, value) = tag_value_to_atom(
594 "DocumentComment: <text>Sample
595Comment</text>",
596 )
597 .unwrap();
598 assert_eq!(value, Atom::DocumentComment("Sample\nComment".to_string()));
599 }
600
601 #[test]
602 fn multiple_key_values_can_be_parsed() {
603 let input = "SPDXVersion: SPDX-1.2
604 DataLicense: CC0-1.0
605 DocumentComment: <text>Sample Comment</text>";
606
607 let (_, value) = atoms(input).unwrap();
608 assert_eq!(
609 value,
610 vec![
611 Atom::SpdxVersion("SPDX-1.2".to_string()),
612 Atom::DataLicense("CC0-1.0".to_string()),
613 Atom::DocumentComment("Sample Comment".to_string())
614 ]
615 );
616 }
617
618 #[test]
619 fn multiple_key_values_with_comment_can_be_parsed() {
620 let input = "SPDXVersion: SPDX-1.2
621 # A comment
622 DataLicense: CC0-1.0
623 DocumentComment: <text>Sample Comment</text>";
624
625 let (_, value) = atoms(input).unwrap();
626 assert_eq!(
627 value,
628 vec![
629 Atom::SpdxVersion("SPDX-1.2".to_string()),
630 Atom::TVComment("A comment".to_string()),
631 Atom::DataLicense("CC0-1.0".to_string()),
632 Atom::DocumentComment("Sample Comment".to_string())
633 ]
634 );
635 }
636
637 #[test]
638 fn multiple_key_values_with_space_can_be_parsed() {
639 let input = "SPDXVersion: SPDX-1.2
640
641 DataLicense: CC0-1.0
642 DocumentComment: <text>Sample Comment</text>";
643
644 let (_, value) = atoms(input).unwrap();
645 assert_eq!(
646 value,
647 vec![
648 Atom::SpdxVersion("SPDX-1.2".to_string()),
649 Atom::DataLicense("CC0-1.0".to_string()),
650 Atom::DocumentComment("Sample Comment".to_string())
651 ]
652 );
653 }
654
655 #[test]
656 fn key_value_pair_is_detected() {
657 let (_, value) = tag_value("SPDXVersion: SPDX-1.2").unwrap();
658 assert_eq!(value, ("SPDXVersion", "SPDX-1.2"));
659 }
660
661 #[test]
662 fn get_tag_values_from_simple_example_file() {
663 let file = read_to_string("tests/data/SPDXSimpleTag.tag").unwrap();
664 let (remains, result) = atoms(&file).unwrap();
665 assert_eq!(remains.len(), 0);
666 assert!(result.contains(&Atom::SpdxVersion("SPDX-1.2".to_string())));
667 assert!(result.contains(&Atom::PackageName("Test".to_string())));
668 assert!(result.contains(&Atom::PackageDescription("A package.".to_string())));
669 }
670
671 #[test]
672 fn get_tag_values_from_example_file() {
673 let file = read_to_string("tests/data/SPDXTagExample-v2.2.spdx").unwrap();
674 let (remains, result) = atoms(&file).unwrap();
675 assert_eq!(remains.len(), 0);
676 assert!(result.contains(&Atom::SpdxVersion("SPDX-2.2".to_string())));
677 assert!(result.contains(&Atom::LicenseListVersion("3.9".to_string())));
678 assert!(result.contains(&Atom::PackageLicenseDeclared("MPL-1.0".to_string())));
679 }
680
681 #[test]
682 fn relationship_case() {
683 relationship("SPDXRef-DOCUMENT DESCRIBES SPDXRef-File").expect("Caps is expected");
684 relationship("SPDXRef-DOCUMENT describes SPDXRef-File")
685 .expect("At least reuse-tool emits lowercase");
686 }
687}
688