1use crate::{blueprint, deps::manifest::Package, package_name::PackageName};
2use aiken_lang::{
3 ast::{self, Span},
4 error::ExtraData,
5 parser::error::ParseError,
6 test_framework::{BenchmarkResult, PropertyTestResult, TestResult, UnitTestResult},
7 tipo,
8};
9use hex::FromHexError;
10use indoc::formatdoc;
11use miette::{
12 Diagnostic, EyreContext, LabeledSpan, MietteHandler, MietteHandlerOpts, NamedSource, RgbColors,
13 SourceCode,
14};
15use ordinal::Ordinal;
16use owo_colors::{
17 OwoColorize,
18 Stream::{Stderr, Stdout},
19};
20use pallas_addresses::ScriptHash;
21use std::{
22 collections::BTreeSet,
23 fmt::{self, Debug, Display},
24 io,
25 path::{Path, PathBuf},
26};
27use zip::result::ZipError;
28
29pub enum TomlLoadingContext {
30 Project,
31 Manifest,
32 Package,
33 Workspace,
34}
35
36impl fmt::Display for TomlLoadingContext {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 match self {
39 TomlLoadingContext::Project => write!(f, "project"),
40 TomlLoadingContext::Manifest => write!(f, "manifest"),
41 TomlLoadingContext::Package => write!(f, "package"),
42 TomlLoadingContext::Workspace => write!(f, "workspace"),
43 }
44 }
45}
46
47#[allow(dead_code)]
48#[derive(thiserror::Error)]
49pub enum Error {
50 #[error("I just found two modules with the same name: '{}'", module.if_supports_color(Stderr, |s| s.yellow()))]
51 DuplicateModule {
52 module: String,
53 first: PathBuf,
54 second: PathBuf,
55 },
56
57 #[error("Some operation on the file-system did fail.")]
58 FileIo { error: io::Error, path: PathBuf },
59
60 #[error("I found some files with incorrectly formatted source code.")]
61 Format { problem_files: Vec<Unformatted> },
62
63 #[error(transparent)]
64 Blueprint(#[from] Box<blueprint::Error>),
65
66 #[error(transparent)]
67 StandardIo(#[from] io::Error),
68
69 #[error(transparent)]
70 Http(#[from] reqwest::Error),
71
72 #[error(transparent)]
73 ZipExtract(#[from] ZipError),
74
75 #[error(transparent)]
76 JoinError(#[from] tokio::task::JoinError),
77
78 #[error(transparent)]
79 Json(#[from] serde_json::Error),
80
81 #[error(transparent)]
82 Module(#[from] ast::Error),
83
84 #[error("I could not load the {ctx} config file.")]
85 TomlLoading {
86 ctx: TomlLoadingContext,
87 path: PathBuf,
88 src: String,
89 named: Box<NamedSource<String>>,
90 location: Option<Span>,
91 help: String,
92 },
93
94 #[error("I couldn't find any 'aiken.toml' manifest in {path}.")]
95 MissingManifest { path: PathBuf },
96
97 #[error("I just found a cycle in module hierarchy!")]
98 ImportCycle { modules: Vec<String> },
99
100 #[error("While parsing files...")]
101 Parse {
102 path: PathBuf,
103 src: String,
104 named: Box<NamedSource<String>>,
105 #[source]
106 error: Box<ParseError>,
107 },
108
109 #[error("While trying to make sense of your code...")]
110 Type {
111 path: PathBuf,
112 src: String,
113 named: NamedSource<String>,
114 #[source]
115 error: Box<tipo::error::Error>,
116 },
117
118 #[error("{name} failed{}", if *verbose { format!("\n{src}") } else { String::new() } )]
119 TestFailure {
120 name: String,
121 path: PathBuf,
122 verbose: bool,
123 src: String,
124 },
125
126 #[error(
127 "I was unable to resolve '{}' for {}/{}",
128 package.version,
129 package.name.owner,
130 package.name.repo
131 )]
132 UnknownPackageVersion { package: Package },
133
134 #[error(
135 "I need to resolve a package {}/{}, but couldn't find it.",
136 package.name.owner,
137 package.name.repo,
138 )]
139 UnableToResolvePackage { package: Package },
140
141 #[error("I couldn't parse the provided stake address.")]
142 MalformedStakeAddress {
143 error: Option<pallas_addresses::Error>,
144 },
145
146 #[error("I couldn't find any exportable function named '{name}' in module '{module}'.")]
147 ExportNotFound { module: String, name: String },
148
149 #[error("No such module '{module}' found in the project.")]
150 ModuleNotFound {
151 module: String,
152 known_modules: Vec<String>,
153 },
154
155 #[error("I located conditional modules under 'env', but no default one!")]
156 NoDefaultEnvironment,
157
158 #[error(
159 "I couldn't find any script matching {} in your blueprint (plutus.json).",
160 script_hash.to_string().if_supports_color(Stdout, |s| s.yellow()),
161 )]
162 ScriptOverrideNotFound {
163 script_hash: ScriptHash,
164 known_scripts: BTreeSet<ScriptHash>,
165 },
166
167 #[error(
168 "I couldn't parse the {} script override argument.",
169 Ordinal(*index as u32 + 1).to_string().if_supports_color(Stdout, |s| s.bold()),
170 )]
171 ScriptOverrideArgumentParseError {
172 index: usize,
173 #[source]
174 error: ScriptOverrideArgumentError,
175 },
176}
177
178#[derive(thiserror::Error, Debug, Diagnostic)]
179pub enum ScriptOverrideArgumentError {
180 #[error(
181 "I couldn't find the left side of the mapping. Are you sure you provided two colon-separated validator qualifiers?\n"
182 )]
183 #[diagnostic(help(
184 "{}",
185 formatdoc!{
186 r#"I am expecting a mapping {from} a validator of the transaction, {to} a replacement in the blueprint as such:
187
188 --script-override "122171C7C82348C420A47AFD8F{colon}7730C913CEEC22524B98E1604A"
189 {underline}
190 {missing}
191 "#,
192 from = "from"
193 .if_supports_color(Stdout, |s| s.bold()),
194 to = "to"
195 .if_supports_color(Stdout, |s| s.bold()),
196 colon = ":"
197 .if_supports_color(Stderr, |s| s.cyan())
198 .if_supports_color(Stderr, |s| s.bold()),
199 underline = "^^^^^^^^^^^^^^^^^^^^^^^^^^"
200 .if_supports_color(Stdout, |s| s.yellow())
201 .if_supports_color(Stdout, |s| s.bold()),
202 missing = "missing left side (from)"
203 .if_supports_color(Stdout, |s| s.yellow())
204 },
205 ))]
206 MissingFrom,
207
208 #[error(
209 "I couldn't find the right side of the mapping. Are you sure you provided two colon-separated validator qualifiers?\n"
210 )]
211 #[diagnostic(help(
212 "{}",
213 formatdoc!{
214 r#"I am expecting a mapping {from} a validator of the transaction, {to} a replacement in the blueprint as such:
215
216 --script-override "122171C7C82348C420A47AFD8F{colon}7730C913CEEC22524B98E1604A"
217 {underline}
218 {missing}
219 "#,
220 from = "from"
221 .if_supports_color(Stdout, |s| s.bold()),
222 to = "to"
223 .if_supports_color(Stdout, |s| s.bold()),
224 colon = ":"
225 .if_supports_color(Stderr, |s| s.cyan())
226 .if_supports_color(Stderr, |s| s.bold()),
227 underline = "^^^^^^^^^^^^^^^^^^^^^^^^^^"
228 .if_supports_color(Stdout, |s| s.yellow())
229 .if_supports_color(Stdout, |s| s.bold()),
230 missing = "missing right side (to)"
231 .if_supports_color(Stdout, |s| s.yellow())
232 },
233 ))]
234 MissingTo,
235
236 #[error(
237 "The origin qualifier ({}) isn't a valid hex-encoded hash digest.\n",
238 "from".if_supports_color(Stdout, |s| s.bold()),
239 )]
240 #[diagnostic(help("{0}"))]
241 InvalidFromHash(FromHexError),
242
243 #[error(
244 "The destination qualifier ({}) isn't a valid hex-encoded hash digest.\n",
245 "to".if_supports_color(Stdout, |s| s.bold()),
246 )]
247 #[diagnostic(help("{0}"))]
248 InvalidToHash(FromHexError),
249
250 #[error(
251 "The origin qualifier ({}) isn't a valid script hash digest.\n",
252 "from".if_supports_color(Stdout, |s| s.bold()),
253 )]
254 #[diagnostic(help(
255 "The size is incorrect. I expected to find {} bytes but found {}.",
256 "28".if_supports_color(Stdout, |s| s.green()),
257 .0.to_string().if_supports_color(Stdout, |s| s.red()),
258 ))]
259 InvalidFromSize(usize),
260
261 #[error(
262 "The destination qualifier ({}) isn't a valid script hash digest.\n",
263 "to".if_supports_color(Stdout, |s| s.bold()),
264 )]
265 #[diagnostic(help(
266 "The size is incorrect. I expected to find {} bytes but found {}.",
267 "28".if_supports_color(Stdout, |s| s.green()),
268 .0.to_string().if_supports_color(Stdout, |s| s.red()),
269 ))]
270 InvalidToSize(usize),
271}
272
273impl Error {
274 pub fn report(&self) {
275 if let Error::TestFailure { verbose, .. } = self {
276 if !verbose {
277 return;
278 }
279 }
280
281 println!("{self:?}")
282 }
283
284 pub fn from_parse_errors(errs: Vec<ParseError>, path: &Path, src: &str) -> Vec<Self> {
285 let mut errors = Vec::with_capacity(errs.len());
286
287 for error in errs {
288 errors.push(Error::Parse {
289 path: path.into(),
290 src: src.to_string(),
291 named: NamedSource::new(path.display().to_string(), src.to_string()).into(),
292 error: error.into(),
293 });
294 }
295
296 errors
297 }
298
299 pub fn from_test_result<U, T>(result: &TestResult<U, T>, verbose: bool) -> Self {
300 let (name, path, src) = match result {
301 TestResult::UnitTestResult(UnitTestResult { test, .. }) => (
302 test.name.to_string(),
303 test.input_path.to_path_buf(),
304 test.program.to_pretty(),
305 ),
306 TestResult::PropertyTestResult(PropertyTestResult { test, .. }) => (
307 test.name.to_string(),
308 test.input_path.to_path_buf(),
309 test.program.to_pretty(),
310 ),
311 TestResult::BenchmarkResult(BenchmarkResult { bench, .. }) => (
312 bench.name.to_string(),
313 bench.input_path.to_path_buf(),
314 bench.program.to_pretty(),
315 ),
316 };
317
318 Error::TestFailure {
319 name,
320 path,
321 src,
322 verbose,
323 }
324 }
325}
326
327impl Debug for Error {
328 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329 default_miette_handler(2)
330 .debug(self, f)
331 .or(Ok(()))
334 }
335}
336
337impl From<Error> for Vec<Error> {
338 fn from(value: Error) -> Self {
339 vec![value]
340 }
341}
342
343impl ExtraData for Error {
344 fn extra_data(&self) -> Option<String> {
345 match self {
346 Error::DuplicateModule { .. }
347 | Error::FileIo { .. }
348 | Error::Format { .. }
349 | Error::StandardIo { .. }
350 | Error::Blueprint { .. }
351 | Error::MissingManifest { .. }
352 | Error::TomlLoading { .. }
353 | Error::ImportCycle { .. }
354 | Error::Parse { .. }
355 | Error::TestFailure { .. }
356 | Error::Http { .. }
357 | Error::ZipExtract { .. }
358 | Error::JoinError { .. }
359 | Error::UnknownPackageVersion { .. }
360 | Error::UnableToResolvePackage { .. }
361 | Error::Json { .. }
362 | Error::MalformedStakeAddress { .. }
363 | Error::Module { .. }
364 | Error::NoDefaultEnvironment
365 | Error::ModuleNotFound { .. }
366 | Error::ExportNotFound { .. }
367 | Error::ScriptOverrideNotFound { .. }
368 | Error::ScriptOverrideArgumentParseError { .. } => None,
369 Error::Type { error, .. } => error.extra_data(),
370 }
371 }
372}
373
374pub trait GetSource {
375 fn path(&self) -> Option<PathBuf>;
376 fn src(&self) -> Option<String>;
377}
378
379impl GetSource for Error {
380 fn path(&self) -> Option<PathBuf> {
381 match self {
382 Error::FileIo { .. }
383 | Error::Format { .. }
384 | Error::StandardIo(_)
385 | Error::Blueprint(_)
386 | Error::ImportCycle { .. }
387 | Error::Http(_)
388 | Error::ZipExtract(_)
389 | Error::JoinError(_)
390 | Error::UnknownPackageVersion { .. }
391 | Error::UnableToResolvePackage { .. }
392 | Error::Json { .. }
393 | Error::MalformedStakeAddress { .. }
394 | Error::ModuleNotFound { .. }
395 | Error::ExportNotFound { .. }
396 | Error::NoDefaultEnvironment
397 | Error::Module { .. }
398 | Error::ScriptOverrideNotFound { .. }
399 | Error::ScriptOverrideArgumentParseError { .. } => None,
400 Error::DuplicateModule { second: path, .. }
401 | Error::MissingManifest { path }
402 | Error::TomlLoading { path, .. }
403 | Error::Parse { path, .. }
404 | Error::Type { path, .. }
405 | Error::TestFailure { path, .. } => Some(path.to_path_buf()),
406 }
407 }
408
409 fn src(&self) -> Option<String> {
410 match self {
411 Error::DuplicateModule { .. }
412 | Error::FileIo { .. }
413 | Error::Format { .. }
414 | Error::StandardIo(_)
415 | Error::Blueprint(_)
416 | Error::MissingManifest { .. }
417 | Error::ImportCycle { .. }
418 | Error::TestFailure { .. }
419 | Error::Http(_)
420 | Error::ZipExtract(_)
421 | Error::JoinError(_)
422 | Error::UnknownPackageVersion { .. }
423 | Error::UnableToResolvePackage { .. }
424 | Error::Json { .. }
425 | Error::MalformedStakeAddress { .. }
426 | Error::NoDefaultEnvironment
427 | Error::ModuleNotFound { .. }
428 | Error::ExportNotFound { .. }
429 | Error::Module { .. }
430 | Error::ScriptOverrideNotFound { .. }
431 | Error::ScriptOverrideArgumentParseError { .. } => None,
432 Error::TomlLoading { src, .. } | Error::Parse { src, .. } | Error::Type { src, .. } => {
433 Some(src.to_string())
434 }
435 }
436 }
437}
438
439impl Diagnostic for Error {
440 fn severity(&self) -> Option<miette::Severity> {
441 Some(miette::Severity::Error)
442 }
443
444 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
445 fn boxed<'a>(s: Box<dyn Display + 'a>) -> Box<dyn Display + 'a> {
446 Box::new(format!(
447 " {} {}",
448 "Error"
449 .if_supports_color(Stdout, |s| s.red())
450 .if_supports_color(Stdout, |s| s.bold()),
451 format!("{s}").if_supports_color(Stdout, |s| s.red())
452 ))
453 }
454
455 match self {
456 Error::DuplicateModule { .. } => Some(boxed(Box::new("aiken::module::duplicate"))),
457 Error::Blueprint(e) => e.code().map(boxed),
458 Error::ImportCycle { .. } => Some(boxed(Box::new("aiken::module::cyclical"))),
459 Error::Parse { .. } => Some(boxed(Box::new("aiken::parser"))),
460 Error::Type { error, .. } => Some(boxed(Box::new(format!(
461 "aiken::check{}",
462 error.code().map(|s| format!("::{s}")).unwrap_or_default()
463 )))),
464 Error::TomlLoading { .. } => Some(boxed(Box::new("aiken::loading::toml"))),
465 Error::TestFailure { path, .. } => Some(boxed(Box::new(path.to_str().unwrap_or("")))),
466 Error::Http(_) => Some(Box::new("aiken::packages::download")),
467 Error::UnknownPackageVersion { .. } => {
468 Some(boxed(Box::new("aiken::packages::resolve")))
469 }
470 Error::UnableToResolvePackage { .. } => {
471 Some(boxed(Box::new("aiken::package::download")))
472 }
473 Error::StandardIo(_)
474 | Error::MissingManifest { .. }
475 | Error::ZipExtract(_)
476 | Error::JoinError(_)
477 | Error::FileIo { .. }
478 | Error::Format { .. }
479 | Error::Json { .. }
480 | Error::MalformedStakeAddress { .. }
481 | Error::ExportNotFound { .. }
482 | Error::ModuleNotFound { .. }
483 | Error::NoDefaultEnvironment
484 | Error::ScriptOverrideNotFound { .. }
485 | Error::ScriptOverrideArgumentParseError { .. } => None,
486 Error::Module(e) => e.code().map(boxed),
487 }
488 }
489
490 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
491 match self {
492 Error::DuplicateModule { first, second, .. } => Some(Box::new(format!(
493 "Rename either of them:\n- {}\n- {}",
494 first.display().if_supports_color(Stderr, |s| s.yellow()),
495 second.display().if_supports_color(Stderr, |s| s.yellow()),
496 ))),
497 Error::FileIo { error, .. } => Some(Box::new(format!("{error}"))),
498 Error::Blueprint(e) => e.help(),
499 Error::ImportCycle { modules } => Some(Box::new(format!(
500 "Try moving the shared code to a separate module that the others can depend on\n- {}",
501 modules.join("\n- ")
502 ))),
503 Error::Parse { error, .. } => error.help(),
504 Error::Type { error, .. } => error.help(),
505 Error::MissingManifest { .. } => Some(Box::new(
506 "Try running `aiken new <REPOSITORY/PROJECT>` to initialise a project with an example manifest.",
507 )),
508 Error::NoDefaultEnvironment => Some(Box::new(
509 "Environment module names are free, but there must be at least one named 'default.ak'.",
510 )),
511 Error::TomlLoading { help, .. } => Some(Box::new(help)),
512
513 Error::ModuleNotFound { known_modules, .. } => Some(Box::new(format!(
514 "I know about the following modules:\n{}",
515 known_modules
516 .iter()
517 .map(|s| format!("─▶ {}", s.if_supports_color(Stdout, |s| s.purple())))
518 .collect::<Vec<_>>()
519 .join("\n")
520 ))),
521 Error::UnknownPackageVersion { .. } => Some(Box::new(
522 "Perhaps, double-check the package repository and version?",
523 )),
524 Error::UnableToResolvePackage { .. } => Some(Box::new(
525 "The network is unavailable and the package isn't in the local cache either. Try connecting to the Internet so I can look it up?",
526 )),
527 Error::Json(error) => Some(Box::new(format!("{error}"))),
528 Error::MalformedStakeAddress { error } => Some(Box::new(format!(
529 "A stake address must be provided either as a base16-encoded string, or as a bech32-encoded string with the 'stake' or 'stake_test' prefix.{hint}",
530 hint = match error {
531 Some(error) => format!("\n\nHere's the error I encountered: {error}"),
532 None => String::new(),
533 }
534 ))),
535 Error::ScriptOverrideNotFound { known_scripts, .. } => Some(Box::new(format!(
536 "These are all the scripts found in your blueprint:\n\n{}",
537 known_scripts
538 .iter()
539 .map(|s| format!("✓ {s}"))
540 .collect::<Vec<_>>()
541 .join("\n"),
542 ))),
543 Error::ScriptOverrideArgumentParseError { error, .. } => error.help(),
544 Error::Module(e) => e.help(),
545 Error::StandardIo(_)
546 | Error::Format { .. }
547 | Error::TestFailure { .. }
548 | Error::Http(_)
549 | Error::ZipExtract(_)
550 | Error::JoinError(_)
551 | Error::ExportNotFound { .. } => None,
552 }
553 }
554
555 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
556 match self {
557 Error::Blueprint(e) => e.labels(),
558 Error::Parse { error, .. } => error.labels(),
559 Error::Type { error, .. } => error.labels(),
560 Error::TomlLoading { location, .. } => {
561 if let Some(location) = location {
562 Some(Box::new(
563 vec![LabeledSpan::new_with_span(None, *location)].into_iter(),
564 ))
565 } else {
566 None
567 }
568 }
569 Error::DuplicateModule { .. }
570 | Error::FileIo { .. }
571 | Error::ImportCycle { .. }
572 | Error::ExportNotFound { .. }
573 | Error::StandardIo(_)
574 | Error::MissingManifest { .. }
575 | Error::Format { .. }
576 | Error::TestFailure { .. }
577 | Error::Http(_)
578 | Error::ZipExtract(_)
579 | Error::JoinError(_)
580 | Error::UnknownPackageVersion { .. }
581 | Error::UnableToResolvePackage { .. }
582 | Error::Json { .. }
583 | Error::MalformedStakeAddress { .. }
584 | Error::NoDefaultEnvironment
585 | Error::ModuleNotFound { .. }
586 | Error::ScriptOverrideNotFound { .. }
587 | Error::ScriptOverrideArgumentParseError { .. } => None,
588
589 Error::Module(e) => e.labels(),
590 }
591 }
592
593 fn source_code(&self) -> Option<&dyn SourceCode> {
594 match self {
595 Error::Blueprint(e) => e.source_code(),
596 Error::Parse { named, .. } => Some(named.as_ref()),
597 Error::Type { named, .. } => Some(named),
598 Error::TomlLoading { named, .. } => Some(named.as_ref()),
599 Error::DuplicateModule { .. }
600 | Error::FileIo { .. }
601 | Error::ImportCycle { .. }
602 | Error::ModuleNotFound { .. }
603 | Error::ExportNotFound { .. }
604 | Error::NoDefaultEnvironment
605 | Error::StandardIo(_)
606 | Error::MissingManifest { .. }
607 | Error::Format { .. }
608 | Error::TestFailure { .. }
609 | Error::Http(_)
610 | Error::ZipExtract(_)
611 | Error::JoinError(_)
612 | Error::UnknownPackageVersion { .. }
613 | Error::UnableToResolvePackage { .. }
614 | Error::Json { .. }
615 | Error::MalformedStakeAddress { .. }
616 | Error::ScriptOverrideNotFound { .. }
617 | Error::ScriptOverrideArgumentParseError { .. } => None,
618 Error::Module(e) => e.source_code(),
619 }
620 }
621
622 fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
623 match self {
624 Error::Blueprint(e) => e.url(),
625 Error::Type { error, .. } => error.url(),
626 Error::DuplicateModule { .. }
627 | Error::FileIo { .. }
628 | Error::ImportCycle { .. }
629 | Error::ModuleNotFound { .. }
630 | Error::ExportNotFound { .. }
631 | Error::Parse { .. }
632 | Error::StandardIo(_)
633 | Error::MissingManifest { .. }
634 | Error::TomlLoading { .. }
635 | Error::Format { .. }
636 | Error::TestFailure { .. }
637 | Error::Http { .. }
638 | Error::ZipExtract { .. }
639 | Error::JoinError { .. }
640 | Error::UnknownPackageVersion { .. }
641 | Error::UnableToResolvePackage { .. }
642 | Error::Json { .. }
643 | Error::MalformedStakeAddress { .. }
644 | Error::NoDefaultEnvironment
645 | Error::ScriptOverrideNotFound { .. }
646 | Error::ScriptOverrideArgumentParseError { .. } => None,
647
648 Error::Module(e) => e.url(),
649 }
650 }
651
652 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
653 match self {
654 Error::Blueprint(e) => e.related(),
655 Error::Type { error, .. } => error.related(),
656 Error::DuplicateModule { .. }
657 | Error::FileIo { .. }
658 | Error::ModuleNotFound { .. }
659 | Error::ExportNotFound { .. }
660 | Error::ImportCycle { .. }
661 | Error::Parse { .. }
662 | Error::StandardIo(_)
663 | Error::NoDefaultEnvironment
664 | Error::MissingManifest { .. }
665 | Error::TomlLoading { .. }
666 | Error::Format { .. }
667 | Error::TestFailure { .. }
668 | Error::Http { .. }
669 | Error::ZipExtract { .. }
670 | Error::JoinError { .. }
671 | Error::UnknownPackageVersion { .. }
672 | Error::UnableToResolvePackage { .. }
673 | Error::Json { .. }
674 | Error::MalformedStakeAddress { .. }
675 | Error::ScriptOverrideNotFound { .. }
676 | Error::ScriptOverrideArgumentParseError { .. } => None,
677 Error::Module(e) => e.related(),
678 }
679 }
680}
681
682#[derive(thiserror::Error)]
683#[allow(clippy::large_enum_variant)]
684pub enum Warning {
685 #[error("You do not have any validators to build!")]
686 NoValidators,
687 #[error("{}", warning)]
688 Type {
689 path: PathBuf,
690 src: String,
691 named: NamedSource<String>,
692 #[source]
693 warning: tipo::error::Warning,
694 },
695 #[error("{name} is already a dependency.")]
696 DependencyAlreadyExists { name: PackageName },
697 #[error("Ignoring file with invalid module name at: {path:?}")]
698 InvalidModuleName { path: PathBuf },
699 #[error("aiken.toml demands compiler version {demanded}, but you are using {current}.")]
700 CompilerVersionMismatch { demanded: String, current: String },
701 #[error("No configuration found for environment {env}.")]
702 NoConfigurationForEnv { env: String },
703 #[error("Suspicious test filter (-m) yielding no test scenarios.")]
704 SuspiciousTestMatch { test: String },
705}
706
707impl ExtraData for Warning {
708 fn extra_data(&self) -> Option<String> {
709 match self {
710 Warning::NoValidators
711 | Warning::DependencyAlreadyExists { .. }
712 | Warning::InvalidModuleName { .. }
713 | Warning::CompilerVersionMismatch { .. }
714 | Warning::NoConfigurationForEnv { .. }
715 | Warning::SuspiciousTestMatch { .. } => None,
716 Warning::Type { warning, .. } => warning.extra_data(),
717 }
718 }
719}
720
721impl GetSource for Warning {
722 fn path(&self) -> Option<PathBuf> {
723 match self {
724 Warning::InvalidModuleName { path } | Warning::Type { path, .. } => Some(path.clone()),
725 Warning::NoValidators
726 | Warning::DependencyAlreadyExists { .. }
727 | Warning::NoConfigurationForEnv { .. }
728 | Warning::CompilerVersionMismatch { .. }
729 | Warning::SuspiciousTestMatch { .. } => None,
730 }
731 }
732
733 fn src(&self) -> Option<String> {
734 match self {
735 Warning::Type { src, .. } => Some(src.clone()),
736 Warning::NoValidators
737 | Warning::InvalidModuleName { .. }
738 | Warning::DependencyAlreadyExists { .. }
739 | Warning::NoConfigurationForEnv { .. }
740 | Warning::CompilerVersionMismatch { .. }
741 | Warning::SuspiciousTestMatch { .. } => None,
742 }
743 }
744}
745
746impl Diagnostic for Warning {
747 fn severity(&self) -> Option<miette::Severity> {
748 Some(miette::Severity::Warning)
749 }
750
751 fn source_code(&self) -> Option<&dyn SourceCode> {
752 match self {
753 Warning::Type { named, .. } => Some(named),
754 Warning::NoValidators
755 | Warning::InvalidModuleName { .. }
756 | Warning::NoConfigurationForEnv { .. }
757 | Warning::DependencyAlreadyExists { .. }
758 | Warning::CompilerVersionMismatch { .. }
759 | Warning::SuspiciousTestMatch { .. } => None,
760 }
761 }
762
763 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
764 match self {
765 Warning::Type { warning, .. } => warning.labels(),
766 Warning::InvalidModuleName { .. }
767 | Warning::NoValidators
768 | Warning::DependencyAlreadyExists { .. }
769 | Warning::NoConfigurationForEnv { .. }
770 | Warning::CompilerVersionMismatch { .. }
771 | Warning::SuspiciousTestMatch { .. } => None,
772 }
773 }
774
775 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
776 match self {
777 Warning::Type { warning, .. } => Some(Box::new(format!(
778 "aiken::check{}",
779 warning.code().map(|s| format!("::{s}")).unwrap_or_default()
780 ))),
781 Warning::NoValidators => Some(Box::new("aiken::check")),
782 Warning::InvalidModuleName { .. } => Some(Box::new("aiken::project::module_name")),
783 Warning::CompilerVersionMismatch { .. } => {
784 Some(Box::new("aiken::project::compiler_version_mismatch"))
785 }
786 Warning::DependencyAlreadyExists { .. } => {
787 Some(Box::new("aiken::packages::already_exists"))
788 }
789 Warning::NoConfigurationForEnv { .. } => {
790 Some(Box::new("aiken::project::config::missing::env"))
791 }
792 Warning::SuspiciousTestMatch { .. } => Some(Box::new("aiken::check::suspicious_match")),
793 }
794 }
795
796 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
797 match self {
798 Warning::Type { warning, .. } => warning.help(),
799 Warning::NoValidators => None,
800 Warning::CompilerVersionMismatch { demanded, .. } => Some(Box::new(format!(
801 "You may want to switch to {}",
802 demanded.if_supports_color(Stdout, |s| s.purple())
803 ))),
804 Warning::InvalidModuleName { .. } => Some(Box::new(
805 "Module names are lowercase, (ascii) alpha-numeric and may contain dashes or underscores.",
806 )),
807 Warning::DependencyAlreadyExists { .. } => Some(Box::new(
808 "If you need to change the version, try 'aiken packages upgrade' instead.",
809 )),
810 Warning::NoConfigurationForEnv { .. } => Some(Box::new(
811 "When configuration keys are missing for a target environment, no 'config' module will be created. This may lead to issues down the line.",
812 )),
813 Warning::SuspiciousTestMatch { test } => Some(Box::new(format!(
814 "Did you mean to match all tests within a specific module? Like so:\n\n╰─▶ {}",
815 format!("-m \"{test}.{{..}}\"").if_supports_color(Stderr, |s| s.bold()),
816 ))),
817 }
818 }
819}
820
821impl Warning {
822 pub fn from_type_warning(warning: tipo::error::Warning, path: PathBuf, src: String) -> Warning {
823 Warning::Type {
824 path: path.clone(),
825 warning,
826 src: src.clone(),
827 named: NamedSource::new(path.display().to_string(), src),
828 }
829 }
830
831 pub fn report(&self) {
832 eprintln!("{self:?}")
833 }
834}
835
836impl Debug for Warning {
837 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
838 default_miette_handler(1)
839 .debug(
840 &DisplayWarning {
841 title: &self.to_string(),
842 source_code: self.source_code(),
843 labels: self.labels().map(|ls| ls.collect()),
844 help: self.help().map(|s| s.to_string()),
845 },
846 f,
847 )
848 .or(Ok(()))
851 }
852}
853
854#[derive(thiserror::Error)]
855#[error("{}", title.if_supports_color(Stderr, |s| s.yellow()))]
856struct DisplayWarning<'a> {
857 title: &'a str,
858 source_code: Option<&'a dyn miette::SourceCode>,
859 labels: Option<Vec<LabeledSpan>>,
860 help: Option<String>,
861}
862
863impl Diagnostic for DisplayWarning<'_> {
864 fn severity(&self) -> Option<miette::Severity> {
865 Some(miette::Severity::Warning)
866 }
867
868 fn source_code(&self) -> Option<&dyn SourceCode> {
869 self.source_code
870 }
871
872 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
873 self.labels
874 .as_ref()
875 .map(|ls| ls.iter().cloned())
876 .map(Box::new)
877 .map(|b| b as Box<dyn Iterator<Item = LabeledSpan>>)
878 }
879
880 fn code<'b>(&'b self) -> Option<Box<dyn Display + 'b>> {
881 None
882 }
883
884 fn help<'b>(&'b self) -> Option<Box<dyn Display + 'b>> {
885 self.help
886 .as_ref()
887 .map(Box::new)
888 .map(|b| b as Box<dyn Display + 'b>)
889 }
890}
891
892impl Debug for DisplayWarning<'_> {
893 fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
894 unreachable!("Display warning are never shown directly.");
895 }
896}
897
898#[derive(Debug, PartialEq, Eq)]
899pub struct Unformatted {
900 pub source: PathBuf,
901 pub destination: PathBuf,
902 pub input: String,
903 pub output: String,
904}
905
906fn default_miette_handler(context_lines: usize) -> MietteHandler {
907 MietteHandlerOpts::new()
908 .rgb_colors(RgbColors::Never)
910 .unicode(true)
912 .terminal_links(true)
913 .context_lines(context_lines)
914 .build()
915}