1+ use crate :: core:: { PackageName , TargetKind } ;
2+ use camino:: Utf8PathBuf ;
13use smol_str:: SmolStr ;
24use thiserror:: Error ;
35use toml_edit:: Document ;
6+ use url:: ParseError as UrlParseError ;
47
58use super :: ManifestDiagnosticData ;
69use super :: diagnostic:: resolve_anchor_in_doc;
7- use super :: { ManifestDiagnosticAnchor , ManifestRelatedAnchor , ManifestRelatedLocation } ;
10+ use super :: {
11+ ManifestDependencyTable , ManifestDiagnosticAnchor , ManifestRelatedAnchor ,
12+ ManifestRelatedLocation ,
13+ } ;
814
915/// Typed manifest validation errors that carry semantic anchors for diagnostic span resolution.
1016///
@@ -19,6 +25,32 @@ pub enum ManifestSemanticError {
1925 ProfileInheritanceInvalid ( #[ from] ProfileInheritanceInvalid ) ,
2026 #[ error( transparent) ]
2127 ProfileCairoConflict ( #[ from] ProfileCairoConflict ) ,
28+ #[ error( transparent) ]
29+ DependencyWorkspaceNotFound ( #[ from] DependencyWorkspaceNotFound ) ,
30+ #[ error( transparent) ]
31+ DependencyGitRefWithoutGit ( #[ from] DependencyGitRefWithoutGit ) ,
32+ #[ error( transparent) ]
33+ DependencyGitReferenceAmbiguous ( #[ from] DependencyGitReferenceAmbiguous ) ,
34+ #[ error( transparent) ]
35+ DependencySourceMissing ( #[ from] DependencySourceMissing ) ,
36+ #[ error( transparent) ]
37+ DependencyGitPathAmbiguous ( #[ from] DependencyGitPathAmbiguous ) ,
38+ #[ error( transparent) ]
39+ DependencyGitRegistryAmbiguous ( #[ from] DependencyGitRegistryAmbiguous ) ,
40+ #[ error( transparent) ]
41+ PatchNotInWorkspaceRoot ( #[ from] PatchNotInWorkspaceRoot ) ,
42+ #[ error( transparent) ]
43+ PatchSourceConflict ( #[ from] PatchSourceConflict ) ,
44+ #[ error( transparent) ]
45+ PatchSourceInvalidUrl ( #[ from] PatchSourceInvalidUrl ) ,
46+ #[ error( transparent) ]
47+ ReadmePathInvalid ( #[ from] ReadmePathInvalid ) ,
48+ #[ error( transparent) ]
49+ LicensePathInvalid ( #[ from] LicensePathInvalid ) ,
50+ #[ error( transparent) ]
51+ DuplicateDefaultTargetDefinition ( #[ from] DuplicateDefaultTargetDefinition ) ,
52+ #[ error( transparent) ]
53+ DuplicateNamedTargetDefinition ( #[ from] DuplicateNamedTargetDefinition ) ,
2254}
2355
2456impl ManifestSemanticError {
@@ -54,12 +86,29 @@ impl ManifestSemanticError {
5486 Self :: ProfileNameInvalid ( e) => Some ( e. primary_anchor ( ) ) ,
5587 Self :: ProfileInheritanceInvalid ( e) => Some ( e. primary_anchor ( ) ) ,
5688 Self :: ProfileCairoConflict ( e) => Some ( e. primary_anchor ( ) ) ,
89+ Self :: DependencyWorkspaceNotFound ( e) => Some ( e. primary_anchor ( ) ) ,
90+ Self :: DependencyGitRefWithoutGit ( e) => Some ( e. primary_anchor ( ) ) ,
91+ Self :: DependencyGitReferenceAmbiguous ( e) => Some ( e. primary_anchor ( ) ) ,
92+ Self :: DependencySourceMissing ( e) => Some ( e. primary_anchor ( ) ) ,
93+ Self :: DependencyGitPathAmbiguous ( e) => Some ( e. primary_anchor ( ) ) ,
94+ Self :: DependencyGitRegistryAmbiguous ( e) => Some ( e. primary_anchor ( ) ) ,
95+ Self :: PatchNotInWorkspaceRoot ( e) => Some ( e. primary_anchor ( ) ) ,
96+ Self :: PatchSourceConflict ( e) => Some ( e. primary_anchor ( ) ) ,
97+ Self :: PatchSourceInvalidUrl ( e) => Some ( e. primary_anchor ( ) ) ,
98+ Self :: ReadmePathInvalid ( e) => e. primary_anchor ( ) ,
99+ Self :: LicensePathInvalid ( e) => e. primary_anchor ( ) ,
100+ Self :: DuplicateDefaultTargetDefinition ( e) => Some ( e. primary_anchor ( ) ) ,
101+ Self :: DuplicateNamedTargetDefinition ( e) => Some ( e. primary_anchor ( ) ) ,
57102 }
58103 }
59104
60105 fn related_anchors ( & self ) -> Vec < ManifestRelatedAnchor > {
61106 match self {
62107 Self :: ProfileCairoConflict ( e) => e. related_anchors ( ) ,
108+ Self :: DependencyGitReferenceAmbiguous ( e) => e. related_anchors ( ) ,
109+ Self :: DependencyGitPathAmbiguous ( e) => e. related_anchors ( ) ,
110+ Self :: DependencyGitRegistryAmbiguous ( e) => e. related_anchors ( ) ,
111+ Self :: PatchSourceConflict ( e) => e. related_anchors ( ) ,
63112 _ => vec ! [ ] ,
64113 }
65114 }
@@ -135,3 +184,306 @@ impl ProfileCairoConflict {
135184 } ]
136185 }
137186}
187+
188+ // ── Dependency errors ─────────────────────────────────────────────────────────
189+
190+ #[ derive( Debug , Clone , Error ) ]
191+ #[ error( "dependency `{name}` not found in workspace" ) ]
192+ pub struct DependencyWorkspaceNotFound {
193+ pub name : PackageName ,
194+ pub table : ManifestDependencyTable ,
195+ }
196+
197+ impl DependencyWorkspaceNotFound {
198+ pub fn new ( name : PackageName , table : ManifestDependencyTable ) -> Self {
199+ Self { name, table }
200+ }
201+
202+ fn primary_anchor ( & self ) -> ManifestDiagnosticAnchor {
203+ ManifestDiagnosticAnchor :: dependency ( self . table . clone ( ) , self . name . clone ( ) )
204+ }
205+ }
206+
207+ #[ derive( Debug , Clone , Error ) ]
208+ #[ error( "dependency ({name}) is non-Git, but provides `branch`, `tag` or `rev`" ) ]
209+ pub struct DependencyGitRefWithoutGit {
210+ pub name : PackageName ,
211+ pub anchor : ManifestDiagnosticAnchor ,
212+ pub field : & ' static str ,
213+ }
214+
215+ impl DependencyGitRefWithoutGit {
216+ pub fn new ( name : PackageName , anchor : ManifestDiagnosticAnchor , field : & ' static str ) -> Self {
217+ Self {
218+ name,
219+ anchor,
220+ field,
221+ }
222+ }
223+
224+ fn primary_anchor ( & self ) -> ManifestDiagnosticAnchor {
225+ self . anchor . clone ( ) . with_field ( self . field )
226+ }
227+ }
228+
229+ #[ derive( Debug , Clone , Error ) ]
230+ #[ error(
231+ "dependency ({name}) specification is ambiguous, only one of `branch`, `tag` or `rev` is allowed"
232+ ) ]
233+ pub struct DependencyGitReferenceAmbiguous {
234+ pub name : PackageName ,
235+ pub anchor : ManifestDiagnosticAnchor ,
236+ pub fields : Vec < & ' static str > ,
237+ }
238+
239+ impl DependencyGitReferenceAmbiguous {
240+ pub fn new (
241+ name : PackageName ,
242+ anchor : ManifestDiagnosticAnchor ,
243+ fields : Vec < & ' static str > ,
244+ ) -> Self {
245+ Self {
246+ name,
247+ anchor,
248+ fields,
249+ }
250+ }
251+
252+ fn primary_anchor ( & self ) -> ManifestDiagnosticAnchor {
253+ self . anchor
254+ . clone ( )
255+ . with_field ( self . fields . first ( ) . copied ( ) . unwrap_or ( "branch" ) )
256+ }
257+
258+ fn related_anchors ( & self ) -> Vec < ManifestRelatedAnchor > {
259+ self . fields
260+ . iter ( )
261+ . skip ( 1 )
262+ . map ( |field| ManifestRelatedAnchor {
263+ message : "conflicting Git reference" . to_string ( ) ,
264+ anchor : self . anchor . clone ( ) . with_field ( field) ,
265+ } )
266+ . collect ( )
267+ }
268+ }
269+
270+ #[ derive( Debug , Clone , Error ) ]
271+ #[ error(
272+ "dependency ({name}) must be specified providing a local path, Git repository, or version to use"
273+ ) ]
274+ pub struct DependencySourceMissing {
275+ pub name : PackageName ,
276+ pub anchor : ManifestDiagnosticAnchor ,
277+ }
278+
279+ impl DependencySourceMissing {
280+ pub fn new ( name : PackageName , anchor : ManifestDiagnosticAnchor ) -> Self {
281+ Self { name, anchor }
282+ }
283+
284+ fn primary_anchor ( & self ) -> ManifestDiagnosticAnchor {
285+ self . anchor . clone ( )
286+ }
287+ }
288+
289+ #[ derive( Debug , Clone , Error ) ]
290+ #[ error( "dependency ({name}) specification is ambiguous, only one of `git` or `path` is allowed" ) ]
291+ pub struct DependencyGitPathAmbiguous {
292+ pub name : PackageName ,
293+ pub anchor : ManifestDiagnosticAnchor ,
294+ }
295+
296+ impl DependencyGitPathAmbiguous {
297+ pub fn new ( name : PackageName , anchor : ManifestDiagnosticAnchor ) -> Self {
298+ Self { name, anchor }
299+ }
300+
301+ fn primary_anchor ( & self ) -> ManifestDiagnosticAnchor {
302+ self . anchor . clone ( ) . with_field ( "git" )
303+ }
304+
305+ fn related_anchors ( & self ) -> Vec < ManifestRelatedAnchor > {
306+ vec ! [ ManifestRelatedAnchor {
307+ message: "conflicts with this field" . to_string( ) ,
308+ anchor: self . anchor. clone( ) . with_field( "path" ) ,
309+ } ]
310+ }
311+ }
312+
313+ #[ derive( Debug , Clone , Error ) ]
314+ #[ error(
315+ "dependency ({name}) specification is ambiguous, only one of `git` or `registry` is allowed"
316+ ) ]
317+ pub struct DependencyGitRegistryAmbiguous {
318+ pub name : PackageName ,
319+ pub anchor : ManifestDiagnosticAnchor ,
320+ }
321+
322+ impl DependencyGitRegistryAmbiguous {
323+ pub fn new ( name : PackageName , anchor : ManifestDiagnosticAnchor ) -> Self {
324+ Self { name, anchor }
325+ }
326+
327+ fn primary_anchor ( & self ) -> ManifestDiagnosticAnchor {
328+ self . anchor . clone ( ) . with_field ( "git" )
329+ }
330+
331+ fn related_anchors ( & self ) -> Vec < ManifestRelatedAnchor > {
332+ vec ! [ ManifestRelatedAnchor {
333+ message: "conflicts with this field" . to_string( ) ,
334+ anchor: self . anchor. clone( ) . with_field( "registry" ) ,
335+ } ]
336+ }
337+ }
338+
339+ // ── Patch errors ──────────────────────────────────────────────────────────────
340+
341+ #[ derive( Debug , Clone , Error ) ]
342+ #[ error(
343+ "the `[patch]` section can only be defined in the workspace root manifests\n \
344+ section found in manifest: `{manifest_path}`\n \
345+ workspace root manifest: `{workspace_manifest_path}`"
346+ ) ]
347+ pub struct PatchNotInWorkspaceRoot {
348+ pub manifest_path : Utf8PathBuf ,
349+ pub workspace_manifest_path : Utf8PathBuf ,
350+ }
351+
352+ impl PatchNotInWorkspaceRoot {
353+ pub fn new ( manifest_path : Utf8PathBuf , workspace_manifest_path : Utf8PathBuf ) -> Self {
354+ Self {
355+ manifest_path,
356+ workspace_manifest_path,
357+ }
358+ }
359+
360+ fn primary_anchor ( & self ) -> ManifestDiagnosticAnchor {
361+ ManifestDiagnosticAnchor :: patch_root ( )
362+ }
363+ }
364+
365+ #[ derive( Debug , Clone , Error ) ]
366+ #[ error( "the `[patch]` section cannot specify both `{source_a}` and `{source_b}`" ) ]
367+ pub struct PatchSourceConflict {
368+ pub source_a : SmolStr ,
369+ pub source_b : SmolStr ,
370+ }
371+
372+ impl PatchSourceConflict {
373+ pub fn new ( source_a : impl Into < SmolStr > , source_b : impl Into < SmolStr > ) -> Self {
374+ Self {
375+ source_a : source_a. into ( ) ,
376+ source_b : source_b. into ( ) ,
377+ }
378+ }
379+
380+ fn primary_anchor ( & self ) -> ManifestDiagnosticAnchor {
381+ ManifestDiagnosticAnchor :: patch_source ( self . source_a . clone ( ) )
382+ }
383+
384+ fn related_anchors ( & self ) -> Vec < ManifestRelatedAnchor > {
385+ vec ! [ ManifestRelatedAnchor {
386+ message: "conflicts with this source" . to_string( ) ,
387+ anchor: ManifestDiagnosticAnchor :: patch_source( self . source_b. clone( ) ) ,
388+ } ]
389+ }
390+ }
391+
392+ #[ derive( Debug , Clone , Error ) ]
393+ #[ error( "failed to parse `{raw_source}` as patch source url" ) ]
394+ pub struct PatchSourceInvalidUrl {
395+ pub raw_source : SmolStr ,
396+ #[ source]
397+ pub cause : UrlParseError ,
398+ }
399+
400+ impl PatchSourceInvalidUrl {
401+ pub fn new ( raw_source : impl Into < SmolStr > , cause : UrlParseError ) -> Self {
402+ Self {
403+ raw_source : raw_source. into ( ) ,
404+ cause,
405+ }
406+ }
407+
408+ fn primary_anchor ( & self ) -> ManifestDiagnosticAnchor {
409+ ManifestDiagnosticAnchor :: patch_source ( self . raw_source . clone ( ) )
410+ }
411+ }
412+
413+ // ── File-path errors ──────────────────────────────────────────────────────────
414+
415+ #[ derive( Debug , Clone , Error ) ]
416+ #[ error( "failed to find readme at {path}" ) ]
417+ pub struct ReadmePathInvalid {
418+ pub path : Utf8PathBuf ,
419+ pub anchor : Option < ManifestDiagnosticAnchor > ,
420+ }
421+
422+ impl ReadmePathInvalid {
423+ pub fn new ( path : Utf8PathBuf , anchor : Option < ManifestDiagnosticAnchor > ) -> Self {
424+ Self { path, anchor }
425+ }
426+
427+ fn primary_anchor ( & self ) -> Option < ManifestDiagnosticAnchor > {
428+ self . anchor . clone ( )
429+ }
430+ }
431+
432+ #[ derive( Debug , Clone , Error ) ]
433+ #[ error( "failed to find license at {path}" ) ]
434+ pub struct LicensePathInvalid {
435+ pub path : Utf8PathBuf ,
436+ pub anchor : Option < ManifestDiagnosticAnchor > ,
437+ }
438+
439+ impl LicensePathInvalid {
440+ pub fn new ( path : Utf8PathBuf , anchor : Option < ManifestDiagnosticAnchor > ) -> Self {
441+ Self { path, anchor }
442+ }
443+
444+ fn primary_anchor ( & self ) -> Option < ManifestDiagnosticAnchor > {
445+ self . anchor . clone ( )
446+ }
447+ }
448+
449+ // ── Target errors ─────────────────────────────────────────────────────────────
450+
451+ #[ derive( Debug , Clone , Error ) ]
452+ #[ error(
453+ "manifest contains duplicate target definitions `{kind}`, \
454+ consider explicitly naming targets with the `name` field"
455+ ) ]
456+ pub struct DuplicateDefaultTargetDefinition {
457+ pub kind : TargetKind ,
458+ pub name : SmolStr ,
459+ }
460+
461+ impl DuplicateDefaultTargetDefinition {
462+ pub fn new ( kind : TargetKind , name : SmolStr ) -> Self {
463+ Self { kind, name }
464+ }
465+
466+ fn primary_anchor ( & self ) -> ManifestDiagnosticAnchor {
467+ ManifestDiagnosticAnchor :: target ( self . kind . clone ( ) , Some ( self . name . clone ( ) ) )
468+ }
469+ }
470+
471+ #[ derive( Debug , Clone , Error ) ]
472+ #[ error(
473+ "manifest contains duplicate target definitions `{kind} ({name})`, \
474+ use different target names to resolve the conflict"
475+ ) ]
476+ pub struct DuplicateNamedTargetDefinition {
477+ pub kind : TargetKind ,
478+ pub name : SmolStr ,
479+ }
480+
481+ impl DuplicateNamedTargetDefinition {
482+ pub fn new ( kind : TargetKind , name : SmolStr ) -> Self {
483+ Self { kind, name }
484+ }
485+
486+ fn primary_anchor ( & self ) -> ManifestDiagnosticAnchor {
487+ ManifestDiagnosticAnchor :: target ( self . kind . clone ( ) , Some ( self . name . clone ( ) ) )
488+ }
489+ }
0 commit comments