use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
tables::{CodedIndex, CodedIndexType, CustomDebugInformationRaw, TableDataOwned, TableId},
token::Token,
},
Error, Result,
};
#[derive(Debug, Clone)]
pub struct CustomDebugInformationBuilder {
parent: Option<CodedIndex>,
kind: Option<u32>,
value: Option<Vec<u8>>,
}
impl CustomDebugInformationBuilder {
#[must_use]
pub fn new() -> Self {
Self {
parent: None,
kind: None,
value: None,
}
}
#[must_use]
pub fn parent(mut self, parent: CodedIndex) -> Self {
self.parent = Some(parent);
self
}
#[must_use]
pub fn kind(mut self, kind: u32) -> Self {
self.kind = Some(kind);
self
}
#[must_use]
pub fn value(mut self, value: &[u8]) -> Self {
self.value = Some(value.to_vec());
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let parent = self.parent.ok_or_else(|| {
Error::ModificationInvalid(
"Parent coded index is required for CustomDebugInformation".to_string(),
)
})?;
let kind = self.kind.ok_or_else(|| {
Error::ModificationInvalid(
"Kind GUID index is required for CustomDebugInformation".to_string(),
)
})?;
let value = self.value.ok_or_else(|| {
Error::ModificationInvalid(
"Value blob data is required for CustomDebugInformation".to_string(),
)
})?;
let valid_tables = CodedIndexType::HasCustomDebugInformation.tables();
if !valid_tables.contains(&parent.tag) {
return Err(Error::ModificationInvalid(format!(
"Invalid parent table {:?} for CustomDebugInformation. Must be a HasCustomDebugInformation coded index.",
parent.tag
)));
}
let value_index = if value.is_empty() {
0
} else {
assembly.blob_add(&value)?.placeholder()
};
let custom_debug_info = CustomDebugInformationRaw {
rid: 0,
token: Token::new(0),
offset: 0,
parent,
kind,
value: value_index,
};
assembly.table_row_add(
TableId::CustomDebugInformation,
TableDataOwned::CustomDebugInformation(custom_debug_info),
)
}
}
impl Default for CustomDebugInformationBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::ChangeRefKind, test::factories::table::assemblyref::get_test_assembly,
};
#[test]
fn test_customdebuginformation_builder_new() {
let builder = CustomDebugInformationBuilder::new();
assert!(builder.parent.is_none());
assert!(builder.kind.is_none());
assert!(builder.value.is_none());
}
#[test]
fn test_customdebuginformation_builder_default() {
let builder = CustomDebugInformationBuilder::default();
assert!(builder.parent.is_none());
assert!(builder.kind.is_none());
assert!(builder.value.is_none());
}
#[test]
fn test_customdebuginformation_builder_method_parent() -> Result<()> {
let mut assembly = get_test_assembly()?;
let parent = CodedIndex::new(
TableId::MethodDef,
1,
CodedIndexType::HasCustomDebugInformation,
);
let debug_data = vec![0x01, 0x02, 0x03];
let ref_ = CustomDebugInformationBuilder::new()
.parent(parent)
.kind(42)
.value(&debug_data)
.build(&mut assembly)
.expect("Should build successfully");
assert_eq!(
ref_.kind(),
ChangeRefKind::TableRow(TableId::CustomDebugInformation)
);
Ok(())
}
#[test]
fn test_customdebuginformation_builder_document_parent() -> Result<()> {
let mut assembly = get_test_assembly()?;
let parent = CodedIndex::new(
TableId::Document,
2,
CodedIndexType::HasCustomDebugInformation,
);
let source_link_json = b"{\"documents\": {\"*\": \"https://github.com/repo/\"}}";
let ref_ = CustomDebugInformationBuilder::new()
.parent(parent)
.kind(1) .value(source_link_json)
.build(&mut assembly)
.expect("Should build successfully");
assert_eq!(
ref_.kind(),
ChangeRefKind::TableRow(TableId::CustomDebugInformation)
);
Ok(())
}
#[test]
fn test_customdebuginformation_builder_empty_value() -> Result<()> {
let mut assembly = get_test_assembly()?;
let parent = CodedIndex::new(
TableId::TypeDef,
1,
CodedIndexType::HasCustomDebugInformation,
);
let ref_ = CustomDebugInformationBuilder::new()
.parent(parent)
.kind(5)
.value(&[]) .build(&mut assembly)
.expect("Should build successfully");
assert_eq!(
ref_.kind(),
ChangeRefKind::TableRow(TableId::CustomDebugInformation)
);
Ok(())
}
#[test]
fn test_customdebuginformation_builder_missing_parent() -> Result<()> {
let mut assembly = get_test_assembly()?;
let debug_data = vec![0x01, 0x02];
let result = CustomDebugInformationBuilder::new()
.kind(1)
.value(&debug_data)
.build(&mut assembly);
assert!(result.is_err());
match result.unwrap_err() {
Error::ModificationInvalid(details) => {
assert!(details.contains("Parent coded index is required"));
}
_ => panic!("Expected ModificationInvalid error"),
}
Ok(())
}
#[test]
fn test_customdebuginformation_builder_missing_kind() -> Result<()> {
let mut assembly = get_test_assembly()?;
let parent = CodedIndex::new(
TableId::MethodDef,
1,
CodedIndexType::HasCustomDebugInformation,
);
let debug_data = vec![0x01, 0x02];
let result = CustomDebugInformationBuilder::new()
.parent(parent)
.value(&debug_data)
.build(&mut assembly);
assert!(result.is_err());
match result.unwrap_err() {
Error::ModificationInvalid(details) => {
assert!(details.contains("Kind GUID index is required"));
}
_ => panic!("Expected ModificationInvalid error"),
}
Ok(())
}
#[test]
fn test_customdebuginformation_builder_missing_value() -> Result<()> {
let mut assembly = get_test_assembly()?;
let parent = CodedIndex::new(
TableId::MethodDef,
1,
CodedIndexType::HasCustomDebugInformation,
);
let result = CustomDebugInformationBuilder::new()
.parent(parent)
.kind(1)
.build(&mut assembly);
assert!(result.is_err());
match result.unwrap_err() {
Error::ModificationInvalid(details) => {
assert!(details.contains("Value blob data is required"));
}
_ => panic!("Expected ModificationInvalid error"),
}
Ok(())
}
#[test]
fn test_customdebuginformation_builder_clone() {
let parent = CodedIndex::new(
TableId::MethodDef,
1,
CodedIndexType::HasCustomDebugInformation,
);
let debug_data = vec![0x01, 0x02, 0x03];
let builder = CustomDebugInformationBuilder::new()
.parent(parent)
.kind(42)
.value(&debug_data);
let cloned = builder.clone();
assert_eq!(builder.parent, cloned.parent);
assert_eq!(builder.kind, cloned.kind);
assert_eq!(builder.value, cloned.value);
}
#[test]
fn test_customdebuginformation_builder_debug() {
let parent = CodedIndex::new(
TableId::MethodDef,
1,
CodedIndexType::HasCustomDebugInformation,
);
let debug_data = vec![0x01, 0x02, 0x03];
let builder = CustomDebugInformationBuilder::new()
.parent(parent)
.kind(42)
.value(&debug_data);
let debug_str = format!("{builder:?}");
assert!(debug_str.contains("CustomDebugInformationBuilder"));
assert!(debug_str.contains("parent"));
assert!(debug_str.contains("kind"));
assert!(debug_str.contains("value"));
}
#[test]
fn test_customdebuginformation_builder_fluent_interface() -> Result<()> {
let mut assembly = get_test_assembly()?;
let parent = CodedIndex::new(TableId::Field, 3, CodedIndexType::HasCustomDebugInformation);
let debug_data = vec![0xFF, 0xEE, 0xDD];
let ref_ = CustomDebugInformationBuilder::new()
.parent(parent)
.kind(99)
.value(&debug_data)
.build(&mut assembly)
.expect("Should build successfully");
assert_eq!(
ref_.kind(),
ChangeRefKind::TableRow(TableId::CustomDebugInformation)
);
Ok(())
}
#[test]
fn test_customdebuginformation_builder_multiple_builds() -> Result<()> {
let mut assembly = get_test_assembly()?;
let parent1 = CodedIndex::new(
TableId::MethodDef,
1,
CodedIndexType::HasCustomDebugInformation,
);
let parent2 = CodedIndex::new(
TableId::MethodDef,
2,
CodedIndexType::HasCustomDebugInformation,
);
let data1 = vec![0x01, 0x02];
let data2 = vec![0x03, 0x04];
let ref1 = CustomDebugInformationBuilder::new()
.parent(parent1)
.kind(1)
.value(&data1)
.build(&mut assembly)
.expect("Should build first debug info");
let ref2 = CustomDebugInformationBuilder::new()
.parent(parent2)
.kind(2)
.value(&data2)
.build(&mut assembly)
.expect("Should build second debug info");
assert_eq!(
ref1.kind(),
ChangeRefKind::TableRow(TableId::CustomDebugInformation)
);
assert_eq!(
ref2.kind(),
ChangeRefKind::TableRow(TableId::CustomDebugInformation)
);
assert!(!std::sync::Arc::ptr_eq(&ref1, &ref2));
Ok(())
}
}