use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
tables::{DocumentRaw, TableDataOwned, TableId},
token::Token,
},
Error, Result,
};
#[derive(Debug, Clone)]
pub struct DocumentBuilder {
name: Option<String>,
hash_algorithm: Option<[u8; 16]>,
hash: Option<Vec<u8>>,
language: Option<[u8; 16]>,
}
impl Default for DocumentBuilder {
fn default() -> Self {
Self::new()
}
}
impl DocumentBuilder {
#[must_use]
pub fn new() -> Self {
Self {
name: None,
hash_algorithm: None,
hash: None,
language: None,
}
}
#[must_use]
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn hash_algorithm(mut self, guid: &[u8; 16]) -> Self {
self.hash_algorithm = Some(*guid);
self
}
#[must_use]
pub fn sha1_hash_algorithm(mut self) -> Self {
self.hash_algorithm = Some([
0xff, 0x18, 0x16, 0xec, 0xaa, 0x5e, 0x4d, 0x10, 0x87, 0xf7, 0x6f, 0x49, 0x63, 0x83,
0x34, 0x60,
]);
self
}
#[must_use]
pub fn sha256_hash_algorithm(mut self) -> Self {
self.hash_algorithm = Some([
0x8b, 0x12, 0xd6, 0x2a, 0x37, 0x7a, 0x42, 0x8c, 0x9b, 0x8c, 0x41, 0x09, 0xc8, 0x5e,
0x29, 0xc6,
]);
self
}
#[must_use]
pub fn hash(mut self, hash_bytes: Vec<u8>) -> Self {
self.hash = Some(hash_bytes);
self
}
#[must_use]
pub fn language(mut self, guid: &[u8; 16]) -> Self {
self.language = Some(*guid);
self
}
#[must_use]
pub fn csharp_language(mut self) -> Self {
self.language = Some([
0x3f, 0x5f, 0x6f, 0x40, 0x15, 0x5c, 0x11, 0xd4, 0x95, 0x68, 0x00, 0x80, 0xc7, 0x05,
0x06, 0x26,
]);
self
}
#[must_use]
pub fn vb_language(mut self) -> Self {
self.language = Some([
0x3a, 0x12, 0xd0, 0xb8, 0xc2, 0x6c, 0x11, 0xd0, 0xb4, 0x42, 0x00, 0xa0, 0x24, 0x4a,
0x1d, 0xd2,
]);
self
}
#[must_use]
pub fn fsharp_language(mut self) -> Self {
self.language = Some([
0xab, 0x4f, 0x38, 0xc9, 0xb6, 0xe6, 0x43, 0xba, 0xbe, 0x3b, 0x58, 0x08, 0x0b, 0x2c,
0xcc, 0xe3,
]);
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let document_name = self.name.ok_or_else(|| {
Error::ModificationInvalid("Document name is required for Document".to_string())
})?;
if document_name.is_empty() {
return Err(Error::ModificationInvalid(
"Document name cannot be empty".to_string(),
));
}
let name_index = assembly.blob_add(document_name.as_bytes())?.placeholder();
let hash_algorithm_index = if let Some(guid) = self.hash_algorithm {
assembly.guid_add(&guid)?.placeholder()
} else {
0
};
let hash_index = if let Some(hash_bytes) = self.hash {
assembly.blob_add(&hash_bytes)?.placeholder()
} else {
0
};
let language_index = if let Some(guid) = self.language {
assembly.guid_add(&guid)?.placeholder()
} else {
0
};
let document = DocumentRaw {
rid: 0,
token: Token::new(0),
offset: 0,
name: name_index,
hash_algorithm: hash_algorithm_index,
hash: hash_index,
language: language_index,
};
assembly.table_row_add(TableId::Document, TableDataOwned::Document(document))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::ChangeRefKind, metadata::tables::TableId,
test::factories::table::assemblyref::get_test_assembly,
};
#[test]
fn test_document_builder_basic() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = DocumentBuilder::new()
.name("Program.cs")
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::Document));
Ok(())
}
#[test]
fn test_document_builder_default() -> Result<()> {
let builder = DocumentBuilder::default();
assert!(builder.name.is_none());
assert!(builder.hash_algorithm.is_none());
assert!(builder.hash.is_none());
assert!(builder.language.is_none());
Ok(())
}
#[test]
fn test_document_builder_missing_name() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = DocumentBuilder::new()
.csharp_language()
.build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Document name is required"));
Ok(())
}
#[test]
fn test_document_builder_empty_name() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = DocumentBuilder::new().name("").build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Document name cannot be empty"));
Ok(())
}
#[test]
fn test_document_builder_with_csharp_language() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = DocumentBuilder::new()
.name("Test.cs")
.csharp_language()
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::Document));
Ok(())
}
#[test]
fn test_document_builder_with_vb_language() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = DocumentBuilder::new()
.name("Test.vb")
.vb_language()
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::Document));
Ok(())
}
#[test]
fn test_document_builder_with_fsharp_language() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = DocumentBuilder::new()
.name("Test.fs")
.fsharp_language()
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::Document));
Ok(())
}
#[test]
fn test_document_builder_with_sha1_hash() -> Result<()> {
let mut assembly = get_test_assembly()?;
let hash_bytes = vec![0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0];
let ref_ = DocumentBuilder::new()
.name("Test.cs")
.sha1_hash_algorithm()
.hash(hash_bytes)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::Document));
Ok(())
}
#[test]
fn test_document_builder_with_sha256_hash() -> Result<()> {
let mut assembly = get_test_assembly()?;
let hash_bytes = vec![0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0];
let ref_ = DocumentBuilder::new()
.name("Test.cs")
.sha256_hash_algorithm()
.hash(hash_bytes)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::Document));
Ok(())
}
#[test]
fn test_document_builder_full_specification() -> Result<()> {
let mut assembly = get_test_assembly()?;
let hash_bytes = vec![0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0];
let ref_ = DocumentBuilder::new()
.name("MyProgram.cs")
.csharp_language()
.sha256_hash_algorithm()
.hash(hash_bytes)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::Document));
Ok(())
}
#[test]
fn test_document_builder_multiple_entries() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref1 = DocumentBuilder::new()
.name("File1.cs")
.csharp_language()
.build(&mut assembly)?;
let ref2 = DocumentBuilder::new()
.name("File2.vb")
.vb_language()
.build(&mut assembly)?;
assert!(!std::sync::Arc::ptr_eq(&ref1, &ref2));
assert_eq!(ref1.kind(), ChangeRefKind::TableRow(TableId::Document));
assert_eq!(ref2.kind(), ChangeRefKind::TableRow(TableId::Document));
Ok(())
}
#[test]
fn test_document_builder_custom_guid() -> Result<()> {
let mut assembly = get_test_assembly()?;
let custom_lang_guid = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
];
let custom_hash_guid = [
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e,
0x1f, 0x20,
];
let ref_ = DocumentBuilder::new()
.name("CustomDoc.txt")
.language(&custom_lang_guid)
.hash_algorithm(&custom_hash_guid)
.hash(vec![0x99, 0x88, 0x77, 0x66])
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::Document));
Ok(())
}
#[test]
fn test_document_builder_fluent_api() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = DocumentBuilder::new()
.name("FluentTest.cs")
.csharp_language()
.sha256_hash_algorithm()
.hash(vec![0xaa, 0xbb, 0xcc, 0xdd])
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::Document));
Ok(())
}
#[test]
fn test_document_builder_clone() {
let hash_bytes = vec![0x12, 0x34, 0x56, 0x78];
let builder1 = DocumentBuilder::new()
.name("Test.cs")
.csharp_language()
.hash(hash_bytes.clone());
let builder2 = builder1.clone();
assert_eq!(builder1.name, builder2.name);
assert_eq!(builder1.language, builder2.language);
assert_eq!(builder1.hash, builder2.hash);
}
#[test]
fn test_document_builder_debug() {
let builder = DocumentBuilder::new().name("Debug.cs").csharp_language();
let debug_str = format!("{builder:?}");
assert!(debug_str.contains("DocumentBuilder"));
}
}