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 pub fn extract_library_for_current_platform<P: AsRef<Path>>(
198 &mut self,
199 output_dir: P,
200 ) -> BundleResult<PathBuf> {
201 let platform = Platform::current().ok_or_else(|| {
202 BundleError::UnsupportedPlatform("Current platform is not supported".to_string())
203 })?;
204
205 self.extract_library(platform, output_dir)
206 }
207
208 pub fn read_file(&mut self, path: &str) -> BundleResult<Vec<u8>> {
210 let mut file = self
211 .archive
212 .by_name(path)
213 .map_err(|_| BundleError::MissingFile(format!("File not found in bundle: {path}")))?;
214
215 let mut contents = Vec::new();
216 file.read_to_end(&mut contents)?;
217 Ok(contents)
218 }
219
220 pub fn read_file_string(&mut self, path: &str) -> BundleResult<String> {
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 = String::new();
228 file.read_to_string(&mut contents)?;
229 Ok(contents)
230 }
231
232 #[must_use]
234 pub fn list_files(&self) -> Vec<String> {
235 (0..self.archive.len())
236 .filter_map(|i| self.archive.name_for_index(i).map(String::from))
237 .collect()
238 }
239
240 #[must_use]
242 pub fn has_file(&self, path: &str) -> bool {
243 self.archive.index_for_name(path).is_some()
244 }
245
246 #[must_use]
248 pub fn has_signatures(&self) -> bool {
249 self.has_file("manifest.json.minisig")
250 }
251
252 #[must_use]
254 pub fn public_key(&self) -> Option<&str> {
255 self.manifest.public_key.as_deref()
256 }
257
258 pub fn verify_manifest_signature(&mut self) -> BundleResult<()> {
263 self.verify_manifest_signature_with_key(None)
264 }
265
266 pub fn verify_manifest_signature_with_key(
272 &mut self,
273 public_key_override: Option<&str>,
274 ) -> BundleResult<()> {
275 let public_key = public_key_override
277 .map(String::from)
278 .or_else(|| self.manifest.public_key.clone())
279 .ok_or(BundleError::NoPublicKey)?;
280
281 let manifest_data = self.read_file(MANIFEST_FILE)?;
283
284 let sig_path = format!("{MANIFEST_FILE}.minisig");
286 let sig_data = self.read_file(&sig_path)?;
287 let signature = String::from_utf8(sig_data).map_err(|e| {
288 BundleError::SignatureVerificationFailed(format!("Invalid signature encoding: {e}"))
289 })?;
290
291 verify_minisign_signature(&public_key, &manifest_data, &signature)
293 }
294
295 pub fn verify_library_signature(
302 &mut self,
303 library_path: &str,
304 library_data: &[u8],
305 ) -> BundleResult<()> {
306 self.verify_library_signature_with_key(library_path, library_data, None)
307 }
308
309 pub fn verify_library_signature_with_key(
311 &mut self,
312 library_path: &str,
313 library_data: &[u8],
314 public_key_override: Option<&str>,
315 ) -> BundleResult<()> {
316 let public_key = public_key_override
318 .map(String::from)
319 .or_else(|| self.manifest.public_key.clone())
320 .ok_or(BundleError::NoPublicKey)?;
321
322 let sig_path = format!("{library_path}.minisig");
324 let sig_data = self.read_file(&sig_path)?;
325 let signature = String::from_utf8(sig_data).map_err(|e| {
326 BundleError::SignatureVerificationFailed(format!("Invalid signature encoding: {e}"))
327 })?;
328
329 verify_minisign_signature(&public_key, library_data, &signature)
331 }
332
333 pub fn extract_library_verified<P: AsRef<Path>>(
344 &mut self,
345 platform: Platform,
346 output_dir: P,
347 verify_signature: bool,
348 public_key_override: Option<&str>,
349 ) -> BundleResult<PathBuf> {
350 if verify_signature {
352 self.verify_manifest_signature_with_key(public_key_override)?;
353 }
354
355 let output_dir = output_dir.as_ref();
356
357 let platform_info = self.manifest.get_platform(platform).ok_or_else(|| {
359 BundleError::UnsupportedPlatform(format!(
360 "Platform {} not found in bundle",
361 platform.as_str()
362 ))
363 })?;
364
365 let variant_info = platform_info
367 .release()
368 .ok_or_else(|| BundleError::VariantNotFound {
369 platform: platform.as_str().to_string(),
370 variant: "release".to_string(),
371 })?;
372
373 let library_path = variant_info.library.clone();
374 let expected_checksum = variant_info.checksum.clone();
375
376 let contents = self.read_file(&library_path)?;
378
379 if !verify_sha256(&contents, &expected_checksum) {
381 let actual = crate::builder::compute_sha256(&contents);
382 return Err(BundleError::ChecksumMismatch {
383 path: library_path,
384 expected: expected_checksum,
385 actual: format!("sha256:{actual}"),
386 });
387 }
388
389 if verify_signature {
391 self.verify_library_signature_with_key(&library_path, &contents, public_key_override)?;
392 }
393
394 fs::create_dir_all(output_dir)?;
396
397 let file_name = Path::new(&library_path)
399 .file_name()
400 .ok_or_else(|| BundleError::InvalidManifest("Invalid library path".to_string()))?;
401
402 let output_path = output_dir.join(file_name);
403
404 fs::write(&output_path, &contents)?;
406
407 #[cfg(unix)]
409 {
410 use std::os::unix::fs::PermissionsExt;
411 let mut perms = fs::metadata(&output_path)?.permissions();
412 perms.set_mode(0o755);
413 fs::set_permissions(&output_path, perms)?;
414 }
415
416 Ok(output_path)
417 }
418}
419
420fn verify_minisign_signature(
428 public_key_base64: &str,
429 data: &[u8],
430 signature_content: &str,
431) -> BundleResult<()> {
432 use minisign::{PublicKey, SignatureBox};
433
434 let public_key = PublicKey::from_base64(public_key_base64).map_err(|e| {
436 BundleError::SignatureVerificationFailed(format!("Invalid public key: {e}"))
437 })?;
438
439 let signature_box = SignatureBox::from_string(signature_content).map_err(|e| {
441 BundleError::SignatureVerificationFailed(format!("Invalid signature format: {e}"))
442 })?;
443
444 let mut data_reader = std::io::Cursor::new(data);
446 minisign::verify(
447 &public_key,
448 &signature_box,
449 &mut data_reader,
450 true,
451 false,
452 false,
453 )
454 .map_err(|e| {
455 BundleError::SignatureVerificationFailed(format!("Signature verification failed: {e}"))
456 })?;
457
458 Ok(())
459}
460
461#[cfg(test)]
462mod tests {
463 #![allow(non_snake_case)]
464
465 use super::*;
466 use crate::builder::{BundleBuilder, compute_sha256};
467 use std::io::Write;
468 use tempfile::TempDir;
469
470 fn create_test_bundle(temp_dir: &TempDir) -> PathBuf {
471 let bundle_path = temp_dir.path().join("test.rbp");
472
473 let lib_path = temp_dir.path().join("libtest.so");
475 fs::write(&lib_path, b"fake library contents").unwrap();
476
477 let manifest = Manifest::new("test-plugin", "1.0.0");
479 BundleBuilder::new(manifest)
480 .add_library(Platform::LinuxX86_64, &lib_path)
481 .unwrap()
482 .add_bytes("schema/messages.h", b"// header".to_vec())
483 .write(&bundle_path)
484 .unwrap();
485
486 bundle_path
487 }
488
489 fn create_multi_platform_bundle(temp_dir: &TempDir) -> PathBuf {
490 let bundle_path = temp_dir.path().join("multi.rbp");
491
492 let linux_lib = temp_dir.path().join("libtest.so");
493 let macos_lib = temp_dir.path().join("libtest.dylib");
494 let windows_lib = temp_dir.path().join("test.dll");
495 fs::write(&linux_lib, b"linux library").unwrap();
496 fs::write(&macos_lib, b"macos library").unwrap();
497 fs::write(&windows_lib, b"windows library").unwrap();
498
499 let manifest = Manifest::new("multi-platform", "2.0.0");
500 BundleBuilder::new(manifest)
501 .add_library(Platform::LinuxX86_64, &linux_lib)
502 .unwrap()
503 .add_library(Platform::DarwinAarch64, &macos_lib)
504 .unwrap()
505 .add_library(Platform::WindowsX86_64, &windows_lib)
506 .unwrap()
507 .write(&bundle_path)
508 .unwrap();
509
510 bundle_path
511 }
512
513 #[test]
514 fn BundleLoader___open___reads_manifest() {
515 let temp_dir = TempDir::new().unwrap();
516 let bundle_path = create_test_bundle(&temp_dir);
517
518 let loader = BundleLoader::open(&bundle_path).unwrap();
519
520 assert_eq!(loader.manifest().plugin.name, "test-plugin");
521 assert_eq!(loader.manifest().plugin.version, "1.0.0");
522 }
523
524 #[test]
525 fn BundleLoader___open___nonexistent_file___returns_error() {
526 let result = BundleLoader::open("/nonexistent/bundle.rbp");
527
528 assert!(result.is_err());
529 }
530
531 #[test]
532 fn BundleLoader___open___not_a_zip___returns_error() {
533 let temp_dir = TempDir::new().unwrap();
534 let fake_bundle = temp_dir.path().join("fake.rbp");
535 fs::write(&fake_bundle, b"not a zip file").unwrap();
536
537 let result = BundleLoader::open(&fake_bundle);
538
539 assert!(result.is_err());
540 }
541
542 #[test]
543 fn BundleLoader___open___missing_manifest___returns_error() {
544 let temp_dir = TempDir::new().unwrap();
545 let bundle_path = temp_dir.path().join("no-manifest.rbp");
546
547 let file = File::create(&bundle_path).unwrap();
549 let mut zip = zip::ZipWriter::new(file);
550 let options = zip::write::SimpleFileOptions::default();
551 zip.start_file("some-file.txt", options).unwrap();
552 zip.write_all(b"content").unwrap();
553 zip.finish().unwrap();
554
555 let result = BundleLoader::open(&bundle_path);
556
557 assert!(result.is_err());
558 let err = result.unwrap_err();
559 assert!(matches!(err, BundleError::MissingFile(_)));
560 assert!(err.to_string().contains("manifest.json"));
561 }
562
563 #[test]
564 fn BundleLoader___open___invalid_manifest_json___returns_error() {
565 let temp_dir = TempDir::new().unwrap();
566 let bundle_path = temp_dir.path().join("bad-manifest.rbp");
567
568 let file = File::create(&bundle_path).unwrap();
570 let mut zip = zip::ZipWriter::new(file);
571 let options = zip::write::SimpleFileOptions::default();
572 zip.start_file("manifest.json", options).unwrap();
573 zip.write_all(b"{ invalid json }").unwrap();
574 zip.finish().unwrap();
575
576 let result = BundleLoader::open(&bundle_path);
577
578 assert!(result.is_err());
579 }
580
581 #[test]
582 fn BundleLoader___list_files___returns_all_files() {
583 let temp_dir = TempDir::new().unwrap();
584 let bundle_path = create_test_bundle(&temp_dir);
585
586 let loader = BundleLoader::open(&bundle_path).unwrap();
587 let files = loader.list_files();
588
589 assert!(files.contains(&"manifest.json".to_string()));
590 assert!(files.contains(&"schema/messages.h".to_string()));
591 }
592
593 #[test]
594 fn BundleLoader___has_file___returns_true_for_existing() {
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
600 assert!(loader.has_file("manifest.json"));
601 assert!(loader.has_file("schema/messages.h"));
602 assert!(!loader.has_file("nonexistent.txt"));
603 }
604
605 #[test]
606 fn BundleLoader___read_file___returns_contents() {
607 let temp_dir = TempDir::new().unwrap();
608 let bundle_path = create_test_bundle(&temp_dir);
609
610 let mut loader = BundleLoader::open(&bundle_path).unwrap();
611 let contents = loader.read_file_string("schema/messages.h").unwrap();
612
613 assert_eq!(contents, "// header");
614 }
615
616 #[test]
617 fn BundleLoader___read_file___missing_file___returns_error() {
618 let temp_dir = TempDir::new().unwrap();
619 let bundle_path = create_test_bundle(&temp_dir);
620
621 let mut loader = BundleLoader::open(&bundle_path).unwrap();
622 let result = loader.read_file("nonexistent.txt");
623
624 assert!(result.is_err());
625 let err = result.unwrap_err();
626 assert!(matches!(err, BundleError::MissingFile(_)));
627 }
628
629 #[test]
630 fn BundleLoader___read_file___returns_bytes() {
631 let temp_dir = TempDir::new().unwrap();
632 let bundle_path = create_test_bundle(&temp_dir);
633
634 let mut loader = BundleLoader::open(&bundle_path).unwrap();
635 let contents = loader.read_file("schema/messages.h").unwrap();
636
637 assert_eq!(contents, b"// header");
638 }
639
640 #[test]
641 fn BundleLoader___extract_library___verifies_checksum() {
642 let temp_dir = TempDir::new().unwrap();
643 let bundle_path = create_test_bundle(&temp_dir);
644 let extract_dir = temp_dir.path().join("extracted");
645
646 let mut loader = BundleLoader::open(&bundle_path).unwrap();
647 let lib_path = loader
648 .extract_library(Platform::LinuxX86_64, &extract_dir)
649 .unwrap();
650
651 assert!(lib_path.exists());
652 let contents = fs::read(&lib_path).unwrap();
653 assert_eq!(contents, b"fake library contents");
654 }
655
656 #[test]
657 fn BundleLoader___extract_library___unsupported_platform___returns_error() {
658 let temp_dir = TempDir::new().unwrap();
659 let bundle_path = create_test_bundle(&temp_dir);
660 let extract_dir = temp_dir.path().join("extracted");
661
662 let mut loader = BundleLoader::open(&bundle_path).unwrap();
663 let result = loader.extract_library(Platform::WindowsX86_64, &extract_dir);
664
665 assert!(result.is_err());
666 let err = result.unwrap_err();
667 assert!(matches!(err, BundleError::UnsupportedPlatform(_)));
668 }
669
670 #[test]
671 fn BundleLoader___extract_library___creates_output_directory() {
672 let temp_dir = TempDir::new().unwrap();
673 let bundle_path = create_test_bundle(&temp_dir);
674 let extract_dir = temp_dir.path().join("deep").join("nested").join("dir");
675
676 let mut loader = BundleLoader::open(&bundle_path).unwrap();
677 let lib_path = loader
678 .extract_library(Platform::LinuxX86_64, &extract_dir)
679 .unwrap();
680
681 assert!(extract_dir.exists());
682 assert!(lib_path.exists());
683 }
684
685 #[test]
686 fn BundleLoader___multi_platform___extract_each_platform() {
687 let temp_dir = TempDir::new().unwrap();
688 let bundle_path = create_multi_platform_bundle(&temp_dir);
689
690 let mut loader = BundleLoader::open(&bundle_path).unwrap();
691
692 assert!(loader.manifest().supports_platform(Platform::LinuxX86_64));
694 assert!(loader.manifest().supports_platform(Platform::DarwinAarch64));
695 assert!(loader.manifest().supports_platform(Platform::WindowsX86_64));
696
697 let linux_dir = temp_dir.path().join("linux");
699 let linux_lib = loader
700 .extract_library(Platform::LinuxX86_64, &linux_dir)
701 .unwrap();
702 assert_eq!(fs::read(&linux_lib).unwrap(), b"linux library");
703
704 let macos_dir = temp_dir.path().join("macos");
706 let macos_lib = loader
707 .extract_library(Platform::DarwinAarch64, &macos_dir)
708 .unwrap();
709 assert_eq!(fs::read(&macos_lib).unwrap(), b"macos library");
710
711 let windows_dir = temp_dir.path().join("windows");
713 let windows_lib = loader
714 .extract_library(Platform::WindowsX86_64, &windows_dir)
715 .unwrap();
716 assert_eq!(fs::read(&windows_lib).unwrap(), b"windows library");
717 }
718
719 #[test]
720 fn BundleLoader___supports_current_platform___returns_correct_value() {
721 let temp_dir = TempDir::new().unwrap();
722 let bundle_path = create_test_bundle(&temp_dir);
723
724 let loader = BundleLoader::open(&bundle_path).unwrap();
725
726 if Platform::current() == Some(Platform::LinuxX86_64) {
729 assert!(loader.supports_current_platform());
730 }
731 }
732
733 #[test]
734 fn BundleLoader___current_platform_info___returns_info_when_supported() {
735 let temp_dir = TempDir::new().unwrap();
736 let bundle_path = create_test_bundle(&temp_dir);
737
738 let loader = BundleLoader::open(&bundle_path).unwrap();
739
740 if Platform::current() == Some(Platform::LinuxX86_64) {
741 let info = loader.current_platform_info();
742 assert!(info.is_some());
743 let release = info.unwrap().release().unwrap();
744 assert!(release.library.contains("libtest.so"));
745 }
746 }
747
748 #[test]
749 fn roundtrip___create_and_load___preserves_all_data() {
750 let temp_dir = TempDir::new().unwrap();
751 let bundle_path = temp_dir.path().join("roundtrip.rbp");
752
753 let lib_path = temp_dir.path().join("libplugin.so");
755 let lib_contents = b"roundtrip test library";
756 fs::write(&lib_path, lib_contents).unwrap();
757
758 let mut manifest = Manifest::new("roundtrip-plugin", "3.2.1");
760 manifest.plugin.description = Some("A test plugin for roundtrip".to_string());
761 manifest.plugin.authors = vec!["Author One".to_string(), "Author Two".to_string()];
762 manifest.plugin.license = Some("MIT".to_string());
763
764 BundleBuilder::new(manifest)
765 .add_library(Platform::LinuxX86_64, &lib_path)
766 .unwrap()
767 .add_bytes("docs/README.md", b"# Documentation".to_vec())
768 .write(&bundle_path)
769 .unwrap();
770
771 let mut loader = BundleLoader::open(&bundle_path).unwrap();
773
774 assert_eq!(loader.manifest().plugin.name, "roundtrip-plugin");
775 assert_eq!(loader.manifest().plugin.version, "3.2.1");
776 assert_eq!(
777 loader.manifest().plugin.description,
778 Some("A test plugin for roundtrip".to_string())
779 );
780 assert_eq!(loader.manifest().plugin.authors.len(), 2);
781 assert_eq!(loader.manifest().plugin.license, Some("MIT".to_string()));
782
783 let platform_info = loader
785 .manifest()
786 .get_platform(Platform::LinuxX86_64)
787 .unwrap();
788 let release = platform_info.release().unwrap();
789 let expected_checksum = format!("sha256:{}", compute_sha256(lib_contents));
790 assert_eq!(release.checksum, expected_checksum);
791
792 let extract_dir = temp_dir.path().join("extract");
794 let extracted = loader
795 .extract_library(Platform::LinuxX86_64, &extract_dir)
796 .unwrap();
797 assert_eq!(fs::read(&extracted).unwrap(), lib_contents);
798 }
799
800 #[test]
801 fn roundtrip___bundle_with_schemas___preserves_schema_info() {
802 let temp_dir = TempDir::new().unwrap();
803 let bundle_path = temp_dir.path().join("schema-bundle.rbp");
804
805 let lib_path = temp_dir.path().join("libtest.so");
806 let header_path = temp_dir.path().join("messages.h");
807 let json_path = temp_dir.path().join("schema.json");
808 fs::write(&lib_path, b"lib").unwrap();
809 fs::write(&header_path, b"#pragma once").unwrap();
810 fs::write(&json_path, b"{}").unwrap();
811
812 let manifest = Manifest::new("schema-plugin", "1.0.0");
813 BundleBuilder::new(manifest)
814 .add_library(Platform::LinuxX86_64, &lib_path)
815 .unwrap()
816 .add_schema_file(&header_path, "messages.h")
817 .unwrap()
818 .add_schema_file(&json_path, "schema.json")
819 .unwrap()
820 .write(&bundle_path)
821 .unwrap();
822
823 let mut loader = BundleLoader::open(&bundle_path).unwrap();
824
825 assert_eq!(loader.manifest().schemas.len(), 2);
827 assert!(loader.manifest().schemas.contains_key("messages.h"));
828 assert!(loader.manifest().schemas.contains_key("schema.json"));
829
830 assert!(loader.has_file("schema/messages.h"));
832 assert!(loader.has_file("schema/schema.json"));
833 assert_eq!(
834 loader.read_file_string("schema/messages.h").unwrap(),
835 "#pragma once"
836 );
837 }
838}