use crate::database::PciDatabase;
use crate::vendors::Vendor;
use crate::devices::Device;
use crate::classes::{DeviceClass, SubClass};
use crate::types::*;
use alloc::{vec::Vec, string::String, string::ToString};
#[derive(Debug)]
pub struct QueryBuilder<'db> {
database: &'db PciDatabase,
vendor_id_filter: Option<VendorId>,
vendor_name_filter: Option<String>,
device_id_filter: Option<DeviceId>,
device_name_filter: Option<String>,
class_id_filter: Option<DeviceClassId>,
class_name_filter: Option<String>,
subclass_id_filter: Option<SubClassId>,
subclass_name_filter: Option<String>,
}
impl<'db> QueryBuilder<'db> {
pub fn new(database: &'db PciDatabase) -> Self {
Self {
database,
vendor_id_filter: None,
vendor_name_filter: None,
device_id_filter: None,
device_name_filter: None,
class_id_filter: None,
class_name_filter: None,
subclass_id_filter: None,
subclass_name_filter: None,
}
}
pub fn vendor_id(mut self, vendor_id: VendorId) -> Self {
self.vendor_id_filter = Some(vendor_id);
self
}
pub fn vendor_name_contains(mut self, name: &str) -> Self {
self.vendor_name_filter = Some(name.to_lowercase());
self
}
pub fn device_id(mut self, device_id: DeviceId) -> Self {
self.device_id_filter = Some(device_id);
self
}
pub fn device_name_contains(mut self, name: &str) -> Self {
self.device_name_filter = Some(name.to_lowercase());
self
}
pub fn class_id(mut self, class_id: DeviceClassId) -> Self {
self.class_id_filter = Some(class_id);
self
}
pub fn class_name_contains(mut self, name: &str) -> Self {
self.class_name_filter = Some(name.to_lowercase());
self
}
pub fn subclass_id(mut self, subclass_id: SubClassId) -> Self {
self.subclass_id_filter = Some(subclass_id);
self
}
pub fn subclass_name_contains(mut self, name: &str) -> Self {
self.subclass_name_filter = Some(name.to_lowercase());
self
}
pub fn execute(self) -> Vec<DeviceMatch<'db>> {
let mut results = Vec::new();
for vendor in self.database.vendors() {
if let Some(ref vendor_id) = self.vendor_id_filter {
if vendor.id() != *vendor_id {
continue;
}
}
if let Some(ref vendor_name) = self.vendor_name_filter {
if !vendor.name().to_lowercase().contains(vendor_name) {
continue;
}
}
for device in vendor.devices() {
if let Some(ref device_id) = self.device_id_filter {
if device.id() != *device_id {
continue;
}
}
if let Some(ref device_name) = self.device_name_filter {
if !device.name().to_lowercase().contains(device_name) {
continue;
}
}
let class_match = self.find_matching_class();
if self.has_class_filters() && class_match.is_none() {
continue;
}
results.push(DeviceMatch {
vendor,
device,
class_info: class_match,
});
}
}
results
}
pub fn execute_vendors(self) -> Vec<&'db Vendor> {
let mut results = Vec::new();
for vendor in self.database.vendors() {
if let Some(ref vendor_id) = self.vendor_id_filter {
if vendor.id() != *vendor_id {
continue;
}
}
if let Some(ref vendor_name) = self.vendor_name_filter {
if !vendor.name().to_lowercase().contains(vendor_name) {
continue;
}
}
results.push(vendor);
}
results
}
pub fn execute_classes(self) -> Vec<ClassMatch<'db>> {
let mut results = Vec::new();
for class in self.database.classes() {
if let Some(ref class_id) = self.class_id_filter {
if class.id() != *class_id {
continue;
}
}
if let Some(ref class_name) = self.class_name_filter {
if !class.name().to_lowercase().contains(class_name) {
continue;
}
}
let matching_subclasses: Vec<&SubClass> = class
.subclasses()
.iter()
.filter(|subclass| {
if let Some(ref subclass_id) = self.subclass_id_filter {
if subclass.id() != *subclass_id {
return false;
}
}
if let Some(ref subclass_name) = self.subclass_name_filter {
if !subclass.name().to_lowercase().contains(subclass_name) {
return false;
}
}
true
})
.collect();
if self.has_subclass_filters() && matching_subclasses.is_empty() {
continue;
}
results.push(ClassMatch {
class,
matching_subclasses,
});
}
results
}
fn has_class_filters(&self) -> bool {
self.class_id_filter.is_some() || self.class_name_filter.is_some() || self.has_subclass_filters()
}
fn has_subclass_filters(&self) -> bool {
self.subclass_id_filter.is_some() || self.subclass_name_filter.is_some()
}
fn find_matching_class(&self) -> Option<&'db DeviceClass> {
for class in self.database.classes() {
if let Some(ref class_id) = self.class_id_filter {
if class.id() != *class_id {
continue;
}
}
if let Some(ref class_name) = self.class_name_filter {
if !class.name().to_lowercase().contains(class_name) {
continue;
}
}
if self.has_subclass_filters() {
let has_matching_subclass = class.subclasses().iter().any(|subclass| {
if let Some(ref subclass_id) = self.subclass_id_filter {
if subclass.id() != *subclass_id {
return false;
}
}
if let Some(ref subclass_name) = self.subclass_name_filter {
if !subclass.name().to_lowercase().contains(subclass_name) {
return false;
}
}
true
});
if !has_matching_subclass {
continue;
}
}
return Some(class);
}
None
}
}
#[derive(Debug)]
pub struct DeviceMatch<'db> {
pub vendor: &'db Vendor,
pub device: &'db Device,
pub class_info: Option<&'db DeviceClass>,
}
impl<'db> DeviceMatch<'db> {
pub fn vendor_id(&self) -> VendorId {
self.vendor.id()
}
pub fn vendor_name(&self) -> &'static str {
self.vendor.name()
}
pub fn device_id(&self) -> DeviceId {
self.device.id()
}
pub fn device_name(&self) -> &'static str {
self.device.name()
}
pub fn description(&self) -> String {
if let Some(class) = self.class_info {
alloc::format!(
"{} {} ({})",
self.vendor_name(),
self.device_name(),
class.name()
)
} else {
alloc::format!("{} {}", self.vendor_name(), self.device_name())
}
}
}
#[derive(Debug)]
pub struct ClassMatch<'db> {
pub class: &'db DeviceClass,
pub matching_subclasses: Vec<&'db SubClass>,
}
impl<'db> ClassMatch<'db> {
pub fn class_id(&self) -> DeviceClassId {
self.class.id()
}
pub fn class_name(&self) -> &'static str {
self.class.name()
}
pub fn description(&self) -> String {
if self.matching_subclasses.is_empty() {
self.class_name().to_string()
} else {
let subclass_names: Vec<&str> = self
.matching_subclasses
.iter()
.map(|sc| sc.name())
.collect();
alloc::format!("{} ({})", self.class_name(), subclass_names.join(", "))
}
}
}
impl PciDatabase {
pub fn devices_by_vendor(&self, vendor_id: VendorId) -> Option<&[Device]> {
self.find_vendor(vendor_id).map(|vendor| vendor.devices())
}
pub fn devices_by_class(&self, class_id: DeviceClassId) -> Vec<DeviceMatch<'_>> {
QueryBuilder::new(self).class_id(class_id).execute()
}
pub fn search_vendors(&self, name: &str) -> Vec<&Vendor> {
QueryBuilder::new(self)
.vendor_name_contains(name)
.execute_vendors()
}
pub fn search_devices(&self, name: &str) -> Vec<DeviceMatch<'_>> {
QueryBuilder::new(self)
.device_name_contains(name)
.execute()
}
pub fn search_classes(&self, name: &str) -> Vec<ClassMatch<'_>> {
QueryBuilder::new(self)
.class_name_contains(name)
.execute_classes()
}
pub fn query(&self) -> QueryBuilder<'_> {
QueryBuilder::new(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vendors::Vendor;
use crate::devices::Device;
use crate::classes::DeviceClass;
#[test]
fn test_query_builder_creation() {
let vendors: &[Vendor] = &[];
let classes: &[DeviceClass] = &[];
let db = PciDatabase::new(vendors, classes);
let query = QueryBuilder::new(&db);
let results = query.execute();
assert!(results.is_empty());
}
#[test]
fn test_empty_database_queries() {
let vendors: &[Vendor] = &[];
let classes: &[DeviceClass] = &[];
let db = PciDatabase::new(vendors, classes);
assert!(db.search_vendors("test").is_empty());
assert!(db.search_devices("test").is_empty());
assert!(db.search_classes("test").is_empty());
}
}