use crate::metadata::{
identity::{AssemblyIdentity, AssemblyVersion},
tables::{AssemblyRefRc, FileRc, ModuleRefRc},
};
#[derive(Debug, Clone)]
pub struct AssemblyDependency {
pub source: DependencySource,
pub target_identity: AssemblyIdentity,
pub dependency_type: DependencyType,
pub version_requirement: VersionRequirement,
pub is_optional: bool,
pub resolution_state: DependencyResolutionState,
}
#[derive(Debug, Clone)]
pub enum DependencySource {
AssemblyRef(AssemblyRefRc),
ModuleRef(ModuleRefRc),
File(FileRc),
}
impl PartialEq for DependencySource {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(DependencySource::AssemblyRef(a), DependencySource::AssemblyRef(b)) => {
a.name == b.name
&& a.major_version == b.major_version
&& a.minor_version == b.minor_version
&& a.build_number == b.build_number
&& a.revision_number == b.revision_number
&& a.culture == b.culture
&& a.identifier == b.identifier
}
(DependencySource::ModuleRef(a), DependencySource::ModuleRef(b)) => a.name == b.name,
(DependencySource::File(a), DependencySource::File(b)) => a.name == b.name,
_ => false,
}
}
}
impl Eq for DependencySource {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DependencyType {
Reference,
Friend,
TypeForwarding,
Resource,
NativeLibrary,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VersionRequirement {
Exact,
Compatible,
Any,
Minimum(AssemblyVersion),
}
impl VersionRequirement {
#[must_use]
pub const fn strictness(&self) -> u8 {
match self {
VersionRequirement::Exact => 3,
VersionRequirement::Minimum(_) => 2,
VersionRequirement::Compatible => 1,
VersionRequirement::Any => 0,
}
}
}
impl PartialOrd for VersionRequirement {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VersionRequirement {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match (self, other) {
(VersionRequirement::Minimum(v1), VersionRequirement::Minimum(v2)) => v1.cmp(v2),
_ => self.strictness().cmp(&other.strictness()),
}
}
}
impl DependencySource {
#[must_use]
pub fn display_name(&self) -> &str {
match self {
DependencySource::AssemblyRef(assembly_ref) => &assembly_ref.name,
DependencySource::ModuleRef(module_ref) => &module_ref.name,
DependencySource::File(file) => &file.name,
}
}
#[must_use]
pub fn dependency_type(&self) -> DependencyType {
match self {
DependencySource::AssemblyRef(_) | DependencySource::ModuleRef(_) => {
DependencyType::Reference
}
DependencySource::File(_) => DependencyType::Resource, }
}
}
#[derive(Debug, Clone)]
pub enum DependencyResolutionState {
Unresolved,
ResolvedAsAssembly {
identity: AssemblyIdentity,
verified: bool,
},
ResolvedAsNativeLibrary {
path: Option<std::path::PathBuf>,
exports_verified: bool,
},
ResolvedAsResource {
resource_type: String,
size_bytes: Option<u64>,
},
ResolutionFailed {
error: String,
is_fatal: bool,
suggestions: Vec<String>,
},
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct DependencyResolveContext {
pub search_paths: Vec<std::path::PathBuf>,
pub check_gac: bool,
pub version_policies: Vec<String>,
}
impl Default for DependencyResolveContext {
fn default() -> Self {
Self {
search_paths: vec![],
check_gac: true,
version_policies: vec![],
}
}
}
impl std::fmt::Display for DependencyType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DependencyType::Reference => write!(f, "Reference"),
DependencyType::Friend => write!(f, "Friend"),
DependencyType::TypeForwarding => write!(f, "TypeForwarding"),
DependencyType::Resource => write!(f, "Resource"),
DependencyType::NativeLibrary => write!(f, "NativeLibrary"),
}
}
}
impl std::fmt::Display for VersionRequirement {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
VersionRequirement::Exact => write!(f, "Exact"),
VersionRequirement::Compatible => write!(f, "Compatible"),
VersionRequirement::Any => write!(f, "Any"),
VersionRequirement::Minimum(version) => {
write!(f, "Minimum({version})")
}
}
}
}
impl std::fmt::Display for DependencySource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DependencySource::AssemblyRef(assembly_ref) => {
write!(f, "AssemblyRef({})", assembly_ref.name)
}
DependencySource::ModuleRef(module_ref) => {
write!(f, "ModuleRef({})", module_ref.name)
}
DependencySource::File(file) => {
write!(f, "File({})", file.name)
}
}
}
}
impl std::fmt::Display for AssemblyDependency {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} -> {} ({}{})",
self.source.display_name(),
self.target_identity.name,
self.dependency_type,
if self.is_optional { ", optional" } else { "" }
)
}
}
impl std::fmt::Display for DependencyResolutionState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DependencyResolutionState::Unresolved => write!(f, "Unresolved"),
DependencyResolutionState::ResolvedAsAssembly { identity, verified } => {
write!(
f,
"ResolvedAsAssembly({}, verified={})",
identity.name, verified
)
}
DependencyResolutionState::ResolvedAsNativeLibrary {
path,
exports_verified,
} => {
write!(
f,
"ResolvedAsNativeLibrary({}, verified={})",
path.as_ref()
.map_or_else(|| "unknown".to_string(), |p| p.display().to_string()),
exports_verified
)
}
DependencyResolutionState::ResolvedAsResource {
resource_type,
size_bytes,
} => {
write!(
f,
"ResolvedAsResource({}, {} bytes)",
resource_type,
size_bytes
.map(|s| s.to_string())
.unwrap_or_else(|| "unknown".to_string())
)
}
DependencyResolutionState::ResolutionFailed {
error, is_fatal, ..
} => {
write!(f, "ResolutionFailed({error}, fatal={is_fatal})")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::helpers::dependencies::create_test_assembly_ref;
#[test]
fn test_dependency_source_display_name() {
let assembly_ref = create_test_assembly_ref("TestLib");
let source = DependencySource::AssemblyRef(assembly_ref);
assert_eq!(source.display_name(), "TestLib");
}
#[test]
fn test_dependency_source_types() {
let assembly_ref = create_test_assembly_ref("TestLib");
let assembly_source = DependencySource::AssemblyRef(assembly_ref);
assert_eq!(assembly_source.dependency_type(), DependencyType::Reference);
}
#[test]
fn test_dependency_resolution_state_initial() {
let state = DependencyResolutionState::Unresolved;
matches!(state, DependencyResolutionState::Unresolved);
}
#[test]
fn test_version_requirement_exact() {
let req = VersionRequirement::Exact;
assert_eq!(req, VersionRequirement::Exact);
}
#[test]
fn test_version_requirement_minimum() {
let version = AssemblyVersion::new(1, 2, 3, 4);
let req = VersionRequirement::Minimum(version);
match req {
VersionRequirement::Minimum(v) => {
assert_eq!(v.major, 1);
assert_eq!(v.minor, 2);
}
_ => panic!("Expected Minimum version requirement"),
}
}
#[test]
fn test_dependency_type_display() {
assert_eq!(DependencyType::Reference.to_string(), "Reference");
assert_eq!(DependencyType::Friend.to_string(), "Friend");
assert_eq!(DependencyType::TypeForwarding.to_string(), "TypeForwarding");
assert_eq!(DependencyType::Resource.to_string(), "Resource");
assert_eq!(DependencyType::NativeLibrary.to_string(), "NativeLibrary");
}
#[test]
fn test_version_requirement_display() {
assert_eq!(VersionRequirement::Exact.to_string(), "Exact");
assert_eq!(VersionRequirement::Compatible.to_string(), "Compatible");
assert_eq!(VersionRequirement::Any.to_string(), "Any");
let version = AssemblyVersion::new(1, 2, 3, 4);
let req = VersionRequirement::Minimum(version);
assert_eq!(req.to_string(), "Minimum(1.2.3.4)");
}
#[test]
fn test_dependency_source_display() {
let assembly_ref = create_test_assembly_ref("TestLib");
let source = DependencySource::AssemblyRef(assembly_ref);
assert_eq!(source.to_string(), "AssemblyRef(TestLib)");
}
#[test]
fn test_dependency_resolution_state_display() {
let state = DependencyResolutionState::Unresolved;
assert_eq!(state.to_string(), "Unresolved");
let resolved = DependencyResolutionState::ResolutionFailed {
error: "Not found".to_string(),
is_fatal: true,
suggestions: vec![],
};
assert!(resolved.to_string().contains("ResolutionFailed"));
assert!(resolved.to_string().contains("fatal=true"));
}
}