socket_patch_core/vex/mod.rs
1//! OpenVEX 0.2.0 document generation from a Socket Patch manifest.
2//!
3//! Self-contained so it can be lifted into its own crate later. The
4//! module is organized as:
5//!
6//! * [`schema`] — hand-rolled OpenVEX 0.2.0 serde structs.
7//! * [`build`] — manifest + applied-set → [`schema::Document`].
8//! * [`product`] — auto-detect the top-level product PURL from the
9//! filesystem (package.json / pyproject.toml / Cargo.toml).
10//! * [`verify`] — partition manifest entries by on-disk hash check.
11//! * [`time`] — minimal RFC 3339 timestamp formatter (no chrono).
12//!
13//! Cross-references against the Go reference implementation
14//! (<https://github.com/openvex/go-vex>) live next to the affected
15//! struct in [`schema`].
16
17pub mod build;
18pub mod product;
19pub mod schema;
20pub mod time;
21pub mod verify;
22
23pub use build::{build_document, BuildOptions};
24pub use product::{detect_product, DetectResult};
25pub use schema::{
26 Document, Justification, Product, Statement, Status, Subcomponent, Vulnerability,
27 OPENVEX_CONTEXT_V0_2_0,
28};
29pub use verify::{applied_patches, FailedPatch, VerifyOutcome};
30
31#[cfg(test)]
32mod conformance_tests;
33
34#[cfg(test)]
35mod reexport_tests {
36 //! Compile-only smoke tests for the public surface. If a future
37 //! refactor drops a `pub use` line, this module will fail to
38 //! compile — the visible symptom we want.
39
40 use super::*;
41
42 #[test]
43 fn every_reexport_is_usable_from_vex_namespace() {
44 // Names — just touching each one keeps the linker honest.
45 let _: &str = OPENVEX_CONTEXT_V0_2_0;
46
47 // Types instantiable via Default or struct literal.
48 let _ = DetectResult::default();
49 let _ = VerifyOutcome::default();
50 let _ = FailedPatch {
51 purl: String::new(),
52 reason: String::new(),
53 };
54 let _ = BuildOptions {
55 product_id: String::new(),
56 doc_id: String::new(),
57 author: String::new(),
58 tooling: None,
59 };
60 let _ = Vulnerability {
61 name: "GHSA-x".to_string(),
62 aliases: Vec::new(),
63 };
64 let _ = Subcomponent {
65 id: "pkg:npm/x@1".to_string(),
66 identifiers: None,
67 hashes: None,
68 };
69 let _ = Product {
70 id: "pkg:npm/app@1.0".to_string(),
71 identifiers: None,
72 hashes: None,
73 subcomponents: Vec::new(),
74 };
75 let _ = Statement {
76 id: None,
77 vulnerability: Vulnerability {
78 name: "GHSA-x".to_string(),
79 aliases: Vec::new(),
80 },
81 timestamp: None,
82 last_updated: None,
83 products: Vec::new(),
84 status: Status::NotAffected,
85 supplier: None,
86 justification: Some(Justification::InlineMitigationsAlreadyExist),
87 impact_statement: None,
88 action_statement: None,
89 };
90 let _ = Document {
91 context: OPENVEX_CONTEXT_V0_2_0.to_string(),
92 id: String::new(),
93 author: String::new(),
94 role: None,
95 timestamp: String::new(),
96 last_updated: None,
97 version: 1,
98 tooling: None,
99 statements: Vec::new(),
100 };
101
102 // Functions — reference them so an accidental rename
103 // surfaces here. We can't easily type async fns with
104 // reference parameters as `fn(_)` pointers (the lifetime
105 // bound goes through the returned future), so just take
106 // their address and discard it; the resolver will error if
107 // the symbol disappears.
108 let _ = build_document as *const ();
109 let _ = detect_product as *const ();
110 let _ = applied_patches as *const ();
111 }
112}