aiken_project/blueprint/
error.rs

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}