provenant/license_detection/embedded/
index.rs1use super::schema::{EmbeddedArtifactMetadata, EmbeddedLoaderSnapshot, SCHEMA_VERSION};
5use crate::license_detection::index::LicenseIndex;
6use crate::license_detection::index::build_index_from_loaded;
7
8#[derive(Debug, Clone)]
10#[allow(dead_code)]
11pub struct LoadedEmbeddedLicenseIndex {
12 pub index: LicenseIndex,
13 pub metadata: EmbeddedArtifactMetadata,
14}
15
16#[derive(Debug, Clone, thiserror::Error)]
17#[error("License loader artifact error: {0}")]
18pub struct SerializationError(pub String);
19
20pub fn load_loader_snapshot_from_bytes(
21 bytes: &[u8],
22) -> Result<EmbeddedLoaderSnapshot, SerializationError> {
23 if bytes.is_empty() {
24 return Err(SerializationError(
25 "Embedded license index artifact is empty".to_string(),
26 ));
27 }
28
29 let decompressed = zstd::decode_all(bytes).map_err(|e| {
30 SerializationError(format!("Failed to decompress embedded artifact: {}", e))
31 })?;
32
33 let snapshot: EmbeddedLoaderSnapshot = postcard::from_bytes(&decompressed).map_err(|e| {
34 SerializationError(format!("Failed to deserialize embedded artifact: {}", e))
35 })?;
36
37 if snapshot.schema_version != SCHEMA_VERSION {
38 return Err(SerializationError(format!(
39 "Embedded artifact schema version mismatch: expected {}, got {}",
40 SCHEMA_VERSION, snapshot.schema_version
41 )));
42 }
43
44 Ok(snapshot)
45}
46
47#[allow(dead_code)]
49pub fn load_embedded_license_index_from_bytes(
50 bytes: &[u8],
51) -> Result<LoadedEmbeddedLicenseIndex, SerializationError> {
52 let snapshot = load_loader_snapshot_from_bytes(bytes)?;
53 let index = build_index_from_loaded(snapshot.rules, snapshot.licenses, false);
54
55 Ok(LoadedEmbeddedLicenseIndex {
56 index,
57 metadata: snapshot.metadata,
58 })
59}
60
61pub fn load_embedded_artifact_metadata_from_bytes(
62 bytes: &[u8],
63) -> Result<EmbeddedArtifactMetadata, SerializationError> {
64 Ok(load_loader_snapshot_from_bytes(bytes)?.metadata)
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use crate::license_detection::models::{LoadedLicense, LoadedRule};
71
72 fn create_test_metadata() -> EmbeddedArtifactMetadata {
73 EmbeddedArtifactMetadata {
74 spdx_license_list_version: "3.27".to_string(),
75 license_index_provenance: crate::models::LicenseIndexProvenance {
76 source: "embedded-artifact".to_string(),
77 dataset_fingerprint: "test".to_string(),
78 ignored_rules: vec![],
79 ignored_licenses: vec![],
80 ignored_rules_due_to_licenses: vec![],
81 added_rules: vec![],
82 replaced_rules: vec![],
83 added_licenses: vec![],
84 replaced_licenses: vec![],
85 },
86 }
87 }
88
89 fn serialize_loader_snapshot_to_bytes(
90 rules: Vec<LoadedRule>,
91 licenses: Vec<LoadedLicense>,
92 ) -> Result<Vec<u8>, SerializationError> {
93 let snapshot = EmbeddedLoaderSnapshot {
94 schema_version: SCHEMA_VERSION,
95 metadata: create_test_metadata(),
96 rules,
97 licenses,
98 };
99
100 let postcard_bytes = postcard::to_allocvec(&snapshot).map_err(|e| {
101 SerializationError(format!("Failed to serialize embedded artifact: {}", e))
102 })?;
103
104 zstd::encode_all(&postcard_bytes[..], 0)
105 .map_err(|e| SerializationError(format!("Failed to compress embedded artifact: {}", e)))
106 }
107
108 fn create_test_loaded_rule() -> LoadedRule {
109 LoadedRule {
110 identifier: "test.RULE".to_string(),
111 license_expression: "mit".to_string(),
112 text: "MIT License text".to_string(),
113 rule_kind: crate::license_detection::models::RuleKind::Text,
114 is_false_positive: false,
115 is_required_phrase: false,
116 skip_for_required_phrase_generation: false,
117 relevance: Some(100),
118 minimum_coverage: None,
119 has_stored_minimum_coverage: false,
120 is_continuous: false,
121 referenced_filenames: None,
122 ignorable_urls: None,
123 ignorable_emails: None,
124 ignorable_copyrights: None,
125 ignorable_holders: None,
126 ignorable_authors: None,
127 language: None,
128 notes: None,
129 is_deprecated: false,
130 replaced_by: vec![],
131 }
132 }
133
134 fn create_test_loaded_license() -> LoadedLicense {
135 LoadedLicense {
136 key: "mit".to_string(),
137 short_name: Some("MIT".to_string()),
138 name: "MIT License".to_string(),
139 language: Some("en".to_string()),
140 spdx_license_key: Some("MIT".to_string()),
141 other_spdx_license_keys: vec![],
142 category: Some("Permissive".to_string()),
143 owner: None,
144 homepage_url: None,
145 text: "MIT License text".to_string(),
146 reference_urls: vec![],
147 osi_license_key: None,
148 text_urls: vec![],
149 osi_url: None,
150 faq_url: None,
151 other_urls: vec![],
152 notes: None,
153 is_deprecated: false,
154 is_exception: false,
155 is_unknown: false,
156 is_generic: false,
157 replaced_by: vec![],
158 minimum_coverage: None,
159 standard_notice: None,
160 ignorable_copyrights: None,
161 ignorable_holders: None,
162 ignorable_authors: None,
163 ignorable_urls: None,
164 ignorable_emails: None,
165 }
166 }
167
168 #[test]
169 fn test_load_license_index_from_bytes_roundtrip() {
170 let bytes = serialize_loader_snapshot_to_bytes(
171 vec![create_test_loaded_rule()],
172 vec![create_test_loaded_license()],
173 )
174 .expect("Should serialize");
175
176 let index = load_embedded_license_index_from_bytes(&bytes)
177 .expect("Should deserialize")
178 .index;
179
180 assert_eq!(index.licenses_by_key.len(), 1);
181 assert!(
182 index
183 .rules_by_rid
184 .iter()
185 .any(|rule| rule.identifier == "test.RULE"),
186 "runtime index should retain the serialized rule"
187 );
188 assert!(
189 index
190 .rules_by_rid
191 .iter()
192 .any(|rule| rule.identifier == "mit.LICENSE"),
193 "runtime index should synthesize a license-derived rule"
194 );
195 }
196
197 #[test]
198 fn test_load_embedded_artifact_metadata_from_bytes_roundtrip() {
199 let bytes = serialize_loader_snapshot_to_bytes(
200 vec![create_test_loaded_rule()],
201 vec![create_test_loaded_license()],
202 )
203 .expect("Should serialize");
204
205 let metadata = load_embedded_artifact_metadata_from_bytes(&bytes)
206 .expect("Should deserialize metadata");
207
208 assert_eq!(metadata.spdx_license_list_version, "3.27");
209 assert_eq!(
210 metadata.license_index_provenance.source,
211 "embedded-artifact"
212 );
213 }
214
215 #[test]
216 fn test_load_license_index_from_bytes_rejects_empty() {
217 let error = load_embedded_license_index_from_bytes(&[]).unwrap_err();
218 assert!(error.to_string().contains("artifact is empty"));
219 }
220}