use super::{Dependency, Manifest, ManifestError, Revision};
use semver::{Version, VersionReq};
use serde::Deserialize;
use std::collections::BTreeMap;
use std::path::Path;
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct RawManifest {
name: String,
version: String,
#[serde(default)]
files: Option<Vec<String>>,
#[serde(default)]
exclude: Vec<String>,
#[serde(default)]
compiler: Option<String>,
#[serde(default)]
dependencies: BTreeMap<String, RawDependency>,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct RawDependency {
src: String,
#[serde(default)]
rev: Option<String>,
#[serde(default)]
tag: Option<String>,
}
fn required_text(path: &Path, field: &str, value: &str) -> Result<(), ManifestError> {
if value.trim().is_empty() {
return Err(ManifestError::InvalidField {
path: path.to_path_buf(),
field: field.to_string(),
reason: "must not be empty".to_string(),
});
}
Ok(())
}
fn parse_version(path: &Path, field: &str, value: &str) -> Result<Version, ManifestError> {
Version::parse(value).map_err(|err| ManifestError::InvalidField {
path: path.to_path_buf(),
field: field.to_string(),
reason: format!("`{value}` is not a semantic version: {err}"),
})
}
fn parse_compiler(path: &Path, value: &str) -> Result<VersionReq, ManifestError> {
VersionReq::parse(value).map_err(|err| ManifestError::InvalidField {
path: path.to_path_buf(),
field: "compiler".to_string(),
reason: format!("`{value}` is not a semantic version requirement: {err}"),
})
}
fn parse_dependency(
path: &Path,
name: &str,
raw: RawDependency,
) -> Result<Dependency, ManifestError> {
let base = format!("dependencies.{name}");
required_text(path, &format!("{base}.src"), &raw.src)?;
let revision = match (raw.rev, raw.tag) {
(Some(rev), None) => {
required_text(path, &format!("{base}.rev"), &rev)?;
Revision::Rev(rev)
}
(None, Some(tag)) => {
required_text(path, &format!("{base}.tag"), &tag)?;
Revision::Tag(tag)
}
(Some(_), Some(_)) => {
return Err(ManifestError::InvalidField {
path: path.to_path_buf(),
field: base,
reason: "declares both `rev` and `tag`; keep exactly one".to_string(),
});
}
(None, None) => {
return Err(ManifestError::InvalidField {
path: path.to_path_buf(),
field: base,
reason: "needs either `rev` or `tag` to pin a revision".to_string(),
});
}
};
Ok(Dependency::new(raw.src, revision))
}
pub(super) fn from_str(path: &Path, text: &str) -> Result<Manifest, ManifestError> {
let raw: RawManifest = toml::from_str(text).map_err(|source| ManifestError::Deserialize {
path: path.to_path_buf(),
source,
})?;
required_text(path, "name", &raw.name)?;
let version = parse_version(path, "version", &raw.version)?;
let compiler = raw
.compiler
.as_deref()
.map(|value| parse_compiler(path, value))
.transpose()?;
let mut dependencies = BTreeMap::new();
for (name, raw_dependency) in raw.dependencies {
required_text(path, &format!("dependencies.{name}"), &name)?;
dependencies.insert(name.clone(), parse_dependency(path, &name, raw_dependency)?);
}
Ok(Manifest {
name: raw.name,
version,
files: raw.files,
exclude: raw.exclude,
compiler,
dependencies,
})
}