use alloc::{borrow::Cow, collections::BTreeMap, vec::Vec};
use serde::{de, Deserialize};
use crate::{Table, Value};
#[derive(Debug, Clone, Deserialize)]
pub struct Dependencies<'d>(#[serde(borrow)] BTreeMap<Cow<'d, str>, Dependency<'d>>);
impl<'d> Dependencies<'d> {
pub fn by_name(&self, name: &str) -> Option<&Dependency<'d>> {
self.0.get(name)
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &Dependency<'d>)> {
self.0.iter().map(|(k, v)| (&**k, v))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Dependency<'d> {
version: Option<Cow<'d, str>>,
optional: Option<bool>,
features: Option<Vec<Cow<'d, str>>>,
workspace: Option<bool>,
package: Option<Cow<'d, str>>,
source: Option<Source<'d>>,
}
impl Dependency<'_> {
pub fn version(&self) -> Option<&str> {
self.version.as_deref()
}
pub fn optional(&self) -> Option<bool> {
self.optional
}
pub fn features(&self) -> Option<impl Iterator<Item = &str>> {
self.features.as_ref().map(|v| v.iter().map(|s| &**s))
}
pub fn workspace(&self) -> Option<bool> {
self.workspace
}
pub fn package(&self) -> Option<&str> {
self.package.as_deref()
}
pub fn source(&self) -> Option<&Source<'_>> {
self.source.as_ref()
}
}
impl<'d, 'de: 'd> Deserialize<'de> for Dependency<'d> {
fn deserialize<D>(deserializer: D) -> Result<Dependency<'d>, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = Value::deserialize(deserializer)?;
match value {
Value::String(version) => Ok(Dependency {
version: Some(version),
optional: None,
features: None,
workspace: None,
package: None,
source: None,
}),
Value::Table(table) => {
let version = get_string(&table, "version")?;
let optional = table.get("optional").and_then(|v| v.as_bool());
let features = table
.get("features")
.map(|v| match v {
Value::Array(a) => a
.clone()
.into_iter()
.map(|v| v.try_into().map_err(de::Error::custom))
.collect(),
_ => Err(de::Error::invalid_type(
de::Unexpected::Other("not an array"),
&"an array",
)),
})
.transpose()?;
let workspace = table.get("workspace").map(|v| v.as_bool().unwrap_or(false));
let package = get_string(&table, "package")?;
let source = Source::new(&table)?;
Ok(Dependency {
version,
optional,
features,
workspace,
package,
source,
})
}
_ => Err(de::Error::invalid_type(
de::Unexpected::Other("not a string or table"),
&"a string or table",
)),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Source<'r> {
Git(Git<'r>),
Path(Cow<'r, str>),
}
impl<'r> Source<'r> {
fn new<E>(table: &Table<'r>) -> Result<Option<Self>, E>
where
E: de::Error,
{
let git = Git::new(table)?;
let path = get_string(table, "path")?;
match (git, path) {
(Some(git), None) => Ok(Some(Source::Git(git))),
(None, Some(path)) => Ok(Some(Source::Path(path))),
(None, None) => Ok(None),
_ => Err(de::Error::invalid_value(
de::Unexpected::Other("both `git` and `path` specified"),
&"either `git` or `path`",
)),
}
}
pub fn git(&self) -> Option<&Git<'r>> {
match self {
Source::Git(git) => Some(git),
_ => None,
}
}
pub fn path(&self) -> Option<&str> {
match self {
Source::Path(path) => Some(path),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Git<'g> {
repo: Cow<'g, str>,
commit: Option<GitCommit<'g>>,
}
impl<'c> Git<'c> {
fn new<E>(table: &Table<'c>) -> Result<Option<Self>, E>
where
E: de::Error,
{
let git_repo = get_string(table, "git")?;
match git_repo {
Some(git_repo) => Ok(Some(Git {
repo: git_repo,
commit: GitCommit::new(table)?,
})),
None => Ok(None),
}
}
pub fn repository(&self) -> &str {
&self.repo
}
pub fn commit(&self) -> Option<&GitCommit<'_>> {
self.commit.as_ref()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum GitCommit<'c> {
Branch(Cow<'c, str>),
Tag(Cow<'c, str>),
Rev(Cow<'c, str>),
}
impl<'c> GitCommit<'c> {
fn new<E>(table: &Table<'c>) -> Result<Option<Self>, E>
where
E: de::Error,
{
let branch = get_string(table, "branch")?;
let tag = get_string(table, "tag")?;
let rev = get_string(table, "rev")?;
match (branch, tag, rev) {
(Some(branch), None, None) => Ok(Some(GitCommit::Branch(branch))),
(None, Some(tag), None) => Ok(Some(GitCommit::Tag(tag))),
(None, None, Some(rev)) => Ok(Some(GitCommit::Rev(rev))),
(None, None, None) => Ok(None),
_ => Err(de::Error::invalid_value(
de::Unexpected::Other("invalid commit specification"),
&"either a branch, tag, or rev",
)),
}
}
pub fn branch(&self) -> Option<&str> {
match self {
GitCommit::Branch(branch) => Some(branch),
_ => None,
}
}
pub fn tag(&self) -> Option<&str> {
match self {
GitCommit::Tag(tag) => Some(tag),
_ => None,
}
}
pub fn revision(&self) -> Option<&str> {
match self {
GitCommit::Rev(rev) => Some(rev),
_ => None,
}
}
}
fn get_string<'t, E>(table: &Table<'t>, key: &str) -> Result<Option<Cow<'t, str>>, E>
where
E: de::Error,
{
table
.get(key)
.map(|v| match v {
Value::String(s) => Ok(s),
_ => Err(de::Error::invalid_type(
de::Unexpected::Other("not a borrowed string"),
&"a borrowed string",
)),
})
.transpose()
.map(|s| s.cloned())
}