| //! Entries in the advisory database |
| |
| use super::Iter; |
| use crate::{ |
| advisory::{self, Advisory}, |
| collection::Collection, |
| error::{Error, ErrorKind}, |
| map, Map, |
| }; |
| use std::{ |
| ffi::{OsStr, OsString}, |
| path::Path, |
| }; |
| |
| /// "Slots" identify the location in the entries table where a particular |
| /// advisory is located. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] |
| pub(crate) struct Slot(usize); |
| |
| /// Entries in the advisory database |
| #[derive(Debug, Default)] |
| pub(crate) struct Entries { |
| /// Index of advisory IDs to their slots |
| index: Map<advisory::Id, Slot>, |
| |
| /// Advisory collection |
| advisories: Vec<Advisory>, |
| } |
| |
| impl Entries { |
| /// Create a new database entries collection |
| pub fn new() -> Self { |
| Self::default() |
| } |
| |
| /// Load an advisory from a file and insert it into the database entry table |
| // TODO(tarcieri): factor more of this into `advisory.rs`? |
| pub fn load_file(&mut self, path: &Path) -> Result<Option<Slot>, Error> { |
| let mut advisory = Advisory::load_file(path)?; |
| |
| // TODO(tarcieri): deprecate and remove legacy TOML-based advisory format |
| let expected_filename = match path.extension().and_then(|ext| ext.to_str()) { |
| Some("md") => OsString::from(format!("{}.md", advisory.metadata.id)), |
| _ => fail!( |
| ErrorKind::Repo, |
| "unexpected file extension: {}", |
| path.display() |
| ), |
| }; |
| |
| // Ensure advisory has the correct filename |
| if path.file_name().unwrap() != expected_filename { |
| fail!( |
| ErrorKind::Repo, |
| "expected {} to be named {:?}", |
| path.display(), |
| expected_filename |
| ); |
| } |
| |
| // Ensure advisory is in a directory named after its package |
| let package_dir = path.parent().ok_or_else(|| { |
| format_err!( |
| ErrorKind::Repo, |
| "advisory has no parent dir: {}", |
| path.display() |
| ) |
| })?; |
| |
| if package_dir.file_name().unwrap() != OsStr::new(advisory.metadata.package.as_str()) { |
| fail!( |
| ErrorKind::Repo, |
| "expected {} to be in {} directory (instead of \"{:?}\")", |
| advisory.metadata.id, |
| advisory.metadata.package, |
| package_dir |
| ); |
| } |
| |
| // Get the collection this advisory is part of |
| let collection_dir = package_dir |
| .parent() |
| .ok_or_else(|| { |
| format_err!( |
| ErrorKind::Repo, |
| "advisory has no collection: {}", |
| path.display() |
| ) |
| })? |
| .file_name() |
| .unwrap(); |
| |
| let collection = if collection_dir == OsStr::new(Collection::Crates.as_str()) { |
| Collection::Crates |
| } else if collection_dir == OsStr::new(Collection::Rust.as_str()) { |
| Collection::Rust |
| } else { |
| fail!( |
| ErrorKind::Repo, |
| "invalid package collection: {:?}", |
| collection_dir |
| ); |
| }; |
| |
| match advisory.metadata.collection { |
| Some(c) => { |
| if c != collection { |
| fail!( |
| ErrorKind::Parse, |
| "collection mismatch for {}", |
| &advisory.metadata.id |
| ); |
| } |
| } |
| None => advisory.metadata.collection = Some(collection), |
| } |
| |
| // Ensure placeholder advisories load and parse correctly, but |
| // don't actually insert them into the advisory database |
| if advisory.metadata.id.is_placeholder() { |
| return Ok(None); |
| } |
| |
| let id = advisory.metadata.id.clone(); |
| let slot = Slot(self.advisories.len()); |
| self.advisories.push(advisory); |
| |
| match self.index.entry(id) { |
| map::Entry::Vacant(entry) => { |
| entry.insert(slot); |
| } |
| map::Entry::Occupied(entry) => { |
| fail!(ErrorKind::Parse, "duplicate advisory ID: {}", entry.key()) |
| } |
| } |
| |
| Ok(Some(slot)) |
| } |
| |
| /// Find an advisory by its `advisory::Id` |
| pub fn find_by_id(&self, id: &advisory::Id) -> Option<&Advisory> { |
| self.index.get(id).and_then(|slot| self.get(*slot)) |
| } |
| |
| /// Get an advisory from the database by its [`Slot`] |
| pub fn get(&self, slot: Slot) -> Option<&Advisory> { |
| self.advisories.get(slot.0) |
| } |
| |
| /// Iterate over all of the entries in the database |
| pub fn iter(&self) -> Iter<'_> { |
| self.advisories.iter() |
| } |
| } |
| |
| impl IntoIterator for Entries { |
| type Item = Advisory; |
| |
| type IntoIter = std::vec::IntoIter<Advisory>; |
| |
| fn into_iter(self) -> Self::IntoIter { |
| self.advisories.into_iter() |
| } |
| } |