1use super::{
2 definitions::Reference,
3 schema::{self, Schema},
4};
5use aiken_lang::ast::Span;
6use miette::{Diagnostic, NamedSource};
7use owo_colors::{OwoColorize, Stream::Stderr, Stream::Stdout};
8use pallas_codec::minicbor as cbor;
9use std::{collections::BTreeSet, fmt::Debug};
10use uplc::ast::Constant;
11
12#[derive(Debug, thiserror::Error, Diagnostic)]
13pub enum Error {
14 #[error("{}", error)]
15 #[diagnostic(help("{}", error.help()))]
16 #[diagnostic(code("aiken::blueprint::interface"))]
17 Schema {
18 error: schema::Error,
19 #[label("invalid validator's boundary")]
20 location: Span,
21 #[source_code]
22 source_code: NamedSource<String>,
23 },
24
25 #[error("Invalid or missing project's blueprint file.")]
26 #[diagnostic(code("aiken::blueprint::missing"))]
27 #[diagnostic(help(
28 "Did you forget to {build} the project?",
29 build = "build"
30 .if_supports_color(Stdout, |s| s.purple())
31 .if_supports_color(Stdout, |s| s.bold())
32 ))]
33 InvalidOrMissingFile,
34
35 #[error("I didn't find any validator matching your criteria.")]
36 #[diagnostic(help(
37 "{hint}",
38 hint = hint_validators(known_validators, "Here's a list of matching validators I've found in your project.\nPlease narrow the selection using additional options.")
39 ))]
40 NoValidatorNotFound {
41 known_validators: BTreeSet<(String, String, bool)>,
42 },
43
44 #[error("I found multiple suitable validators and I need you to tell me which one to pick.")]
45 #[diagnostic(help(
46 "{hint}",
47 hint = hint_validators(known_validators, "Here's a list of matching validators I've found in your project.\nPlease narrow the selection using additional options.")
48 ))]
49 MoreThanOneValidatorFound {
50 known_validators: BTreeSet<(String, String, bool)>,
51 },
52
53 #[error("I didn't find any parameters to apply in the given validator.")]
54 #[diagnostic(code("aiken::blueprint::apply::no_parameters"))]
55 NoParametersToApply,
56
57 #[error(
58 "I couldn't compute the address of the given validator because it's parameterized by {} parameter(s)!",
59 n.if_supports_color(Stdout, |s| s.purple())
60 )]
61 #[diagnostic(code("aiken::blueprint::address::parameterized"))]
62 #[diagnostic(help(
63 "I can only compute addresses of validators that are fully applied. For example, a {keyword_spend} validator must have exactly {spend_arity} arguments: a datum, a redeemer and a context. If it has more, they need to be provided beforehand and applied directly to the validator.\n\nApplying parameters change the validator's compiled code, and thus the address. This is why I need you to apply parameters first using the {blueprint_apply_command} command.",
64 keyword_spend = "spend".if_supports_color(Stdout, |s| s.yellow()),
65 spend_arity = "3".if_supports_color(Stdout, |s| s.yellow()),
66 blueprint_apply_command = "blueprint apply".if_supports_color(Stdout, |s| s.purple()),
67 ))]
68 ParameterizedValidator { n: usize },
69
70 #[error(
71 "I couldn't compute the address of the given validator because it's actually a minting policy!"
72 )]
73 #[diagnostic(code("aiken::blueprint::address::minting_validator"))]
74 #[diagnostic(help(
75 "I can only compute addresses for spending validators. Did you mean to call {blueprint_policy_command} instead?",
76 blueprint_policy_command = "blueprint policy".if_supports_color(Stdout, |s| s.purple()),
77 ))]
78 UnexpectedMintingValidator,
79
80 #[error(
81 "I couldn't compute the policyId of the given validator because it's actually a spending policy!"
82 )]
83 #[diagnostic(code("aiken::blueprint::address::spending_validator"))]
84 #[diagnostic(help(
85 "I can only compute policyIds for minting validators. Did you mean to call {blueprint_address_command} instead?",
86 blueprint_address_command = "blueprint address".if_supports_color(Stdout, |s| s.purple()),
87 ))]
88 UnexpectedSpendingValidator,
89
90 #[error("I stumble upon something else than a constant when I expected one.")]
91 #[diagnostic(code("aiken:blueprint::apply::malformed::argument"))]
92 #[diagnostic(help(
93 "Parameters applied to blueprints must be constant; they cannot be lambdas or delayed terms."
94 ))]
95 NonConstantParameter,
96
97 #[error("I couldn't find a definition corresponding to a reference.")]
98 #[diagnostic(code("aiken::blueprint::apply::unknown::reference"))]
99 #[diagnostic(help(
100 "While resolving a schema definition, I stumbled upon an unknown reference:\n\n→ {reference}\n\nThis is unfortunate, but signals that either the reference is invalid or that the corresponding schema definition is missing. Double-check the blueprint for that reference or definition.",
101 reference = reference.as_json_pointer().if_supports_color(Stdout, |s| s.red())
102 ))]
103 UnresolvedSchemaReference { reference: Reference },
104
105 #[error("I caught a parameter application that seems off.")]
106 #[diagnostic(code("aiken::blueprint::apply::mismatch"))]
107 #[diagnostic(help(
108 "When applying parameters to a validator, I control that the shape of the parameter you give me matches what is specified in the blueprint. Unfortunately, it didn't match in this case.\n\nI am looking at the following value:\n\n{term}\n\nbut failed to match it against the specified schema:\n\n{expected}\n\n\nNOTE: this may only represent part of a bigger whole as I am validating the parameter incrementally.",
109 expected = serde_json::to_string_pretty(&schema).unwrap().if_supports_color(Stdout, |s| s.green()),
110 term = {
111 let mut buf = vec![];
112 match term {
113 Constant::Data(data) => {
114 cbor::encode(data, &mut buf).unwrap();
115 cbor::display(&buf).to_string()
116 },
117 _ => term.to_pretty()
118 }
119 }.if_supports_color(Stdout, |s| s.red()),
120 ))]
121 SchemaMismatch { schema: Schema, term: Constant },
122
123 #[error(
124 "I discovered a discrepancy of elements between a given tuple and its declared schema."
125 )]
126 #[diagnostic(code("aiken::blueprint::apply::tuple::mismatch"))]
127 #[diagnostic(help(
128 "When validating a list-like schema with multiple 'items' schemas, I try to match each element of the instance with each item schema (by their position). Hence, I expect to be as many items in the declared schema ({expected}) than there are items in the instance ({found}).",
129 expected = expected.if_supports_color(Stdout, |s| s.green()),
130 found = found.if_supports_color(Stdout, |s| s.red()),
131 ))]
132 TupleItemsMismatch { expected: usize, found: usize },
133
134 #[error("I failed to convert some input into a valid parameter")]
135 #[diagnostic(code("aiken::blueprint::parse::parameter"))]
136 #[diagnostic(help("{hint}"))]
137 MalformedParameter { hint: String },
138}
139
140unsafe impl Send for Error {}
141
142unsafe impl Sync for Error {}
143
144fn hint_validators(known_validators: &BTreeSet<(String, String, bool)>, hint: &str) -> String {
145 let (pad_module, pad_validator) = known_validators.iter().fold(
146 (9, 12),
147 |(module_len, validator_len), (module, validator, _)| {
148 (
149 module_len.max(module.len()),
150 validator_len.max(validator.len()),
151 )
152 },
153 );
154
155 format!(
156 "{hint}\n\n\
157 {:<pad_module$} {:<pad_validator$}\n\
158 {:─<pad_module$}┒ ┎{:─<pad_validator$}\n\
159 {}\n\nFor convenience, I have highlighted in {bold_green} suitable candidates that {has_params}.",
160 "module(s)",
161 "validator(s)",
162 "─",
163 "─",
164 {
165 known_validators
166 .iter()
167 .map(|(module, validator, has_params)| {
168 let title = format!("{module:>pad_module$} . {validator:<pad_validator$}",);
169 if *has_params {
170 title
171 .if_supports_color(Stderr, |s| s.bold())
172 .if_supports_color(Stderr, |s| s.green())
173 .to_string()
174 } else {
175 title
176 }
177 })
178 .collect::<Vec<String>>()
179 .join("\n")
180 },
181 bold_green = "bold green"
182 .if_supports_color(Stderr, |s| s.bold())
183 .if_supports_color(Stderr, |s| s.green()),
184 has_params = "can take parameters"
185 .if_supports_color(Stderr, |s| s.bold())
186 .if_supports_color(Stderr, |s| s.green()),
187 )
188}