use std::{path::Path, str::FromStr};
use async_trait::async_trait;
use miette::{Context, IntoDiagnostic};
use serde::{Deserialize, Serialize};
use super::MANIFEST_FILE;
use super::raw::RawManifest;
use crate::{ManagedFile, errors::DeserializationError, io::File};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkspaceManifest {
pub workspace: Workspace,
}
impl WorkspaceManifest {
pub fn builder() -> WorkspaceManifestBuilder<NoWorkspace> {
WorkspaceManifestBuilder {
workspace: NoWorkspace,
}
}
}
#[derive(Default, Clone)]
pub struct NoWorkspace;
pub struct WorkspaceManifestBuilder<W> {
workspace: W,
}
impl WorkspaceManifestBuilder<NoWorkspace> {
pub fn workspace(self, workspace: Workspace) -> WorkspaceManifestBuilder<Workspace> {
WorkspaceManifestBuilder { workspace }
}
}
impl WorkspaceManifestBuilder<Workspace> {
pub fn build(self) -> WorkspaceManifest {
WorkspaceManifest {
workspace: self.workspace,
}
}
}
#[async_trait]
impl File for WorkspaceManifest {
const DEFAULT_PATH: &str = MANIFEST_FILE;
async fn load_from<P>(path: P) -> miette::Result<Self>
where
P: AsRef<Path> + Send + Sync,
{
RawManifest::load_from(path).await?.try_into()
}
async fn save_to<P>(&self, path: P) -> miette::Result<()>
where
P: AsRef<Path> + Send + Sync,
{
RawManifest::from(self.clone()).save_to(path).await
}
}
impl FromStr for WorkspaceManifest {
type Err = miette::Report;
fn from_str(input: &str) -> Result<Self, Self::Err> {
input
.parse::<RawManifest>()
.into_diagnostic()
.wrap_err(DeserializationError(ManagedFile::Manifest))
.map(WorkspaceManifest::try_from)?
}
}
impl TryInto<String> for WorkspaceManifest {
type Error = toml::ser::Error;
fn try_into(self) -> Result<String, Self::Error> {
toml::to_string_pretty(&RawManifest::from(self))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Workspace {
pub members: Vec<String>,
pub exclude: Option<Vec<String>>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::manifest::RawManifest;
use std::collections::HashMap;
#[test]
fn workspace_manifest_builder() {
let workspace = Workspace {
members: vec!["pkg1".to_string(), "pkg2".to_string()],
exclude: Some(vec!["internal".to_string()]),
};
let manifest = WorkspaceManifest::builder()
.workspace(workspace.clone())
.build();
assert_eq!(manifest.workspace, workspace);
}
#[test]
fn workspace_manifest_from_str() {
let toml = r#"
[workspace]
members = ["pkg1", "pkg2"]
"#;
let manifest = WorkspaceManifest::from_str(toml).expect("should parse");
assert_eq!(manifest.workspace.members, vec!["pkg1", "pkg2"]);
}
#[test]
fn workspace_manifest_from_str_with_exclude() {
let toml = r#"
[workspace]
members = ["packages/*"]
exclude = ["packages/internal"]
"#;
let manifest = WorkspaceManifest::from_str(toml).expect("should parse");
assert_eq!(manifest.workspace.members, vec!["packages/*"]);
assert_eq!(
manifest.workspace.exclude,
Some(vec!["packages/internal".to_string()])
);
}
#[test]
fn workspace_manifest_to_raw_manifest() {
let workspace = Workspace {
members: vec!["pkg1".to_string()],
exclude: None,
};
let manifest = WorkspaceManifest::builder().workspace(workspace).build();
let raw: RawManifest = manifest.into();
assert!(matches!(raw, RawManifest::Canary { .. }));
assert!(raw.workspace().is_some());
assert_eq!(raw.package(), None);
assert_eq!(raw.dependencies(), None);
}
#[test]
fn workspace_manifest_roundtrip() {
let toml = r#"
[workspace]
members = ["pkg1", "pkg2"]
"#;
let manifest = WorkspaceManifest::from_str(toml).expect("should parse");
let serialized: String = manifest.try_into().expect("should serialize");
assert!(serialized.contains("[workspace]"));
assert!(serialized.contains("pkg1"));
assert!(serialized.contains("pkg2"));
}
#[test]
fn workspace_manifest_try_from_raw_missing_workspace_errors() {
let raw = RawManifest::Canary {
package: None,
dependencies: Some(HashMap::new()),
workspace: None,
};
let result = WorkspaceManifest::try_from(raw);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("no workspace manifest")
);
}
}