1use crate::builder::verify_sha256;
6use crate::{BundleError, BundleResult, MANIFEST_FILE, Manifest, Platform};
7use std::fs::{self, File};
8use std::io::Read;
9use std::path::{Path, PathBuf};
10use zip::ZipArchive;
11
12#[derive(Debug)]
27pub struct BundleLoader {
28 archive: ZipArchive<File>,
29 manifest: Manifest,
30}
31
32impl BundleLoader {
33 pub fn open<P: AsRef<Path>>(path: P) -> BundleResult<Self> {
35 let path = path.as_ref();
36 let file = File::open(path)?;
37 let mut archive = ZipArchive::new(file)?;
38
39 let manifest = {
41 let mut manifest_file = archive.by_name(MANIFEST_FILE).map_err(|_| {
42 BundleError::MissingFile(format!("{MANIFEST_FILE} not found in bundle"))
43 })?;
44
45 let mut manifest_json = String::new();
46 manifest_file.read_to_string(&mut manifest_json)?;
47 Manifest::from_json(&manifest_json)?
48 };
49
50 manifest.validate()?;
52
53 Ok(Self { archive, manifest })
54 }
55
56 #[must_use]
58 pub fn manifest(&self) -> &Manifest {
59 &self.manifest
60 }
61
62 #[must_use]
64 pub fn supports_current_platform(&self) -> bool {
65 Platform::current()
66 .map(|p| self.manifest.supports_platform(p))
67 .unwrap_or(false)
68 }
69
70 #[must_use]
72 pub fn current_platform_info(&self) -> Option<&crate::PlatformInfo> {
73 Platform::current().and_then(|p| self.manifest.get_platform(p))
74 }
75
76 pub fn extract_library<P: AsRef<Path>>(
83 &mut self,
84 platform: Platform,
85 output_dir: P,
86 ) -> BundleResult<PathBuf> {
87 self.extract_library_variant(platform, "release", output_dir)
88 }
89
90 pub fn extract_library_variant<P: AsRef<Path>>(
94 &mut self,
95 platform: Platform,
96 variant: &str,
97 output_dir: P,
98 ) -> BundleResult<PathBuf> {
99 let output_dir = output_dir.as_ref();
100
101 let platform_info = self.manifest.get_platform(platform).ok_or_else(|| {
103 BundleError::UnsupportedPlatform(format!(
104 "Platform {} not found in bundle",
105 platform.as_str()
106 ))
107 })?;
108
109 let variant_info =
111 platform_info
112 .variant(variant)
113 .ok_or_else(|| BundleError::VariantNotFound {
114 platform: platform.as_str().to_string(),
115 variant: variant.to_string(),
116 })?;
117
118 let library_path = variant_info.library.clone();
119 let expected_checksum = variant_info.checksum.clone();
120
121 let contents = {
123 let mut library_file = self.archive.by_name(&library_path).map_err(|_| {
124 BundleError::MissingFile(format!("Library not found in bundle: {library_path}"))
125 })?;
126
127 let mut contents = Vec::new();
128 library_file.read_to_end(&mut contents)?;
129 contents
130 };
131
132 if !verify_sha256(&contents, &expected_checksum) {
134 let actual = crate::builder::compute_sha256(&contents);
135 return Err(BundleError::ChecksumMismatch {
136 path: library_path,
137 expected: expected_checksum,
138 actual: format!("sha256:{actual}"),
139 });
140 }
141
142 fs::create_dir_all(output_dir)?;
144
145 let file_name = Path::new(&library_path)
147 .file_name()
148 .ok_or_else(|| BundleError::InvalidManifest("Invalid library path".to_string()))?;
149
150 let output_path = output_dir.join(file_name);
151
152 fs::write(&output_path, &contents)?;
154
155 #[cfg(unix)]
157 {
158 use std::os::unix::fs::PermissionsExt;
159 let mut perms = fs::metadata(&output_path)?.permissions();
160 perms.set_mode(0o755);
161 fs::set_permissions(&output_path, perms)?;
162 }
163
164 Ok(output_path)
165 }
166
167 #[must_use]
169 pub fn list_variants(&self, platform: Platform) -> Vec<&str> {
170 self.manifest.list_variants(platform)
171 }
172
173 #[must_use]
175 pub fn has_variant(&self, platform: Platform, variant: &str) -> bool {
176 self.manifest
177 .get_platform(platform)
178 .map(|p| p.has_variant(variant))
179 .unwrap_or(false)
180 }
181
182 #[must_use]
184 pub fn build_info(&self) -> Option<&crate::BuildInfo> {
185 self.manifest.get_build_info()
186 }
187
188 #[must_use]
190 pub fn sbom(&self) -> Option<&crate::Sbom> {
191 self.manifest.get_sbom()
192 }
193
194 #[must_use]
196 pub fn effective_build_info(&self, platform: &str, variant: &str) -> Option<&crate::BuildInfo> {
197 self.manifest.get_effective_build_info(platform, variant)
198 }
199
200 #[must_use]
202 pub fn effective_sbom(&self, platform: &str, variant: &str) -> Option<&crate::Sbom> {
203 self.manifest.get_effective_sbom(platform, variant)
204 }
205
206 pub fn extract_library_for_current_platform<P: AsRef<Path>>(
210 &mut self,
211 output_dir: P,
212 ) -> BundleResult<PathBuf> {
213 let platform = Platform::current().ok_or_else(|| {
214 BundleError::UnsupportedPlatform("Current platform is not supported".to_string())
215 })?;
216
217 self.extract_library(platform, output_dir)
218 }
219
220 pub fn read_file(&mut self, path: &str) -> BundleResult<Vec<u8>> {
222 let mut file = self
223 .archive
224 .by_name(path)
225 .map_err(|_| BundleError::MissingFile(format!("File not found in bundle: {path}")))?;
226
227 let mut contents = Vec::new();
228 file.read_to_end(&mut contents)?;
229 Ok(contents)
230 }
231
232 pub fn read_file_string(&mut self, path: &str) -> BundleResult<String> {
234 let mut file = self
235 .archive
236 .by_name(path)
237 .map_err(|_| BundleError::MissingFile(format!("File not found in bundle: {path}")))?;
238
239 let mut contents = String::new();
240 file.read_to_string(&mut contents)?;
241 Ok(contents)
242 }
243
244 #[must_use]
246 pub fn list_files(&self) -> Vec<String> {
247 (0..self.archive.len())
248 .filter_map(|i| self.archive.name_for_index(i).map(String::from))
249 .collect()
250 }
251
252 #[must_use]
254 pub fn has_file(&self, path: &str) -> bool {
255 self.archive.index_for_name(path).is_some()
256 }
257
258 #[must_use]
260 pub fn has_signatures(&self) -> bool {
261 self.has_file("manifest.json.minisig")
262 }
263
264 #[must_use]
266 pub fn public_key(&self) -> Option<&str> {
267 self.manifest.public_key.as_deref()
268 }
269
270 pub fn verify_manifest_signature(&mut self) -> BundleResult<()> {
275 self.verify_manifest_signature_with_key(None)
276 }
277
278 pub fn verify_manifest_signature_with_key(
284 &mut self,
285 public_key_override: Option<&str>,
286 ) -> BundleResult<()> {
287 let public_key = public_key_override
289 .map(String::from)
290 .or_else(|| self.manifest.public_key.clone())
291 .ok_or(BundleError::NoPublicKey)?;
292
293 let manifest_data = self.read_file(MANIFEST_FILE)?;
295
296 let sig_path = format!("{MANIFEST_FILE}.minisig");
298 let sig_data = self.read_file(&sig_path)?;
299 let signature = String::from_utf8(sig_data).map_err(|e| {
300 BundleError::SignatureVerificationFailed(format!("Invalid signature encoding: {e}"))
301 })?;
302
303 verify_minisign_signature(&public_key, &manifest_data, &signature)
305 }
306
307 pub fn verify_library_signature(
314 &mut self,
315 library_path: &str,
316 library_data: &[u8],
317 ) -> BundleResult<()> {
318 self.verify_library_signature_with_key(library_path, library_data, None)
319 }
320
321 pub fn verify_library_signature_with_key(
323 &mut self,
324 library_path: &str,
325 library_data: &[u8],
326 public_key_override: Option<&str>,
327 ) -> BundleResult<()> {
328 let public_key = public_key_override
330 .map(String::from)
331 .or_else(|| self.manifest.public_key.clone())
332 .ok_or(BundleError::NoPublicKey)?;
333
334 let sig_path = format!("{library_path}.minisig");
336 let sig_data = self.read_file(&sig_path)?;
337 let signature = String::from_utf8(sig_data).map_err(|e| {
338 BundleError::SignatureVerificationFailed(format!("Invalid signature encoding: {e}"))
339 })?;
340
341 verify_minisign_signature(&public_key, library_data, &signature)
343 }
344
345 pub fn extract_library_verified<P: AsRef<Path>>(
356 &mut self,
357 platform: Platform,
358 output_dir: P,
359 verify_signature: bool,
360 public_key_override: Option<&str>,
361 ) -> BundleResult<PathBuf> {
362 if verify_signature {
364 self.verify_manifest_signature_with_key(public_key_override)?;
365 }
366
367 let output_dir = output_dir.as_ref();
368
369 let platform_info = self.manifest.get_platform(platform).ok_or_else(|| {
371 BundleError::UnsupportedPlatform(format!(
372 "Platform {} not found in bundle",
373 platform.as_str()
374 ))
375 })?;
376
377 let variant_info = platform_info
379 .release()
380 .ok_or_else(|| BundleError::VariantNotFound {
381 platform: platform.as_str().to_string(),
382 variant: "release".to_string(),
383 })?;
384
385 let library_path = variant_info.library.clone();
386 let expected_checksum = variant_info.checksum.clone();
387
388 let contents = self.read_file(&library_path)?;
390
391 if !verify_sha256(&contents, &expected_checksum) {
393 let actual = crate::builder::compute_sha256(&contents);
394 return Err(BundleError::ChecksumMismatch {
395 path: library_path,
396 expected: expected_checksum,
397 actual: format!("sha256:{actual}"),
398 });
399 }
400
401 if verify_signature {
403 self.verify_library_signature_with_key(&library_path, &contents, public_key_override)?;
404 }
405
406 fs::create_dir_all(output_dir)?;
408
409 let file_name = Path::new(&library_path)
411 .file_name()
412 .ok_or_else(|| BundleError::InvalidManifest("Invalid library path".to_string()))?;
413
414 let output_path = output_dir.join(file_name);
415
416 fs::write(&output_path, &contents)?;
418
419 #[cfg(unix)]
421 {
422 use std::os::unix::fs::PermissionsExt;
423 let mut perms = fs::metadata(&output_path)?.permissions();
424 perms.set_mode(0o755);
425 fs::set_permissions(&output_path, perms)?;
426 }
427
428 Ok(output_path)
429 }
430}
431
432fn verify_minisign_signature(
440 public_key_base64: &str,
441 data: &[u8],
442 signature_content: &str,
443) -> BundleResult<()> {
444 use minisign::{PublicKey, SignatureBox};
445
446 let public_key = PublicKey::from_base64(public_key_base64).map_err(|e| {
448 BundleError::SignatureVerificationFailed(format!("Invalid public key: {e}"))
449 })?;
450
451 let signature_box = SignatureBox::from_string(signature_content).map_err(|e| {
453 BundleError::SignatureVerificationFailed(format!("Invalid signature format: {e}"))
454 })?;
455
456 let mut data_reader = std::io::Cursor::new(data);
458 minisign::verify(
459 &public_key,
460 &signature_box,
461 &mut data_reader,
462 true,
463 false,
464 false,
465 )
466 .map_err(|e| {
467 BundleError::SignatureVerificationFailed(format!("Signature verification failed: {e}"))
468 })?;
469
470 Ok(())
471}
472
473#[cfg(test)]
474mod tests {
475 #![allow(non_snake_case)]
476
477 use super::*;
478 use crate::builder::{BundleBuilder, compute_sha256};
479 use std::io::Write;
480 use tempfile::TempDir;
481
482 fn create_test_bundle(temp_dir: &TempDir) -> PathBuf {
483 let bundle_path = temp_dir.path().join("test.rbp");
484
485 let lib_path = temp_dir.path().join("libtest.so");
487 fs::write(&lib_path, b"fake library contents").unwrap();
488
489 let manifest = Manifest::new("test-plugin", "1.0.0");
491 BundleBuilder::new(manifest)
492 .add_library(Platform::LinuxX86_64, &lib_path)
493 .unwrap()
494 .add_bytes("schema/messages.h", b"// header".to_vec())
495 .write(&bundle_path)
496 .unwrap();
497
498 bundle_path
499 }
500
501 fn create_multi_platform_bundle(temp_dir: &TempDir) -> PathBuf {
502 let bundle_path = temp_dir.path().join("multi.rbp");
503
504 let linux_lib = temp_dir.path().join("libtest.so");
505 let macos_lib = temp_dir.path().join("libtest.dylib");
506 let windows_lib = temp_dir.path().join("test.dll");
507 fs::write(&linux_lib, b"linux library").unwrap();
508 fs::write(&macos_lib, b"macos library").unwrap();
509 fs::write(&windows_lib, b"windows library").unwrap();
510
511 let manifest = Manifest::new("multi-platform", "2.0.0");
512 BundleBuilder::new(manifest)
513 .add_library(Platform::LinuxX86_64, &linux_lib)
514 .unwrap()
515 .add_library(Platform::DarwinAarch64, &macos_lib)
516 .unwrap()
517 .add_library(Platform::WindowsX86_64, &windows_lib)
518 .unwrap()
519 .write(&bundle_path)
520 .unwrap();
521
522 bundle_path
523 }
524
525 #[test]
526 fn BundleLoader___open___reads_manifest() {
527 let temp_dir = TempDir::new().unwrap();
528 let bundle_path = create_test_bundle(&temp_dir);
529
530 let loader = BundleLoader::open(&bundle_path).unwrap();
531
532 assert_eq!(loader.manifest().plugin.name, "test-plugin");
533 assert_eq!(loader.manifest().plugin.version, "1.0.0");
534 }
535
536 #[test]
537 fn BundleLoader___open___nonexistent_file___returns_error() {
538 let result = BundleLoader::open("/nonexistent/bundle.rbp");
539
540 assert!(result.is_err());
541 }
542
543 #[test]
544 fn BundleLoader___open___not_a_zip___returns_error() {
545 let temp_dir = TempDir::new().unwrap();
546 let fake_bundle = temp_dir.path().join("fake.rbp");
547 fs::write(&fake_bundle, b"not a zip file").unwrap();
548
549 let result = BundleLoader::open(&fake_bundle);
550
551 assert!(result.is_err());
552 }
553
554 #[test]
555 fn BundleLoader___open___missing_manifest___returns_error() {
556 let temp_dir = TempDir::new().unwrap();
557 let bundle_path = temp_dir.path().join("no-manifest.rbp");
558
559 let file = File::create(&bundle_path).unwrap();
561 let mut zip = zip::ZipWriter::new(file);
562 let options = zip::write::SimpleFileOptions::default();
563 zip.start_file("some-file.txt", options).unwrap();
564 zip.write_all(b"content").unwrap();
565 zip.finish().unwrap();
566
567 let result = BundleLoader::open(&bundle_path);
568
569 assert!(result.is_err());
570 let err = result.unwrap_err();
571 assert!(matches!(err, BundleError::MissingFile(_)));
572 assert!(err.to_string().contains("manifest.json"));
573 }
574
575 #[test]
576 fn BundleLoader___open___invalid_manifest_json___returns_error() {
577 let temp_dir = TempDir::new().unwrap();
578 let bundle_path = temp_dir.path().join("bad-manifest.rbp");
579
580 let file = File::create(&bundle_path).unwrap();
582 let mut zip = zip::ZipWriter::new(file);
583 let options = zip::write::SimpleFileOptions::default();
584 zip.start_file("manifest.json", options).unwrap();
585 zip.write_all(b"{ invalid json }").unwrap();
586 zip.finish().unwrap();
587
588 let result = BundleLoader::open(&bundle_path);
589
590 assert!(result.is_err());
591 }
592
593 #[test]
594 fn BundleLoader___list_files___returns_all_files() {
595 let temp_dir = TempDir::new().unwrap();
596 let bundle_path = create_test_bundle(&temp_dir);
597
598 let loader = BundleLoader::open(&bundle_path).unwrap();
599 let files = loader.list_files();
600
601 assert!(files.contains(&"manifest.json".to_string()));
602 assert!(files.contains(&"schema/messages.h".to_string()));
603 }
604
605 #[test]
606 fn BundleLoader___has_file___returns_true_for_existing() {
607 let temp_dir = TempDir::new().unwrap();
608 let bundle_path = create_test_bundle(&temp_dir);
609
610 let loader = BundleLoader::open(&bundle_path).unwrap();
611
612 assert!(loader.has_file("manifest.json"));
613 assert!(loader.has_file("schema/messages.h"));
614 assert!(!loader.has_file("nonexistent.txt"));
615 }
616
617 #[test]
618 fn BundleLoader___read_file___returns_contents() {
619 let temp_dir = TempDir::new().unwrap();
620 let bundle_path = create_test_bundle(&temp_dir);
621
622 let mut loader = BundleLoader::open(&bundle_path).unwrap();
623 let contents = loader.read_file_string("schema/messages.h").unwrap();
624
625 assert_eq!(contents, "// header");
626 }
627
628 #[test]
629 fn BundleLoader___read_file___missing_file___returns_error() {
630 let temp_dir = TempDir::new().unwrap();
631 let bundle_path = create_test_bundle(&temp_dir);
632
633 let mut loader = BundleLoader::open(&bundle_path).unwrap();
634 let result = loader.read_file("nonexistent.txt");
635
636 assert!(result.is_err());
637 let err = result.unwrap_err();
638 assert!(matches!(err, BundleError::MissingFile(_)));
639 }
640
641 #[test]
642 fn BundleLoader___read_file___returns_bytes() {
643 let temp_dir = TempDir::new().unwrap();
644 let bundle_path = create_test_bundle(&temp_dir);
645
646 let mut loader = BundleLoader::open(&bundle_path).unwrap();
647 let contents = loader.read_file("schema/messages.h").unwrap();
648
649 assert_eq!(contents, b"// header");
650 }
651
652 #[test]
653 fn BundleLoader___extract_library___verifies_checksum() {
654 let temp_dir = TempDir::new().unwrap();
655 let bundle_path = create_test_bundle(&temp_dir);
656 let extract_dir = temp_dir.path().join("extracted");
657
658 let mut loader = BundleLoader::open(&bundle_path).unwrap();
659 let lib_path = loader
660 .extract_library(Platform::LinuxX86_64, &extract_dir)
661 .unwrap();
662
663 assert!(lib_path.exists());
664 let contents = fs::read(&lib_path).unwrap();
665 assert_eq!(contents, b"fake library contents");
666 }
667
668 #[test]
669 fn BundleLoader___extract_library___unsupported_platform___returns_error() {
670 let temp_dir = TempDir::new().unwrap();
671 let bundle_path = create_test_bundle(&temp_dir);
672 let extract_dir = temp_dir.path().join("extracted");
673
674 let mut loader = BundleLoader::open(&bundle_path).unwrap();
675 let result = loader.extract_library(Platform::WindowsX86_64, &extract_dir);
676
677 assert!(result.is_err());
678 let err = result.unwrap_err();
679 assert!(matches!(err, BundleError::UnsupportedPlatform(_)));
680 }
681
682 #[test]
683 fn BundleLoader___extract_library___creates_output_directory() {
684 let temp_dir = TempDir::new().unwrap();
685 let bundle_path = create_test_bundle(&temp_dir);
686 let extract_dir = temp_dir.path().join("deep").join("nested").join("dir");
687
688 let mut loader = BundleLoader::open(&bundle_path).unwrap();
689 let lib_path = loader
690 .extract_library(Platform::LinuxX86_64, &extract_dir)
691 .unwrap();
692
693 assert!(extract_dir.exists());
694 assert!(lib_path.exists());
695 }
696
697 #[test]
698 fn BundleLoader___multi_platform___extract_each_platform() {
699 let temp_dir = TempDir::new().unwrap();
700 let bundle_path = create_multi_platform_bundle(&temp_dir);
701
702 let mut loader = BundleLoader::open(&bundle_path).unwrap();
703
704 assert!(loader.manifest().supports_platform(Platform::LinuxX86_64));
706 assert!(loader.manifest().supports_platform(Platform::DarwinAarch64));
707 assert!(loader.manifest().supports_platform(Platform::WindowsX86_64));
708
709 let linux_dir = temp_dir.path().join("linux");
711 let linux_lib = loader
712 .extract_library(Platform::LinuxX86_64, &linux_dir)
713 .unwrap();
714 assert_eq!(fs::read(&linux_lib).unwrap(), b"linux library");
715
716 let macos_dir = temp_dir.path().join("macos");
718 let macos_lib = loader
719 .extract_library(Platform::DarwinAarch64, &macos_dir)
720 .unwrap();
721 assert_eq!(fs::read(&macos_lib).unwrap(), b"macos library");
722
723 let windows_dir = temp_dir.path().join("windows");
725 let windows_lib = loader
726 .extract_library(Platform::WindowsX86_64, &windows_dir)
727 .unwrap();
728 assert_eq!(fs::read(&windows_lib).unwrap(), b"windows library");
729 }
730
731 #[test]
732 fn BundleLoader___supports_current_platform___returns_correct_value() {
733 let temp_dir = TempDir::new().unwrap();
734 let bundle_path = create_test_bundle(&temp_dir);
735
736 let loader = BundleLoader::open(&bundle_path).unwrap();
737
738 if Platform::current() == Some(Platform::LinuxX86_64) {
741 assert!(loader.supports_current_platform());
742 }
743 }
744
745 #[test]
746 fn BundleLoader___current_platform_info___returns_info_when_supported() {
747 let temp_dir = TempDir::new().unwrap();
748 let bundle_path = create_test_bundle(&temp_dir);
749
750 let loader = BundleLoader::open(&bundle_path).unwrap();
751
752 if Platform::current() == Some(Platform::LinuxX86_64) {
753 let info = loader.current_platform_info();
754 assert!(info.is_some());
755 let release = info.unwrap().release().unwrap();
756 assert!(release.library.contains("libtest.so"));
757 }
758 }
759
760 #[test]
761 fn roundtrip___create_and_load___preserves_all_data() {
762 let temp_dir = TempDir::new().unwrap();
763 let bundle_path = temp_dir.path().join("roundtrip.rbp");
764
765 let lib_path = temp_dir.path().join("libplugin.so");
767 let lib_contents = b"roundtrip test library";
768 fs::write(&lib_path, lib_contents).unwrap();
769
770 let mut manifest = Manifest::new("roundtrip-plugin", "3.2.1");
772 manifest.plugin.description = Some("A test plugin for roundtrip".to_string());
773 manifest.plugin.authors = vec!["Author One".to_string(), "Author Two".to_string()];
774 manifest.plugin.license = Some("MIT".to_string());
775
776 BundleBuilder::new(manifest)
777 .add_library(Platform::LinuxX86_64, &lib_path)
778 .unwrap()
779 .add_bytes("docs/README.md", b"# Documentation".to_vec())
780 .write(&bundle_path)
781 .unwrap();
782
783 let mut loader = BundleLoader::open(&bundle_path).unwrap();
785
786 assert_eq!(loader.manifest().plugin.name, "roundtrip-plugin");
787 assert_eq!(loader.manifest().plugin.version, "3.2.1");
788 assert_eq!(
789 loader.manifest().plugin.description,
790 Some("A test plugin for roundtrip".to_string())
791 );
792 assert_eq!(loader.manifest().plugin.authors.len(), 2);
793 assert_eq!(loader.manifest().plugin.license, Some("MIT".to_string()));
794
795 let platform_info = loader
797 .manifest()
798 .get_platform(Platform::LinuxX86_64)
799 .unwrap();
800 let release = platform_info.release().unwrap();
801 let expected_checksum = format!("sha256:{}", compute_sha256(lib_contents));
802 assert_eq!(release.checksum, expected_checksum);
803
804 let extract_dir = temp_dir.path().join("extract");
806 let extracted = loader
807 .extract_library(Platform::LinuxX86_64, &extract_dir)
808 .unwrap();
809 assert_eq!(fs::read(&extracted).unwrap(), lib_contents);
810 }
811
812 #[test]
813 fn roundtrip___bundle_with_schemas___preserves_schema_info() {
814 let temp_dir = TempDir::new().unwrap();
815 let bundle_path = temp_dir.path().join("schema-bundle.rbp");
816
817 let lib_path = temp_dir.path().join("libtest.so");
818 let header_path = temp_dir.path().join("messages.h");
819 let json_path = temp_dir.path().join("schema.json");
820 fs::write(&lib_path, b"lib").unwrap();
821 fs::write(&header_path, b"#pragma once").unwrap();
822 fs::write(&json_path, b"{}").unwrap();
823
824 let manifest = Manifest::new("schema-plugin", "1.0.0");
825 BundleBuilder::new(manifest)
826 .add_library(Platform::LinuxX86_64, &lib_path)
827 .unwrap()
828 .add_schema_file(&header_path, "messages.h")
829 .unwrap()
830 .add_schema_file(&json_path, "schema.json")
831 .unwrap()
832 .write(&bundle_path)
833 .unwrap();
834
835 let mut loader = BundleLoader::open(&bundle_path).unwrap();
836
837 assert_eq!(loader.manifest().schemas.len(), 2);
839 assert!(loader.manifest().schemas.contains_key("messages.h"));
840 assert!(loader.manifest().schemas.contains_key("schema.json"));
841
842 assert!(loader.has_file("schema/messages.h"));
844 assert!(loader.has_file("schema/schema.json"));
845 assert_eq!(
846 loader.read_file_string("schema/messages.h").unwrap(),
847 "#pragma once"
848 );
849 }
850}