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