1use alloc::{
23 format,
24 string::{String, ToString},
25 sync::Arc,
26 vec::Vec,
27};
28
29use miden_assembly_syntax::{
30 Library,
31 ast::{AttributeSet, PathBuf},
32};
33use miden_core::{
34 Word,
35 serde::{
36 BudgetedReader, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
37 SliceReader,
38 },
39};
40
41use super::{ConstantExport, PackageId, ProcedureExport, TargetType, TypeExport};
42use crate::{Dependency, Package, PackageExport, PackageManifest, Section};
43
44const MAGIC_PACKAGE: &[u8; 5] = b"MASP\0";
49
50const VERSION: [u8; 3] = [4, 0, 0];
54
55const PACKAGE_BYTE_READ_BUDGET_MULTIPLIER: usize = 64;
60
61impl Serializable for Package {
65 fn write_into<W: ByteWriter>(&self, target: &mut W) {
66 target.write_bytes(MAGIC_PACKAGE);
68 target.write_bytes(&VERSION);
69
70 self.name.write_into(target);
72
73 self.version.to_string().write_into(target);
75
76 self.description.write_into(target);
78
79 target.write_u8(self.kind.into());
81
82 self.mast.write_into(target);
84
85 self.manifest.write_into(target);
87
88 target.write_usize(self.sections.len());
90 for section in self.sections.iter() {
91 section.write_into(target);
92 }
93 }
94}
95
96impl Deserializable for Package {
97 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
98 let magic: [u8; 5] = source.read_array()?;
100 if magic != *MAGIC_PACKAGE {
101 return Err(DeserializationError::InvalidValue(format!(
102 "invalid magic bytes. Expected '{MAGIC_PACKAGE:?}', got '{magic:?}'"
103 )));
104 }
105
106 let version: [u8; 3] = source.read_array()?;
107 if version != VERSION {
108 return Err(DeserializationError::InvalidValue(format!(
109 "unsupported version. Got '{version:?}', but only '{VERSION:?}' is supported"
110 )));
111 }
112
113 let name = PackageId::read_from(source)?;
115
116 let version = String::read_from(source)?
118 .parse::<crate::Version>()
119 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
120
121 let description = Option::<String>::read_from(source)?;
123
124 let kind_tag = source.read_u8()?;
126 let kind = TargetType::try_from(kind_tag)
127 .map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
128
129 let mast = Arc::new(Library::read_from(source)?);
131
132 let manifest = PackageManifest::read_from(source)?;
134
135 let sections = Vec::<Section>::read_from(source)?;
137
138 Ok(Self {
139 name,
140 version,
141 description,
142 kind,
143 mast,
144 manifest,
145 sections,
146 })
147 }
148
149 fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
150 let budget = bytes.len().saturating_mul(PACKAGE_BYTE_READ_BUDGET_MULTIPLIER);
151 let mut reader = BudgetedReader::new(SliceReader::new(bytes), budget);
152 Self::read_from(&mut reader)
153 }
154}
155
156#[cfg(feature = "serde")]
160impl serde::Serialize for PackageManifest {
161 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
162 where
163 S: serde::Serializer,
164 {
165 use alloc::collections::BTreeMap;
166
167 use miden_assembly_syntax::Path;
168 use serde::ser::SerializeStruct;
169
170 struct PackageExports<'a>(&'a BTreeMap<Arc<Path>, PackageExport>);
171
172 impl serde::Serialize for PackageExports<'_> {
173 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
174 where
175 S: serde::Serializer,
176 {
177 use serde::ser::SerializeSeq;
178
179 let mut serializer = serializer.serialize_seq(Some(self.0.len()))?;
180 for value in self.0.values() {
181 serializer.serialize_element(value)?;
182 }
183 serializer.end()
184 }
185 }
186
187 let mut serializer = serializer.serialize_struct("PackageManifest", 2)?;
188 serializer.serialize_field("exports", &PackageExports(&self.exports))?;
189 serializer.serialize_field("dependencies", &self.dependencies)?;
190 serializer.end()
191 }
192}
193
194#[cfg(feature = "serde")]
195impl<'de> serde::Deserialize<'de> for PackageManifest {
196 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
197 where
198 D: serde::Deserializer<'de>,
199 {
200 #[derive(serde::Deserialize)]
201 #[serde(field_identifier, rename_all = "lowercase")]
202 enum Field {
203 Exports,
204 Dependencies,
205 }
206
207 struct PackageManifestVisitor;
208
209 impl<'de> serde::de::Visitor<'de> for PackageManifestVisitor {
210 type Value = PackageManifest;
211
212 fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
213 formatter.write_str("struct PackageManifest")
214 }
215
216 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
217 where
218 A: serde::de::SeqAccess<'de>,
219 {
220 let exports = seq
221 .next_element::<Vec<PackageExport>>()?
222 .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
223 let dependencies = seq
224 .next_element::<Vec<Dependency>>()?
225 .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
226 PackageManifest::new(exports)
227 .and_then(|manifest| manifest.with_dependencies(dependencies))
228 .map_err(serde::de::Error::custom)
229 }
230
231 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
232 where
233 A: serde::de::MapAccess<'de>,
234 {
235 let mut exports = None;
236 let mut dependencies = None;
237 while let Some(key) = map.next_key()? {
238 match key {
239 Field::Exports => {
240 if exports.is_some() {
241 return Err(serde::de::Error::duplicate_field("exports"));
242 }
243 exports = Some(map.next_value::<Vec<PackageExport>>()?);
244 },
245 Field::Dependencies => {
246 if dependencies.is_some() {
247 return Err(serde::de::Error::duplicate_field("dependencies"));
248 }
249 dependencies = Some(map.next_value::<Vec<Dependency>>()?);
250 },
251 }
252 }
253 let exports = exports.ok_or_else(|| serde::de::Error::missing_field("exports"))?;
254 let dependencies =
255 dependencies.ok_or_else(|| serde::de::Error::missing_field("dependencies"))?;
256 PackageManifest::new(exports)
257 .and_then(|manifest| manifest.with_dependencies(dependencies))
258 .map_err(serde::de::Error::custom)
259 }
260 }
261
262 deserializer.deserialize_struct(
263 "PackageManifest",
264 &["exports", "dependencies"],
265 PackageManifestVisitor,
266 )
267 }
268}
269
270impl Serializable for PackageManifest {
271 fn write_into<W: ByteWriter>(&self, target: &mut W) {
272 target.write_usize(self.num_exports());
274 for export in self.exports() {
275 export.write_into(target);
276 }
277
278 target.write_usize(self.num_dependencies());
280 for dep in self.dependencies() {
281 dep.write_into(target);
282 }
283 }
284}
285
286impl Deserializable for PackageManifest {
287 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
288 let exports_len = source.read_usize()?;
290 let exports = source.read_many_iter(exports_len)?.collect::<Result<Vec<_>, _>>()?;
291
292 let dependencies = Vec::<Dependency>::read_from(source)?;
294
295 PackageManifest::new(exports)
296 .and_then(|manifest| manifest.with_dependencies(dependencies))
297 .map_err(|error| DeserializationError::InvalidValue(error.to_string()))
298 }
299}
300
301impl Serializable for PackageExport {
304 fn write_into<W: ByteWriter>(&self, target: &mut W) {
305 target.write_u8(self.tag());
306 match self {
307 Self::Procedure(export) => export.write_into(target),
308 Self::Constant(export) => export.write_into(target),
309 Self::Type(export) => export.write_into(target),
310 }
311 }
312}
313
314impl Deserializable for PackageExport {
315 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
316 match source.read_u8()? {
317 1 => ProcedureExport::read_from(source).map(Self::Procedure),
318 2 => ConstantExport::read_from(source).map(Self::Constant),
319 3 => TypeExport::read_from(source).map(Self::Type),
320 invalid => Err(DeserializationError::InvalidValue(format!(
321 "unexpected PackageExport tag: '{invalid}'"
322 ))),
323 }
324 }
325}
326
327impl Serializable for ProcedureExport {
328 fn write_into<W: ByteWriter>(&self, target: &mut W) {
329 self.path.write_into(target);
330 self.digest.write_into(target);
331 match self.signature.as_ref() {
332 Some(sig) => {
333 target.write_bool(true);
334 sig.write_into(target);
335 },
336 None => {
337 target.write_bool(false);
338 },
339 }
340 self.attributes.write_into(target);
341 }
342}
343
344impl Deserializable for ProcedureExport {
345 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
346 use miden_assembly_syntax::ast::types::FunctionType;
347 let path = PathBuf::read_from(source)?.into_boxed_path().into();
348 let digest = Word::read_from(source)?;
349 let signature = if source.read_bool()? {
350 Some(FunctionType::read_from(source)?)
351 } else {
352 None
353 };
354 let attributes = AttributeSet::read_from(source)?;
355 Ok(Self { path, digest, signature, attributes })
356 }
357}
358
359impl Serializable for ConstantExport {
360 fn write_into<W: ByteWriter>(&self, target: &mut W) {
361 self.path.write_into(target);
362 self.value.write_into(target);
363 }
364}
365
366impl Deserializable for ConstantExport {
367 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
368 let path = PathBuf::read_from(source)?.into_boxed_path().into();
369 let value = miden_assembly_syntax::ast::ConstantValue::read_from(source)?;
370 Ok(Self { path, value })
371 }
372}
373
374impl Serializable for TypeExport {
375 fn write_into<W: ByteWriter>(&self, target: &mut W) {
376 self.path.write_into(target);
377 self.ty.write_into(target);
378 }
379}
380
381impl Deserializable for TypeExport {
382 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
383 use miden_assembly_syntax::ast::types::Type;
384 let path = PathBuf::read_from(source)?.into_boxed_path().into();
385 let ty = Type::read_from(source)?;
386 Ok(Self { path, ty })
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use alloc::{
393 collections::BTreeMap,
394 string::{String, ToString},
395 sync::Arc,
396 vec,
397 vec::Vec,
398 };
399
400 use miden_assembly_syntax::{
401 Library,
402 ast::{AttributeSet, Path as AstPath, PathBuf},
403 library::{LibraryExport, ProcedureExport as LibraryProcedureExport},
404 };
405 use miden_core::{
406 Word,
407 mast::{BasicBlockNodeBuilder, MastForest, MastForestContributor, MastNodeExt, MastNodeId},
408 operations::Operation,
409 serde::{
410 BudgetedReader, ByteWriter, Deserializable, DeserializationError, Serializable,
411 SliceReader,
412 },
413 };
414 #[cfg(feature = "serde")]
415 use serde_json::{json, to_value};
416
417 use super::{
418 MAGIC_PACKAGE, PACKAGE_BYTE_READ_BUDGET_MULTIPLIER, Package, PackageExport,
419 PackageManifest, Section, VERSION,
420 };
421 use crate::{
422 Dependency, ManifestValidationError, PackageId, SectionId, TargetType,
423 package::manifest::ProcedureExport as PackageProcedureExport,
424 };
425
426 fn build_forest() -> (MastForest, MastNodeId) {
427 let mut forest = MastForest::new();
428 let node_id = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new())
429 .add_to_forest(&mut forest)
430 .expect("failed to build basic block");
431 forest.make_root(node_id);
432 (forest, node_id)
433 }
434
435 fn absolute_path(name: &str) -> Arc<AstPath> {
436 let path = PathBuf::new(name).expect("invalid path");
437 let path = path.as_path().to_absolute().into_owned();
438 Arc::from(path.into_boxed_path())
439 }
440
441 fn build_library() -> Arc<Library> {
442 let (forest, node_id) = build_forest();
443 let path = absolute_path("test::proc");
444 let export = LibraryProcedureExport::new(node_id, Arc::clone(&path));
445
446 let mut exports = BTreeMap::new();
447 exports.insert(path, LibraryExport::Procedure(export));
448
449 Arc::new(Library::new(Arc::new(forest), exports).expect("failed to build library"))
450 }
451
452 fn build_package() -> Package {
453 let library = build_library();
454 let path = absolute_path("test::proc");
455 let node_id = library.get_export_node_id(path.as_ref());
456 let digest = library.mast_forest()[node_id].digest();
457
458 let export = PackageExport::Procedure(PackageProcedureExport {
459 path: Arc::clone(&path),
460 digest,
461 signature: None,
462 attributes: AttributeSet::default(),
463 });
464
465 let manifest =
466 PackageManifest::new([export]).expect("test package manifest should be valid");
467
468 Package {
469 name: PackageId::from("test_pkg"),
470 version: crate::Version::new(0, 0, 0),
471 description: None,
472 kind: TargetType::Library,
473 mast: library,
474 manifest,
475 sections: Vec::new(),
476 }
477 }
478
479 fn build_dependency() -> Dependency {
480 Dependency {
481 name: PackageId::from("dep"),
482 kind: TargetType::Library,
483 version: crate::Version::new(1, 0, 0),
484 digest: Default::default(),
485 }
486 }
487
488 fn package_bytes_with_sections_count(count: usize) -> Vec<u8> {
489 let package = build_package();
490 let mut bytes = Vec::new();
491
492 bytes.write_bytes(MAGIC_PACKAGE);
493 bytes.write_bytes(&VERSION);
494 package.name.write_into(&mut bytes);
495 package.version.to_string().write_into(&mut bytes);
496 package.description.write_into(&mut bytes);
497 bytes.write_u8(package.kind.into());
498 package.mast.write_into(&mut bytes);
499 package.manifest.write_into(&mut bytes);
500 bytes.write_usize(count);
501
502 bytes
503 }
504
505 #[test]
506 fn package_content_digest_changes_when_identity_fields_change() {
507 let package = build_package();
508 let digest = package.content_digest();
509
510 let renamed = Package {
511 name: PackageId::from("renamed_pkg"),
512 ..package.clone()
513 };
514 assert_ne!(digest, renamed.content_digest());
515
516 let versioned = Package {
517 version: crate::Version::new(1, 2, 3),
518 ..package.clone()
519 };
520 assert_ne!(digest, versioned.content_digest());
521
522 let executable = Package { kind: TargetType::Executable, ..package };
523 assert_ne!(digest, executable.content_digest());
524 }
525
526 #[test]
527 fn package_content_digest_changes_when_manifest_changes() {
528 let package = build_package();
529 let digest = package.content_digest();
530
531 let mut with_dependency = package;
532 with_dependency
533 .manifest
534 .add_dependency(Dependency {
535 name: PackageId::from("dep_pkg"),
536 kind: TargetType::Library,
537 version: crate::Version::new(1, 0, 0),
538 digest: Word::from([1_u32, 2, 3, 4]),
539 })
540 .expect("test dependency should be unique");
541 assert_ne!(digest, with_dependency.content_digest());
542 }
543
544 #[test]
545 fn package_content_digest_changes_when_account_component_metadata_changes() {
546 let package = build_package();
547 let digest = package.content_digest();
548
549 let with_metadata = Package {
550 sections: vec![Section::new(SectionId::ACCOUNT_COMPONENT_METADATA, vec![1, 2, 3, 4])],
551 ..package.clone()
552 };
553 assert_ne!(digest, with_metadata.content_digest());
554
555 let with_different_metadata = Package {
556 sections: vec![Section::new(SectionId::ACCOUNT_COMPONENT_METADATA, vec![4, 3, 2, 1])],
557 ..package
558 };
559 assert_ne!(with_metadata.content_digest(), with_different_metadata.content_digest());
560 }
561
562 #[test]
563 fn package_content_digest_ignores_description_and_opaque_custom_sections_for_now() {
564 let package = build_package();
565 let digest = package.content_digest();
566
567 let described = Package {
568 description: Some(String::from("human-facing package description")),
569 ..package.clone()
570 };
571 assert_eq!(digest, described.content_digest());
572
573 let with_section = Package {
574 sections: vec![Section::new(
575 SectionId::custom("opaque").expect("valid custom section id"),
576 vec![1, 2, 3, 4],
577 )],
578 ..package
579 };
580 assert_eq!(digest, with_section.content_digest());
581 }
582
583 #[test]
584 fn package_manifest_rejects_over_budget_dependencies() {
585 let mut bytes = Vec::new();
586 bytes.write_usize(0);
587 bytes.write_usize(2);
588
589 let mut reader = BudgetedReader::new(SliceReader::new(&bytes), 2);
590 let err = PackageManifest::read_from(&mut reader).unwrap_err();
591 assert!(matches!(err, DeserializationError::InvalidValue(_)));
592 }
593
594 #[test]
595 fn package_rejects_over_budget_sections() {
596 let bytes = package_bytes_with_sections_count(2);
597 let mut reader = BudgetedReader::new(SliceReader::new(&bytes), bytes.len());
598 let err = Package::read_from(&mut reader).unwrap_err();
599 assert!(matches!(err, DeserializationError::InvalidValue(_)));
600 }
601
602 #[test]
603 fn package_read_from_bytes_rejects_fuzzed_oom_payload() {
604 let payload = [
607 0x4d, 0x41, 0x53, 0x50, 0x00, 0x04, 0x00, 0x00, 0x11, 0x74, 0x65, 0x73, 0x74, 0x5f,
608 0x70, 0x6b, 0x67, 0x0b, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x00, 0x00, 0x4d, 0x41, 0x53,
609 0x54, 0x00, 0x00, 0x00, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x17, 0x03, 0x22,
610 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
611 0x00, 0x00, 0x30, 0x2f, 0x08, 0x0a, 0x21, 0xa9, 0xb6, 0xf6, 0x1a, 0x52, 0x30, 0xc5,
612 0x64, 0xc7, 0xdb, 0x4d, 0x83, 0x0b, 0x32, 0x58, 0x89, 0x88, 0xb2, 0x78, 0x69, 0xbb,
613 0x23, 0xa6, 0x18, 0x9c, 0xc9, 0x35, 0x2d, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
614 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
615 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x00, 0x0c, 0x00, 0x3a, 0x3a, 0x74, 0x65, 0x73,
616 0x74, 0x3a, 0x3a, 0x70, 0x72, 0x6f, 0x63, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03,
617 0x0f, 0x03, 0x0f, 0x01, 0x00, 0x00, 0x17, 0x03, 0x22, 0x01, 0x00, 0x00, 0x00, 0x01,
618 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0xc9, 0x35, 0x2d, 0x01, 0x00, 0x03, 0x0f, 0x03,
619 0x0f, 0x01, 0x01, 0x01,
620 ];
621
622 let result = Package::read_from_bytes(&payload);
623 assert!(result.is_err());
624
625 let mut vec_payload = vec![0];
628 vec_payload.extend_from_slice(&1000u64.to_le_bytes());
629 let budget = vec_payload.len().saturating_mul(PACKAGE_BYTE_READ_BUDGET_MULTIPLIER);
630 let result = Vec::<Package>::read_from_bytes_with_budget(&vec_payload, budget);
631 assert!(result.is_err());
632
633 let mut option_payload = vec![1];
634 option_payload.extend_from_slice(&payload);
635 let budget = option_payload.len().saturating_mul(PACKAGE_BYTE_READ_BUDGET_MULTIPLIER);
636 let result = Option::<Package>::read_from_bytes_with_budget(&option_payload, budget);
637 assert!(result.is_err());
638 }
639
640 #[test]
641 fn package_manifest_new_rejects_duplicate_export_paths() {
642 let library = build_library();
643 let path = absolute_path("test::proc");
644 let node_id = library.get_export_node_id(path.as_ref());
645 let digest = library.mast_forest()[node_id].digest();
646 let export = PackageExport::Procedure(PackageProcedureExport {
647 path: path.clone(),
648 digest,
649 signature: None,
650 attributes: AttributeSet::default(),
651 });
652
653 let err = PackageManifest::new([export.clone(), export])
654 .expect_err("duplicate export paths should be rejected by constructors");
655 assert_eq!(err, ManifestValidationError::DuplicateExport(path));
656 }
657
658 #[test]
659 fn package_manifest_add_dependency_rejects_duplicate_dependencies() {
660 let mut manifest =
661 PackageManifest::new([]).expect("empty package manifest should be valid");
662 let dependency = build_dependency();
663
664 manifest
665 .add_dependency(dependency.clone())
666 .expect("first dependency should be accepted");
667 let err = manifest
668 .add_dependency(dependency)
669 .expect_err("duplicate dependencies should be rejected by helpers");
670 assert_eq!(err, ManifestValidationError::DuplicateDependency(PackageId::from("dep")));
671 }
672
673 #[test]
674 fn package_manifest_rejects_duplicate_export_paths() {
675 let library = build_library();
676 let path = absolute_path("test::proc");
677 let node_id = library.get_export_node_id(path.as_ref());
678 let digest = library.mast_forest()[node_id].digest();
679 let export = PackageExport::Procedure(PackageProcedureExport {
680 path,
681 digest,
682 signature: None,
683 attributes: AttributeSet::default(),
684 });
685
686 let mut bytes = Vec::new();
687 bytes.write_usize(2);
688 export.write_into(&mut bytes);
689 export.write_into(&mut bytes);
690 bytes.write_usize(0);
691
692 let mut reader = SliceReader::new(&bytes);
693 let err = PackageManifest::read_from(&mut reader)
694 .expect_err("duplicate export paths should be rejected during deserialization");
695 assert!(matches!(err, DeserializationError::InvalidValue(_)));
696 }
697
698 #[test]
699 fn package_manifest_rejects_duplicate_dependencies() {
700 let dependency = build_dependency();
701
702 let mut bytes = Vec::new();
703 bytes.write_usize(0);
704 bytes.write_usize(2);
705 dependency.write_into(&mut bytes);
706 dependency.write_into(&mut bytes);
707
708 let mut reader = SliceReader::new(&bytes);
709 let err = PackageManifest::read_from(&mut reader)
710 .expect_err("duplicate dependencies should be rejected during deserialization");
711 assert!(matches!(err, DeserializationError::InvalidValue(_)));
712 }
713
714 #[cfg(feature = "serde")]
715 #[test]
716 fn serde_package_manifest_rejects_duplicate_export_paths() {
717 let library = build_library();
718 let path = absolute_path("test::proc");
719 let node_id = library.get_export_node_id(path.as_ref());
720 let digest = library.mast_forest()[node_id].digest();
721 let export = PackageExport::Procedure(PackageProcedureExport {
722 path,
723 digest,
724 signature: None,
725 attributes: AttributeSet::default(),
726 });
727 let export = to_value(&export).expect("export should serialize");
728
729 let manifest = serde_json::to_string(&json!({
730 "exports": [export.clone(), export],
731 "dependencies": [],
732 }))
733 .expect("manifest should serialize to JSON");
734 let err = serde_json::from_str::<PackageManifest>(&manifest)
735 .expect_err("serde deserialization should reject duplicate export paths");
736 let message = err.to_string();
737 assert!(message.contains("duplicate export path"));
738 }
739
740 #[cfg(feature = "serde")]
741 #[test]
742 fn serde_package_manifest_rejects_duplicate_dependencies() {
743 let dependency = to_value(build_dependency()).expect("dependency should serialize");
744
745 let manifest = serde_json::to_string(&json!({
746 "exports": [],
747 "dependencies": [dependency.clone(), dependency],
748 }))
749 .expect("manifest should serialize to JSON");
750 let err = serde_json::from_str::<PackageManifest>(&manifest)
751 .expect_err("serde deserialization should reject duplicate dependencies");
752 let message = err.to_string();
753 assert!(message.contains("duplicate dependency"));
754 }
755}