1pub fn strip_purl_qualifiers(purl: &str) -> &str {
5 match purl.find('?') {
6 Some(idx) => &purl[..idx],
7 None => purl,
8 }
9}
10
11pub fn is_pypi_purl(purl: &str) -> bool {
13 purl.starts_with("pkg:pypi/")
14}
15
16pub fn is_npm_purl(purl: &str) -> bool {
18 purl.starts_with("pkg:npm/")
19}
20
21pub fn parse_pypi_purl(purl: &str) -> Option<(&str, &str)> {
25 let base = strip_purl_qualifiers(purl);
26 let rest = base.strip_prefix("pkg:pypi/")?;
27 let at_idx = rest.rfind('@')?;
28 let name = &rest[..at_idx];
29 let version = &rest[at_idx + 1..];
30 if name.is_empty() || version.is_empty() {
31 return None;
32 }
33 Some((name, version))
34}
35
36pub fn parse_npm_purl(purl: &str) -> Option<(Option<&str>, &str, &str)> {
41 let base = strip_purl_qualifiers(purl);
42 let rest = base.strip_prefix("pkg:npm/")?;
43
44 let at_idx = rest.rfind('@')?;
46 let name_part = &rest[..at_idx];
47 let version = &rest[at_idx + 1..];
48
49 if name_part.is_empty() || version.is_empty() {
50 return None;
51 }
52
53 if name_part.starts_with('@') {
55 let slash_idx = name_part.find('/')?;
56 let namespace = &name_part[..slash_idx];
57 let name = &name_part[slash_idx + 1..];
58 if name.is_empty() {
59 return None;
60 }
61 Some((Some(namespace), name, version))
62 } else {
63 Some((None, name_part, version))
64 }
65}
66
67#[cfg(feature = "gem")]
69pub fn is_gem_purl(purl: &str) -> bool {
70 purl.starts_with("pkg:gem/")
71}
72
73#[cfg(feature = "gem")]
77pub fn parse_gem_purl(purl: &str) -> Option<(&str, &str)> {
78 let base = strip_purl_qualifiers(purl);
79 let rest = base.strip_prefix("pkg:gem/")?;
80 let at_idx = rest.rfind('@')?;
81 let name = &rest[..at_idx];
82 let version = &rest[at_idx + 1..];
83 if name.is_empty() || version.is_empty() {
84 return None;
85 }
86 Some((name, version))
87}
88
89#[cfg(feature = "gem")]
91pub fn build_gem_purl(name: &str, version: &str) -> String {
92 format!("pkg:gem/{name}@{version}")
93}
94
95#[cfg(feature = "maven")]
97pub fn is_maven_purl(purl: &str) -> bool {
98 purl.starts_with("pkg:maven/")
99}
100
101#[cfg(feature = "maven")]
105pub fn parse_maven_purl(purl: &str) -> Option<(&str, &str, &str)> {
106 let base = strip_purl_qualifiers(purl);
107 let rest = base.strip_prefix("pkg:maven/")?;
108 let at_idx = rest.rfind('@')?;
109 let name_part = &rest[..at_idx];
110 let version = &rest[at_idx + 1..];
111
112 if name_part.is_empty() || version.is_empty() {
113 return None;
114 }
115
116 let slash_idx = name_part.find('/')?;
118 let group_id = &name_part[..slash_idx];
119 let artifact_id = &name_part[slash_idx + 1..];
120
121 if group_id.is_empty() || artifact_id.is_empty() {
122 return None;
123 }
124
125 Some((group_id, artifact_id, version))
126}
127
128#[cfg(feature = "maven")]
130pub fn build_maven_purl(group_id: &str, artifact_id: &str, version: &str) -> String {
131 format!("pkg:maven/{group_id}/{artifact_id}@{version}")
132}
133
134#[cfg(feature = "golang")]
136pub fn is_golang_purl(purl: &str) -> bool {
137 purl.starts_with("pkg:golang/")
138}
139
140#[cfg(feature = "golang")]
144pub fn parse_golang_purl(purl: &str) -> Option<(&str, &str)> {
145 let base = strip_purl_qualifiers(purl);
146 let rest = base.strip_prefix("pkg:golang/")?;
147 let at_idx = rest.rfind('@')?;
148 let module_path = &rest[..at_idx];
149 let version = &rest[at_idx + 1..];
150 if module_path.is_empty() || version.is_empty() {
151 return None;
152 }
153 Some((module_path, version))
154}
155
156#[cfg(feature = "golang")]
158pub fn build_golang_purl(module_path: &str, version: &str) -> String {
159 format!("pkg:golang/{module_path}@{version}")
160}
161
162#[cfg(feature = "composer")]
164pub fn is_composer_purl(purl: &str) -> bool {
165 purl.starts_with("pkg:composer/")
166}
167
168#[cfg(feature = "composer")]
173pub fn parse_composer_purl(purl: &str) -> Option<((&str, &str), &str)> {
174 let base = strip_purl_qualifiers(purl);
175 let rest = base.strip_prefix("pkg:composer/")?;
176 let at_idx = rest.rfind('@')?;
177 let name_part = &rest[..at_idx];
178 let version = &rest[at_idx + 1..];
179
180 if name_part.is_empty() || version.is_empty() {
181 return None;
182 }
183
184 let slash_idx = name_part.find('/')?;
186 let namespace = &name_part[..slash_idx];
187 let name = &name_part[slash_idx + 1..];
188
189 if namespace.is_empty() || name.is_empty() {
190 return None;
191 }
192
193 Some(((namespace, name), version))
194}
195
196#[cfg(feature = "composer")]
198pub fn build_composer_purl(namespace: &str, name: &str, version: &str) -> String {
199 format!("pkg:composer/{namespace}/{name}@{version}")
200}
201
202#[cfg(feature = "nuget")]
204pub fn is_nuget_purl(purl: &str) -> bool {
205 purl.starts_with("pkg:nuget/")
206}
207
208#[cfg(feature = "nuget")]
212pub fn parse_nuget_purl(purl: &str) -> Option<(&str, &str)> {
213 let base = strip_purl_qualifiers(purl);
214 let rest = base.strip_prefix("pkg:nuget/")?;
215 let at_idx = rest.rfind('@')?;
216 let name = &rest[..at_idx];
217 let version = &rest[at_idx + 1..];
218 if name.is_empty() || version.is_empty() {
219 return None;
220 }
221 Some((name, version))
222}
223
224#[cfg(feature = "nuget")]
226pub fn build_nuget_purl(name: &str, version: &str) -> String {
227 format!("pkg:nuget/{name}@{version}")
228}
229
230#[cfg(feature = "cargo")]
232pub fn is_cargo_purl(purl: &str) -> bool {
233 purl.starts_with("pkg:cargo/")
234}
235
236#[cfg(feature = "cargo")]
240pub fn parse_cargo_purl(purl: &str) -> Option<(&str, &str)> {
241 let base = strip_purl_qualifiers(purl);
242 let rest = base.strip_prefix("pkg:cargo/")?;
243 let at_idx = rest.rfind('@')?;
244 let name = &rest[..at_idx];
245 let version = &rest[at_idx + 1..];
246 if name.is_empty() || version.is_empty() {
247 return None;
248 }
249 Some((name, version))
250}
251
252#[cfg(feature = "cargo")]
254pub fn build_cargo_purl(name: &str, version: &str) -> String {
255 format!("pkg:cargo/{name}@{version}")
256}
257
258pub fn parse_purl(purl: &str) -> Option<(&str, String, &str)> {
261 let base = strip_purl_qualifiers(purl);
262 if let Some(rest) = base.strip_prefix("pkg:npm/") {
263 let at_idx = rest.rfind('@')?;
264 let pkg_dir = &rest[..at_idx];
265 let version = &rest[at_idx + 1..];
266 if pkg_dir.is_empty() || version.is_empty() {
267 return None;
268 }
269 Some(("npm", pkg_dir.to_string(), version))
270 } else if let Some(rest) = base.strip_prefix("pkg:pypi/") {
271 let at_idx = rest.rfind('@')?;
272 let name = &rest[..at_idx];
273 let version = &rest[at_idx + 1..];
274 if name.is_empty() || version.is_empty() {
275 return None;
276 }
277 Some(("pypi", name.to_string(), version))
278 } else {
279 #[cfg(feature = "cargo")]
280 if let Some(rest) = base.strip_prefix("pkg:cargo/") {
281 let at_idx = rest.rfind('@')?;
282 let name = &rest[..at_idx];
283 let version = &rest[at_idx + 1..];
284 if name.is_empty() || version.is_empty() {
285 return None;
286 }
287 return Some(("cargo", name.to_string(), version));
288 }
289 #[cfg(feature = "golang")]
290 if let Some(rest) = base.strip_prefix("pkg:golang/") {
291 let at_idx = rest.rfind('@')?;
292 let module_path = &rest[..at_idx];
293 let version = &rest[at_idx + 1..];
294 if module_path.is_empty() || version.is_empty() {
295 return None;
296 }
297 return Some(("golang", module_path.to_string(), version));
298 }
299 #[cfg(feature = "gem")]
300 if let Some(rest) = base.strip_prefix("pkg:gem/") {
301 let at_idx = rest.rfind('@')?;
302 let name = &rest[..at_idx];
303 let version = &rest[at_idx + 1..];
304 if name.is_empty() || version.is_empty() {
305 return None;
306 }
307 return Some(("gem", name.to_string(), version));
308 }
309 #[cfg(feature = "maven")]
310 if let Some(rest) = base.strip_prefix("pkg:maven/") {
311 let at_idx = rest.rfind('@')?;
312 let name_part = &rest[..at_idx];
313 let version = &rest[at_idx + 1..];
314 if name_part.is_empty() || version.is_empty() {
315 return None;
316 }
317 return Some(("maven", name_part.to_string(), version));
318 }
319 #[cfg(feature = "composer")]
320 if let Some(rest) = base.strip_prefix("pkg:composer/") {
321 let at_idx = rest.rfind('@')?;
322 let name_part = &rest[..at_idx];
323 let version = &rest[at_idx + 1..];
324 if name_part.is_empty() || version.is_empty() {
325 return None;
326 }
327 return Some(("composer", name_part.to_string(), version));
328 }
329 #[cfg(feature = "nuget")]
330 if let Some(rest) = base.strip_prefix("pkg:nuget/") {
331 let at_idx = rest.rfind('@')?;
332 let name = &rest[..at_idx];
333 let version = &rest[at_idx + 1..];
334 if name.is_empty() || version.is_empty() {
335 return None;
336 }
337 return Some(("nuget", name.to_string(), version));
338 }
339 None
340 }
341}
342
343pub fn is_purl(s: &str) -> bool {
345 s.starts_with("pkg:")
346}
347
348pub fn build_npm_purl(namespace: Option<&str>, name: &str, version: &str) -> String {
350 match namespace {
351 Some(ns) => format!("pkg:npm/{}/{name}@{version}", ns),
352 None => format!("pkg:npm/{name}@{version}"),
353 }
354}
355
356pub fn build_pypi_purl(name: &str, version: &str) -> String {
358 format!("pkg:pypi/{name}@{version}")
359}
360
361#[cfg(test)]
362mod tests {
363 use super::*;
364
365 #[test]
366 fn test_strip_qualifiers() {
367 assert_eq!(
368 strip_purl_qualifiers("pkg:pypi/requests@2.28.0?artifact_id=abc"),
369 "pkg:pypi/requests@2.28.0"
370 );
371 assert_eq!(
372 strip_purl_qualifiers("pkg:npm/lodash@4.17.21"),
373 "pkg:npm/lodash@4.17.21"
374 );
375 }
376
377 #[test]
378 fn test_is_pypi_purl() {
379 assert!(is_pypi_purl("pkg:pypi/requests@2.28.0"));
380 assert!(!is_pypi_purl("pkg:npm/lodash@4.17.21"));
381 }
382
383 #[test]
384 fn test_is_npm_purl() {
385 assert!(is_npm_purl("pkg:npm/lodash@4.17.21"));
386 assert!(!is_npm_purl("pkg:pypi/requests@2.28.0"));
387 }
388
389 #[test]
390 fn test_parse_pypi_purl() {
391 assert_eq!(
392 parse_pypi_purl("pkg:pypi/requests@2.28.0"),
393 Some(("requests", "2.28.0"))
394 );
395 assert_eq!(
396 parse_pypi_purl("pkg:pypi/requests@2.28.0?artifact_id=abc"),
397 Some(("requests", "2.28.0"))
398 );
399 assert_eq!(parse_pypi_purl("pkg:npm/lodash@4.17.21"), None);
400 assert_eq!(parse_pypi_purl("pkg:pypi/@2.28.0"), None);
401 assert_eq!(parse_pypi_purl("pkg:pypi/requests@"), None);
402 }
403
404 #[test]
405 fn test_parse_npm_purl() {
406 assert_eq!(
407 parse_npm_purl("pkg:npm/lodash@4.17.21"),
408 Some((None, "lodash", "4.17.21"))
409 );
410 assert_eq!(
411 parse_npm_purl("pkg:npm/@types/node@20.0.0"),
412 Some((Some("@types"), "node", "20.0.0"))
413 );
414 assert_eq!(parse_npm_purl("pkg:pypi/requests@2.28.0"), None);
415 }
416
417 #[test]
418 fn test_parse_purl() {
419 let (eco, dir, ver) = parse_purl("pkg:npm/lodash@4.17.21").unwrap();
420 assert_eq!(eco, "npm");
421 assert_eq!(dir, "lodash");
422 assert_eq!(ver, "4.17.21");
423
424 let (eco, dir, ver) = parse_purl("pkg:npm/@types/node@20.0.0").unwrap();
425 assert_eq!(eco, "npm");
426 assert_eq!(dir, "@types/node");
427 assert_eq!(ver, "20.0.0");
428
429 let (eco, dir, ver) = parse_purl("pkg:pypi/requests@2.28.0").unwrap();
430 assert_eq!(eco, "pypi");
431 assert_eq!(dir, "requests");
432 assert_eq!(ver, "2.28.0");
433 }
434
435 #[test]
436 fn test_is_purl() {
437 assert!(is_purl("pkg:npm/lodash@4.17.21"));
438 assert!(is_purl("pkg:pypi/requests@2.28.0"));
439 assert!(!is_purl("lodash"));
440 assert!(!is_purl("CVE-2024-1234"));
441 }
442
443 #[test]
444 fn test_build_npm_purl() {
445 assert_eq!(
446 build_npm_purl(None, "lodash", "4.17.21"),
447 "pkg:npm/lodash@4.17.21"
448 );
449 assert_eq!(
450 build_npm_purl(Some("@types"), "node", "20.0.0"),
451 "pkg:npm/@types/node@20.0.0"
452 );
453 }
454
455 #[test]
456 fn test_build_pypi_purl() {
457 assert_eq!(
458 build_pypi_purl("requests", "2.28.0"),
459 "pkg:pypi/requests@2.28.0"
460 );
461 }
462
463 #[cfg(feature = "cargo")]
464 #[test]
465 fn test_is_cargo_purl() {
466 assert!(is_cargo_purl("pkg:cargo/serde@1.0.200"));
467 assert!(!is_cargo_purl("pkg:npm/lodash@4.17.21"));
468 assert!(!is_cargo_purl("pkg:pypi/requests@2.28.0"));
469 }
470
471 #[cfg(feature = "cargo")]
472 #[test]
473 fn test_parse_cargo_purl() {
474 assert_eq!(
475 parse_cargo_purl("pkg:cargo/serde@1.0.200"),
476 Some(("serde", "1.0.200"))
477 );
478 assert_eq!(
479 parse_cargo_purl("pkg:cargo/serde_json@1.0.120"),
480 Some(("serde_json", "1.0.120"))
481 );
482 assert_eq!(parse_cargo_purl("pkg:npm/lodash@4.17.21"), None);
483 assert_eq!(parse_cargo_purl("pkg:cargo/@1.0.0"), None);
484 assert_eq!(parse_cargo_purl("pkg:cargo/serde@"), None);
485 }
486
487 #[cfg(feature = "cargo")]
488 #[test]
489 fn test_build_cargo_purl() {
490 assert_eq!(
491 build_cargo_purl("serde", "1.0.200"),
492 "pkg:cargo/serde@1.0.200"
493 );
494 }
495
496 #[cfg(feature = "cargo")]
497 #[test]
498 fn test_cargo_purl_round_trip() {
499 let purl = build_cargo_purl("tokio", "1.38.0");
500 let (name, version) = parse_cargo_purl(&purl).unwrap();
501 assert_eq!(name, "tokio");
502 assert_eq!(version, "1.38.0");
503 }
504
505 #[cfg(feature = "cargo")]
506 #[test]
507 fn test_parse_purl_cargo() {
508 let (eco, dir, ver) = parse_purl("pkg:cargo/serde@1.0.200").unwrap();
509 assert_eq!(eco, "cargo");
510 assert_eq!(dir, "serde");
511 assert_eq!(ver, "1.0.200");
512 }
513
514 #[cfg(feature = "gem")]
515 #[test]
516 fn test_is_gem_purl() {
517 assert!(is_gem_purl("pkg:gem/rails@7.1.0"));
518 assert!(!is_gem_purl("pkg:npm/lodash@4.17.21"));
519 assert!(!is_gem_purl("pkg:pypi/requests@2.28.0"));
520 }
521
522 #[cfg(feature = "gem")]
523 #[test]
524 fn test_parse_gem_purl() {
525 assert_eq!(
526 parse_gem_purl("pkg:gem/rails@7.1.0"),
527 Some(("rails", "7.1.0"))
528 );
529 assert_eq!(
530 parse_gem_purl("pkg:gem/nokogiri@1.16.5"),
531 Some(("nokogiri", "1.16.5"))
532 );
533 assert_eq!(parse_gem_purl("pkg:npm/lodash@4.17.21"), None);
534 assert_eq!(parse_gem_purl("pkg:gem/@1.0.0"), None);
535 assert_eq!(parse_gem_purl("pkg:gem/rails@"), None);
536 }
537
538 #[cfg(feature = "gem")]
539 #[test]
540 fn test_build_gem_purl() {
541 assert_eq!(
542 build_gem_purl("rails", "7.1.0"),
543 "pkg:gem/rails@7.1.0"
544 );
545 }
546
547 #[cfg(feature = "gem")]
548 #[test]
549 fn test_gem_purl_round_trip() {
550 let purl = build_gem_purl("nokogiri", "1.16.5");
551 let (name, version) = parse_gem_purl(&purl).unwrap();
552 assert_eq!(name, "nokogiri");
553 assert_eq!(version, "1.16.5");
554 }
555
556 #[cfg(feature = "gem")]
557 #[test]
558 fn test_parse_purl_gem() {
559 let (eco, dir, ver) = parse_purl("pkg:gem/rails@7.1.0").unwrap();
560 assert_eq!(eco, "gem");
561 assert_eq!(dir, "rails");
562 assert_eq!(ver, "7.1.0");
563 }
564
565 #[cfg(feature = "maven")]
566 #[test]
567 fn test_is_maven_purl() {
568 assert!(is_maven_purl("pkg:maven/org.apache.commons/commons-lang3@3.12.0"));
569 assert!(!is_maven_purl("pkg:npm/lodash@4.17.21"));
570 assert!(!is_maven_purl("pkg:pypi/requests@2.28.0"));
571 }
572
573 #[cfg(feature = "maven")]
574 #[test]
575 fn test_parse_maven_purl() {
576 assert_eq!(
577 parse_maven_purl("pkg:maven/org.apache.commons/commons-lang3@3.12.0"),
578 Some(("org.apache.commons", "commons-lang3", "3.12.0"))
579 );
580 assert_eq!(
581 parse_maven_purl("pkg:maven/com.google.guava/guava@32.1.3-jre"),
582 Some(("com.google.guava", "guava", "32.1.3-jre"))
583 );
584 assert_eq!(parse_maven_purl("pkg:npm/lodash@4.17.21"), None);
585 assert_eq!(parse_maven_purl("pkg:maven/@3.12.0"), None);
586 assert_eq!(parse_maven_purl("pkg:maven/org.apache.commons/@3.12.0"), None);
587 assert_eq!(parse_maven_purl("pkg:maven/org.apache.commons/commons-lang3@"), None);
588 }
589
590 #[cfg(feature = "maven")]
591 #[test]
592 fn test_build_maven_purl() {
593 assert_eq!(
594 build_maven_purl("org.apache.commons", "commons-lang3", "3.12.0"),
595 "pkg:maven/org.apache.commons/commons-lang3@3.12.0"
596 );
597 }
598
599 #[cfg(feature = "maven")]
600 #[test]
601 fn test_maven_purl_round_trip() {
602 let purl = build_maven_purl("com.google.guava", "guava", "32.1.3-jre");
603 let (group_id, artifact_id, version) = parse_maven_purl(&purl).unwrap();
604 assert_eq!(group_id, "com.google.guava");
605 assert_eq!(artifact_id, "guava");
606 assert_eq!(version, "32.1.3-jre");
607 }
608
609 #[cfg(feature = "maven")]
610 #[test]
611 fn test_parse_purl_maven() {
612 let (eco, dir, ver) = parse_purl("pkg:maven/org.apache.commons/commons-lang3@3.12.0").unwrap();
613 assert_eq!(eco, "maven");
614 assert_eq!(dir, "org.apache.commons/commons-lang3");
615 assert_eq!(ver, "3.12.0");
616 }
617
618 #[cfg(feature = "golang")]
619 #[test]
620 fn test_is_golang_purl() {
621 assert!(is_golang_purl("pkg:golang/github.com/gin-gonic/gin@v1.9.1"));
622 assert!(!is_golang_purl("pkg:npm/lodash@4.17.21"));
623 assert!(!is_golang_purl("pkg:pypi/requests@2.28.0"));
624 }
625
626 #[cfg(feature = "golang")]
627 #[test]
628 fn test_parse_golang_purl() {
629 assert_eq!(
630 parse_golang_purl("pkg:golang/github.com/gin-gonic/gin@v1.9.1"),
631 Some(("github.com/gin-gonic/gin", "v1.9.1"))
632 );
633 assert_eq!(
634 parse_golang_purl("pkg:golang/golang.org/x/text@v0.14.0"),
635 Some(("golang.org/x/text", "v0.14.0"))
636 );
637 assert_eq!(parse_golang_purl("pkg:npm/lodash@4.17.21"), None);
638 assert_eq!(parse_golang_purl("pkg:golang/@v1.0.0"), None);
639 assert_eq!(parse_golang_purl("pkg:golang/github.com/foo/bar@"), None);
640 }
641
642 #[cfg(feature = "golang")]
643 #[test]
644 fn test_build_golang_purl() {
645 assert_eq!(
646 build_golang_purl("github.com/gin-gonic/gin", "v1.9.1"),
647 "pkg:golang/github.com/gin-gonic/gin@v1.9.1"
648 );
649 }
650
651 #[cfg(feature = "golang")]
652 #[test]
653 fn test_golang_purl_round_trip() {
654 let purl = build_golang_purl("golang.org/x/text", "v0.14.0");
655 let (module_path, version) = parse_golang_purl(&purl).unwrap();
656 assert_eq!(module_path, "golang.org/x/text");
657 assert_eq!(version, "v0.14.0");
658 }
659
660 #[cfg(feature = "golang")]
661 #[test]
662 fn test_parse_purl_golang() {
663 let (eco, dir, ver) = parse_purl("pkg:golang/github.com/gin-gonic/gin@v1.9.1").unwrap();
664 assert_eq!(eco, "golang");
665 assert_eq!(dir, "github.com/gin-gonic/gin");
666 assert_eq!(ver, "v1.9.1");
667 }
668
669 #[cfg(feature = "composer")]
670 #[test]
671 fn test_is_composer_purl() {
672 assert!(is_composer_purl("pkg:composer/monolog/monolog@3.5.0"));
673 assert!(!is_composer_purl("pkg:npm/lodash@4.17.21"));
674 assert!(!is_composer_purl("pkg:pypi/requests@2.28.0"));
675 }
676
677 #[cfg(feature = "composer")]
678 #[test]
679 fn test_parse_composer_purl() {
680 assert_eq!(
681 parse_composer_purl("pkg:composer/monolog/monolog@3.5.0"),
682 Some((("monolog", "monolog"), "3.5.0"))
683 );
684 assert_eq!(
685 parse_composer_purl("pkg:composer/symfony/console@6.4.1"),
686 Some((("symfony", "console"), "6.4.1"))
687 );
688 assert_eq!(parse_composer_purl("pkg:npm/lodash@4.17.21"), None);
689 assert_eq!(parse_composer_purl("pkg:composer/@3.5.0"), None);
690 assert_eq!(parse_composer_purl("pkg:composer/monolog/@3.5.0"), None);
691 assert_eq!(parse_composer_purl("pkg:composer/monolog/monolog@"), None);
692 }
693
694 #[cfg(feature = "composer")]
695 #[test]
696 fn test_build_composer_purl() {
697 assert_eq!(
698 build_composer_purl("monolog", "monolog", "3.5.0"),
699 "pkg:composer/monolog/monolog@3.5.0"
700 );
701 }
702
703 #[cfg(feature = "composer")]
704 #[test]
705 fn test_composer_purl_round_trip() {
706 let purl = build_composer_purl("symfony", "console", "6.4.1");
707 let ((namespace, name), version) = parse_composer_purl(&purl).unwrap();
708 assert_eq!(namespace, "symfony");
709 assert_eq!(name, "console");
710 assert_eq!(version, "6.4.1");
711 }
712
713 #[cfg(feature = "composer")]
714 #[test]
715 fn test_parse_purl_composer() {
716 let (eco, dir, ver) = parse_purl("pkg:composer/monolog/monolog@3.5.0").unwrap();
717 assert_eq!(eco, "composer");
718 assert_eq!(dir, "monolog/monolog");
719 assert_eq!(ver, "3.5.0");
720 }
721
722 #[cfg(feature = "nuget")]
723 #[test]
724 fn test_is_nuget_purl() {
725 assert!(is_nuget_purl("pkg:nuget/Newtonsoft.Json@13.0.3"));
726 assert!(!is_nuget_purl("pkg:npm/lodash@4.17.21"));
727 assert!(!is_nuget_purl("pkg:pypi/requests@2.28.0"));
728 }
729
730 #[cfg(feature = "nuget")]
731 #[test]
732 fn test_parse_nuget_purl() {
733 assert_eq!(
734 parse_nuget_purl("pkg:nuget/Newtonsoft.Json@13.0.3"),
735 Some(("Newtonsoft.Json", "13.0.3"))
736 );
737 assert_eq!(
738 parse_nuget_purl("pkg:nuget/System.Text.Json@8.0.0"),
739 Some(("System.Text.Json", "8.0.0"))
740 );
741 assert_eq!(parse_nuget_purl("pkg:npm/lodash@4.17.21"), None);
742 assert_eq!(parse_nuget_purl("pkg:nuget/@1.0.0"), None);
743 assert_eq!(parse_nuget_purl("pkg:nuget/Newtonsoft.Json@"), None);
744 }
745
746 #[cfg(feature = "nuget")]
747 #[test]
748 fn test_build_nuget_purl() {
749 assert_eq!(
750 build_nuget_purl("Newtonsoft.Json", "13.0.3"),
751 "pkg:nuget/Newtonsoft.Json@13.0.3"
752 );
753 }
754
755 #[cfg(feature = "nuget")]
756 #[test]
757 fn test_nuget_purl_round_trip() {
758 let purl = build_nuget_purl("System.Text.Json", "8.0.0");
759 let (name, version) = parse_nuget_purl(&purl).unwrap();
760 assert_eq!(name, "System.Text.Json");
761 assert_eq!(version, "8.0.0");
762 }
763
764 #[cfg(feature = "nuget")]
765 #[test]
766 fn test_parse_purl_nuget() {
767 let (eco, dir, ver) = parse_purl("pkg:nuget/Newtonsoft.Json@13.0.3").unwrap();
768 assert_eq!(eco, "nuget");
769 assert_eq!(dir, "Newtonsoft.Json");
770 assert_eq!(ver, "13.0.3");
771 }
772}