use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
tables::{FileAttributes, FileRaw, TableDataOwned, TableId},
token::Token,
},
Error, Result,
};
#[derive(Debug, Clone, Default)]
pub struct FileBuilder {
name: Option<String>,
flags: u32,
hash_value: Option<Vec<u8>>,
}
impl FileBuilder {
#[must_use]
pub fn new() -> Self {
Self {
name: None,
flags: FileAttributes::CONTAINS_META_DATA, hash_value: None,
}
}
#[must_use]
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn flags(mut self, flags: u32) -> Self {
self.flags = flags;
self
}
#[must_use]
pub fn contains_metadata(mut self) -> Self {
self.flags |= FileAttributes::CONTAINS_META_DATA;
self.flags &= !FileAttributes::CONTAINS_NO_META_DATA;
self
}
#[must_use]
pub fn contains_no_metadata(mut self) -> Self {
self.flags |= FileAttributes::CONTAINS_NO_META_DATA;
self.flags &= !FileAttributes::CONTAINS_META_DATA;
self
}
#[must_use]
pub fn hash_value(mut self, hash: &[u8]) -> Self {
self.hash_value = Some(hash.to_vec());
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let name = self.name.ok_or_else(|| {
Error::ModificationInvalid("File name is required for File".to_string())
})?;
if name.is_empty() {
return Err(Error::ModificationInvalid(
"File name cannot be empty for File".to_string(),
));
}
let name_index = assembly.string_get_or_add(&name)?.placeholder();
let hash_value_index = if let Some(hash) = self.hash_value {
if hash.is_empty() {
0
} else {
assembly.blob_add(&hash)?.placeholder()
}
} else {
0
};
let file = FileRaw {
rid: 0,
token: Token::new(0),
offset: 0,
flags: self.flags,
name: name_index,
hash_value: hash_value_index,
};
assembly.table_row_add(TableId::File, TableDataOwned::File(file))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::ChangeRefKind, metadata::tables::FileAttributes,
test::factories::table::assemblyref::get_test_assembly,
};
#[test]
fn test_file_builder_basic() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = FileBuilder::new()
.name("MyModule.netmodule")
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::File));
Ok(())
}
#[test]
fn test_file_builder_default() -> Result<()> {
let builder = FileBuilder::default();
assert!(builder.name.is_none());
assert_eq!(builder.flags, FileAttributes::CONTAINS_META_DATA);
assert!(builder.hash_value.is_none());
Ok(())
}
#[test]
fn test_file_builder_missing_name() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = FileBuilder::new().contains_metadata().build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("File name is required"));
Ok(())
}
#[test]
fn test_file_builder_empty_name() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = FileBuilder::new().name("").build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("File name cannot be empty"));
Ok(())
}
#[test]
fn test_file_builder_contains_metadata() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = FileBuilder::new()
.name("Module.netmodule")
.contains_metadata()
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::File));
Ok(())
}
#[test]
fn test_file_builder_contains_no_metadata() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = FileBuilder::new()
.name("Resources.resources")
.contains_no_metadata()
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::File));
Ok(())
}
#[test]
fn test_file_builder_with_hash_value() -> Result<()> {
let mut assembly = get_test_assembly()?;
let hash = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
let ref_ = FileBuilder::new()
.name("HashedFile.dll")
.hash_value(&hash)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::File));
Ok(())
}
#[test]
fn test_file_builder_with_flags() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = FileBuilder::new()
.name("CustomFile.data")
.flags(FileAttributes::CONTAINS_NO_META_DATA)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::File));
Ok(())
}
#[test]
fn test_file_builder_multiple_files() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref1 = FileBuilder::new()
.name("Module1.netmodule")
.contains_metadata()
.build(&mut assembly)?;
let ref2 = FileBuilder::new()
.name("Resources.resources")
.contains_no_metadata()
.build(&mut assembly)?;
assert!(!std::sync::Arc::ptr_eq(&ref1, &ref2));
assert_eq!(ref1.kind(), ChangeRefKind::TableRow(TableId::File));
assert_eq!(ref2.kind(), ChangeRefKind::TableRow(TableId::File));
Ok(())
}
#[test]
fn test_file_builder_comprehensive() -> Result<()> {
let mut assembly = get_test_assembly()?;
let hash = vec![0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE];
let ref_ = FileBuilder::new()
.name("ComprehensiveModule.netmodule")
.contains_metadata()
.hash_value(&hash)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::File));
Ok(())
}
#[test]
fn test_file_builder_fluent_api() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = FileBuilder::new()
.name("FluentFile.resources")
.contains_no_metadata()
.hash_value(&[0x11, 0x22, 0x33, 0x44])
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::File));
Ok(())
}
#[test]
fn test_file_builder_clone() {
let builder1 = FileBuilder::new().name("CloneTest.dll").contains_metadata();
let builder2 = builder1.clone();
assert_eq!(builder1.name, builder2.name);
assert_eq!(builder1.flags, builder2.flags);
assert_eq!(builder1.hash_value, builder2.hash_value);
}
#[test]
fn test_file_builder_debug() {
let builder = FileBuilder::new().name("DebugFile.netmodule");
let debug_str = format!("{builder:?}");
assert!(debug_str.contains("FileBuilder"));
assert!(debug_str.contains("DebugFile.netmodule"));
}
#[test]
fn test_file_builder_empty_hash() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = FileBuilder::new()
.name("NoHashFile.dll")
.hash_value(&[]) .build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::File));
Ok(())
}
}