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 = rmp_serde::from_slice(&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 }
78 }
79
80 fn serialize_loader_snapshot_to_bytes(
81 rules: Vec<LoadedRule>,
82 licenses: Vec<LoadedLicense>,
83 ) -> Result<Vec<u8>, SerializationError> {
84 let snapshot = EmbeddedLoaderSnapshot {
85 schema_version: SCHEMA_VERSION,
86 metadata: create_test_metadata(),
87 rules,
88 licenses,
89 };
90
91 let msgpack = rmp_serde::to_vec(&snapshot).map_err(|e| {
92 SerializationError(format!("Failed to serialize embedded artifact: {}", e))
93 })?;
94
95 zstd::encode_all(&msgpack[..], 0)
96 .map_err(|e| SerializationError(format!("Failed to compress embedded artifact: {}", e)))
97 }
98
99 fn create_test_loaded_rule() -> LoadedRule {
100 LoadedRule {
101 identifier: "test.RULE".to_string(),
102 license_expression: "mit".to_string(),
103 text: "MIT License text".to_string(),
104 rule_kind: crate::license_detection::models::RuleKind::Text,
105 is_false_positive: false,
106 is_required_phrase: false,
107 skip_for_required_phrase_generation: false,
108 relevance: Some(100),
109 minimum_coverage: None,
110 has_stored_minimum_coverage: false,
111 is_continuous: false,
112 referenced_filenames: None,
113 ignorable_urls: None,
114 ignorable_emails: None,
115 ignorable_copyrights: None,
116 ignorable_holders: None,
117 ignorable_authors: None,
118 language: None,
119 notes: None,
120 is_deprecated: false,
121 replaced_by: vec![],
122 }
123 }
124
125 fn create_test_loaded_license() -> LoadedLicense {
126 LoadedLicense {
127 key: "mit".to_string(),
128 short_name: Some("MIT".to_string()),
129 name: "MIT License".to_string(),
130 language: Some("en".to_string()),
131 spdx_license_key: Some("MIT".to_string()),
132 other_spdx_license_keys: vec![],
133 category: Some("Permissive".to_string()),
134 owner: None,
135 homepage_url: None,
136 text: "MIT License text".to_string(),
137 reference_urls: vec![],
138 osi_license_key: None,
139 text_urls: vec![],
140 osi_url: None,
141 faq_url: None,
142 other_urls: vec![],
143 notes: None,
144 is_deprecated: false,
145 is_exception: false,
146 is_unknown: false,
147 is_generic: false,
148 replaced_by: vec![],
149 minimum_coverage: None,
150 standard_notice: None,
151 ignorable_copyrights: None,
152 ignorable_holders: None,
153 ignorable_authors: None,
154 ignorable_urls: None,
155 ignorable_emails: None,
156 }
157 }
158
159 #[test]
160 fn test_load_license_index_from_bytes_roundtrip() {
161 let bytes = serialize_loader_snapshot_to_bytes(
162 vec![create_test_loaded_rule()],
163 vec![create_test_loaded_license()],
164 )
165 .expect("Should serialize");
166
167 let index = load_embedded_license_index_from_bytes(&bytes)
168 .expect("Should deserialize")
169 .index;
170
171 assert_eq!(index.licenses_by_key.len(), 1);
172 assert!(
173 index
174 .rules_by_rid
175 .iter()
176 .any(|rule| rule.identifier == "test.RULE"),
177 "runtime index should retain the serialized rule"
178 );
179 assert!(
180 index
181 .rules_by_rid
182 .iter()
183 .any(|rule| rule.identifier == "mit.LICENSE"),
184 "runtime index should synthesize a license-derived rule"
185 );
186 }
187
188 #[test]
189 fn test_load_embedded_artifact_metadata_from_bytes_roundtrip() {
190 let bytes = serialize_loader_snapshot_to_bytes(
191 vec![create_test_loaded_rule()],
192 vec![create_test_loaded_license()],
193 )
194 .expect("Should serialize");
195
196 let metadata = load_embedded_artifact_metadata_from_bytes(&bytes)
197 .expect("Should deserialize metadata");
198
199 assert_eq!(metadata.spdx_license_list_version, "3.27");
200 }
201
202 #[test]
203 fn test_load_license_index_from_bytes_rejects_empty() {
204 let error = load_embedded_license_index_from_bytes(&[]).unwrap_err();
205 assert!(error.to_string().contains("artifact is empty"));
206 }
207}