blob: 849f5bb9ee99e1b678cc0b0a06f57e6b45506afe [file] [log] [blame]
//! 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()
}
}