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