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