miden_project/ast/
workspace.rs1use super::{
2 parsing::{MaybeInherit, SetSourceId, Validate},
3 *,
4};
5use crate::{SourceId, Span, Uri};
6
7#[derive(Debug, Clone)]
9#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
11pub struct WorkspaceTable {
12 #[cfg_attr(feature = "serde", serde(default))]
14 pub members: Vec<Span<Uri>>,
15 #[cfg_attr(feature = "serde", serde(default))]
17 pub package: PackageDetail,
18 #[cfg_attr(feature = "serde", serde(flatten, default))]
20 pub config: PackageConfig,
21}
22
23impl SetSourceId for WorkspaceTable {
24 fn set_source_id(&mut self, source_id: SourceId) {
25 let Self { members, package, config } = self;
26 members.set_source_id(source_id);
27 package.set_source_id(source_id);
28 config.set_source_id(source_id);
29 }
30}
31
32#[derive(Debug, Clone)]
34#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
36pub struct WorkspaceFile {
37 #[cfg_attr(feature = "serde", serde(skip, default))]
39 pub source_file: Option<Arc<SourceFile>>,
40 pub workspace: WorkspaceTable,
42 #[cfg_attr(
44 feature = "serde",
45 serde(
46 default,
47 rename = "profile",
48 deserialize_with = "profile::deserialize_profiles_table",
49 skip_serializing_if = "Vec::is_empty"
50 )
51 )]
52 pub profiles: Vec<Profile>,
53}
54
55impl WorkspaceFile {
57 #[cfg(feature = "serde")]
65 pub fn parse(source: Arc<SourceFile>) -> Result<Self, Report> {
66 use parsing::{SetSourceId, Validate};
67
68 let source_id = source.id();
69
70 let mut workspace = toml::from_str::<Self>(source.as_str()).map_err(|err| {
72 let span = err
73 .span()
74 .map(|span| {
75 let start = span.start as u32;
76 let end = span.end as u32;
77 SourceSpan::new(source_id, start..end)
78 })
79 .unwrap_or_default();
80 Report::from(ProjectFileError::ParseError {
81 message: err.message().to_string(),
82 source_file: source.clone(),
83 span,
84 })
85 })?;
86
87 workspace.source_file = Some(source.clone());
88 workspace.set_source_id(source_id);
89 workspace.validate(source)?;
90
91 Ok(workspace)
92 }
93}
94
95impl SetSourceId for WorkspaceFile {
96 fn set_source_id(&mut self, source_id: SourceId) {
97 let Self { source_file: _, workspace, profiles } = self;
98 workspace.set_source_id(source_id);
99 profiles.set_source_id(source_id);
100 }
101}
102
103impl Validate for WorkspaceFile {
104 fn validate(&self, source: Arc<SourceFile>) -> Result<(), Report> {
105 if let Some(span) = self.workspace.package.version.as_ref().and_then(|v| {
107 if matches!(v.inner(), MaybeInherit::Inherit) {
108 Some(v.span())
109 } else {
110 None
111 }
112 }) {
113 return Err(ProjectFileError::NotAWorkspace { source_file: source, span }.into());
114 }
115
116 if let Some(description) = self.workspace.package.description.as_ref()
117 && matches!(description.inner(), MaybeInherit::Inherit)
118 {
119 return Err(ProjectFileError::NotAWorkspace {
120 source_file: source,
121 span: description.span(),
122 }
123 .into());
124 }
125
126 for dependency in self.workspace.config.dependencies.values() {
128 if dependency.inherits_workspace_version() {
129 let label = if dependency.version().is_none()
130 && !dependency.is_git()
131 && !dependency.is_path()
132 {
133 "expected 'version', 'digest', or 'path' here"
134 } else {
135 "cannot use the 'workspace' option in a workspace-level dependency spec"
136 };
137 return Err(Report::from(ProjectFileError::InvalidWorkspaceDependency {
138 source_file: source.clone(),
139 label: Label::new(dependency.span(), label),
140 }));
141 }
142 }
143
144 Ok(())
145 }
146}