use crate::{
manifest::{self, ManifestFile},
plan::{PinnedId, PinnedManifests},
};
use serde::{Deserialize, Serialize};
use std::{
collections::hash_map,
fmt,
hash::{Hash, Hasher},
path::{Path, PathBuf},
};
use thiserror::Error;
mod member;
mod path;
trait Pin {
type Pinned: Fetch + Hash;
type Error: fmt::Debug + fmt::Display;
fn pin(&self, ctx: PinCtx) -> Result<(Self::Pinned, PathBuf), Self::Error>;
}
trait Fetch {
type Error: fmt::Debug + fmt::Display;
fn fetch(&self, ctx: PinCtx, local: &Path) -> Result<ManifestFile, Self::Error>;
}
trait DepPath {
type Error: fmt::Debug + fmt::Display;
fn dep_path(&self, name: &str) -> Result<DependencyPath, Self::Error>;
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
pub enum Source {
Member(member::Source),
Path(path::Source),
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
pub enum Pinned {
Member(member::Pinned),
Path(path::Pinned),
}
#[derive(Clone)]
pub(crate) struct PinCtx<'a> {
pub(crate) _fetch_id: FetchId,
pub(crate) path_root: PinnedId,
pub(crate) pkg_name: &'a str,
}
pub(crate) enum DependencyPath {
Member,
#[allow(dead_code)]
ManifestPath(PathBuf),
Root(PinnedId),
}
#[derive(Debug, Error)]
pub enum SourceError {
#[error("failed to canonicalize path {0:?}: {1}")]
FailedToCanonicalizePath(std::path::PathBuf, std::io::Error),
}
#[derive(Debug, Error)]
pub enum PinAndFetchError {
#[error("{0}")]
Path(#[from] PinAndFetchErrorKind<path::Source>),
#[error("{0}")]
Member(#[from] PinAndFetchErrorKind<member::Source>),
}
#[derive(Debug, Error)]
#[allow(private_bounds, private_interfaces)]
pub enum PinAndFetchErrorKind<T: Pin> {
#[error("failed to pin dependency source: {0}")]
Pin(T::Error),
#[error("failed to fetch dependency source: {0}")]
Fetch(<T::Pinned as Fetch>::Error),
}
#[derive(Debug, Error)]
#[error("failed to resolve the path to the dependency's local source")]
pub enum DepPathError {}
pub type FetchId = u64;
impl Source {
fn from_relative_path<'a>(
manifest_dir: &Path,
relative_path: &Path,
member_manifests: impl IntoIterator<Item = &'a ManifestFile>,
) -> Result<Self, SourceError> {
let path = manifest_dir.join(relative_path);
let canonical_path = path
.canonicalize()
.map_err(|e| SourceError::FailedToCanonicalizePath(path, e))?;
let is_member = member_manifests
.into_iter()
.any(|pkg_manifest| pkg_manifest.dir() == canonical_path);
if is_member {
Ok(Source::Member(member::Source(canonical_path)))
} else {
Ok(Source::Path(canonical_path))
}
}
pub fn from_manifest_dep<'a>(
manifest_dir: &Path,
dep: &manifest::Dependency,
member_manifests: impl IntoIterator<Item = &'a ManifestFile>,
) -> Result<Self, SourceError> {
match &dep.source {
manifest::dependency::Source::Path(path) => {
Self::from_relative_path(manifest_dir, &path.path, member_manifests)
}
}
}
pub(crate) fn pin_and_fetch(
&self,
ctx: PinCtx,
manifests: &mut PinnedManifests,
) -> Result<Pinned, PinAndFetchError> {
match self {
Source::Member(source) => Ok(Pinned::Member(pin_and_fetch(source, ctx, manifests)?)),
Source::Path(source) => Ok(Pinned::Path(pin_and_fetch(source, ctx, manifests)?)),
}
}
}
impl Pinned {
pub const MEMBER: Self = Self::Member(member::Pinned);
pub fn unpinned(&self, path: &Path) -> Source {
match self {
Self::Member(_) => Source::Member(member::Source(path.to_owned())),
Self::Path(_) => Source::Path(path.to_owned()),
}
}
pub(crate) fn dep_path(&self, name: &str) -> Result<DependencyPath, DepPathError> {
match self {
Self::Member(pinned) => Ok(pinned.dep_path(name).expect("infallible")),
Self::Path(pinned) => Ok(pinned.dep_path(name).expect("infallible")),
}
}
}
impl fmt::Display for Pinned {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Member(p) => p.fmt(f),
Self::Path(p) => p.fmt(f),
}
}
}
fn pin_and_fetch<T>(
source: &T,
ctx: PinCtx,
manifests: &mut PinnedManifests,
) -> Result<T::Pinned, PinAndFetchErrorKind<T>>
where
T: Pin,
T::Pinned: Clone,
Pinned: From<T::Pinned>,
{
let (pinned, fetch_path) = source.pin(ctx.clone()).map_err(PinAndFetchErrorKind::Pin)?;
let id = PinnedId::new(ctx.pkg_name, &Pinned::from(pinned.clone()));
if let hash_map::Entry::Vacant(entry) = manifests.entry(id) {
let res = pinned.fetch(ctx, &fetch_path);
let manifest = res.map_err(PinAndFetchErrorKind::Fetch)?;
entry.insert(manifest);
}
Ok(pinned)
}
pub fn fetch_graph_id(path: &Path, timestamp: std::time::Instant) -> FetchId {
let mut hasher = hash_map::DefaultHasher::new();
path.hash(&mut hasher);
timestamp.hash(&mut hasher);
hasher.finish()
}