Rework 'blueprint apply' command and wrap up wiring up validation.

The apply command now works only from a serialized CBOR data (instead of a UPLC syntax). So it is no longer possible to specify arbitrary cbor terms through the CLI. I believe it to be an acceptable limitation for now; especially given that Aiken will never generate blueprints with non-data terms at the interface boundary.
This commit is contained in:
KtorZ 2023-04-07 17:40:40 +02:00
parent bf222a3ea2
commit 4799af3242
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
4 changed files with 95 additions and 26 deletions

View File

@ -59,15 +59,15 @@ pub enum Error {
#[error("I couldn't find a definition corresponding to a reference.")] #[error("I couldn't find a definition corresponding to a reference.")]
#[diagnostic(code("aiken::blueprint::apply::unknown::reference"))] #[diagnostic(code("aiken::blueprint::apply::unknown::reference"))]
#[diagnostic(help( #[diagnostic(help(
"While resolving a schema definition, I stumble upon an unknown reference:\n\n {reference}\n\nThis is unfortunate, but signals that either the reference is invalid or that the correspond schema definition is missing.", "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.",
reference = reference.as_json_pointer() reference = reference.as_json_pointer().if_supports_color(Stdout, |s| s.red())
))] ))]
UnresolvedSchemaReference { reference: Reference }, UnresolvedSchemaReference { reference: Reference },
#[error("I caught a parameter application that seems off.")] #[error("I caught a parameter application that seems off.")]
#[diagnostic(code("aiken::blueprint::apply::mismatch"))] #[diagnostic(code("aiken::blueprint::apply::mismatch"))]
#[diagnostic(help( #[diagnostic(help(
"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, schemas didn't match in this case.\n\nI am expecting something of the shape:\n\n{expected}Which I couldn't match against the following term:\n\n{term}\n\nNote that this may only represent part of a bigger whole.", "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.",
expected = serde_json::to_string_pretty(&schema).unwrap().if_supports_color(Stdout, |s| s.green()), expected = serde_json::to_string_pretty(&schema).unwrap().if_supports_color(Stdout, |s| s.green()),
term = { term = {
let mut buf = vec![]; let mut buf = vec![];
@ -92,6 +92,11 @@ pub enum Error {
found = found.if_supports_color(Stdout, |s| s.red()), found = found.if_supports_color(Stdout, |s| s.red()),
))] ))]
TupleItemsMismatch { expected: usize, found: usize }, TupleItemsMismatch { expected: usize, found: usize },
#[error("I failed to convert some input into a valid parameter")]
#[diagnostic(code("aiken::blueprint::parse::parameter"))]
#[diagnostic(help("{hint}"))]
MalformedParameter { hint: String },
} }
unsafe impl Send for Error {} unsafe impl Send for Error {}

View File

@ -154,11 +154,15 @@ impl Validator {
} }
impl Validator { impl Validator {
pub fn apply(self, arg: &Term<DeBruijn>) -> Result<Self, Error> { pub fn apply(
self,
definitions: &Definitions<Annotated<Schema>>,
arg: &Term<DeBruijn>,
) -> Result<Self, Error> {
match self.parameters.split_first() { match self.parameters.split_first() {
None => Err(Error::NoParametersToApply), None => Err(Error::NoParametersToApply),
Some((head, tail)) => { Some((head, tail)) => {
head.validate(&self.definitions, arg)?; head.validate(definitions, arg)?;
Ok(Self { Ok(Self {
program: self.program.apply_term(arg), program: self.program.apply_term(arg),
parameters: tail.to_vec(), parameters: tail.to_vec(),

View File

@ -391,7 +391,9 @@ where
let applied_validator = let applied_validator =
blueprint.with_validator(title, when_too_many, when_missing, |validator| { blueprint.with_validator(title, when_too_many, when_missing, |validator| {
validator.apply(param).map_err(|e| e.into()) validator
.apply(&blueprint.definitions, param)
.map_err(|e| e.into())
})?; })?;
// Overwrite validator // Overwrite validator

View File

@ -1,18 +1,22 @@
use crate::with_project; use crate::with_project;
use aiken_project::error::Error; use aiken_project::{blueprint, error::Error};
use miette::IntoDiagnostic; use owo_colors::{OwoColorize, Stream::Stderr};
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf, process, rc::Rc};
use uplc::{ use uplc::ast::{Constant, DeBruijn, Term};
ast::{DeBruijn, Term},
parser,
};
/// Apply a parameter to a parameterized validator. /// Apply a parameter to a parameterized validator.
#[derive(clap::Args)] #[derive(clap::Args)]
pub struct Args { pub struct Args {
/// The parameter, as a Plutus Data (CBOR, hex-encoded)
parameter: String,
/// Path to project /// Path to project
directory: Option<PathBuf>, directory: Option<PathBuf>,
/// Output file. Optional, print on stdout when omitted.
#[clap(short, long)]
out: Option<PathBuf>,
/// Name of the validator's module within the project. Optional if there's only one validator. /// Name of the validator's module within the project. Optional if there's only one validator.
#[clap(short, long)] #[clap(short, long)]
module: Option<String>, module: Option<String>,
@ -20,23 +24,58 @@ pub struct Args {
/// Name of the validator within the module. Optional if there's only one validator. /// Name of the validator within the module. Optional if there's only one validator.
#[clap(short, long)] #[clap(short, long)]
validator: Option<String>, validator: Option<String>,
/// The parameter, using high-level UPLC-syntax
parameter: String,
} }
pub fn exec( pub fn exec(
Args { Args {
parameter,
directory, directory,
out,
module, module,
validator, validator,
parameter,
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
let term: Term<DeBruijn> = parser::term(&parameter) eprintln!(
.into_diagnostic()? "{} inputs",
.try_into() " Parsing"
.into_diagnostic()?; .if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold()),
);
let bytes = hex::decode(parameter)
.map_err::<Error, _>(|e| {
blueprint::error::Error::MalformedParameter {
hint: format!("Invalid hex-encoded string: {e}"),
}
.into()
})
.unwrap_or_else(|e| {
println!();
e.report();
process::exit(1)
});
let data = uplc::plutus_data(&bytes)
.map_err::<Error, _>(|e| {
blueprint::error::Error::MalformedParameter {
hint: format!("Invalid Plutus data; malformed CBOR encoding: {e}"),
}
.into()
})
.unwrap_or_else(|e| {
println!();
e.report();
process::exit(1)
});
let term: Term<DeBruijn> = Term::Constant(Rc::new(Constant::Data(data)));
eprintln!(
"{} blueprint",
" Analyzing"
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold()),
);
with_project(directory, |p| { with_project(directory, |p| {
let title = module.as_ref().map(|m| { let title = module.as_ref().map(|m| {
@ -51,16 +90,35 @@ pub fn exec(
let title = title.as_ref().or(validator.as_ref()); let title = title.as_ref().or(validator.as_ref());
eprintln!(
"{} parameter",
" Applying"
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold()),
);
let blueprint = p.apply_parameter(title, &term)?; let blueprint = p.apply_parameter(title, &term)?;
let json = serde_json::to_string_pretty(&blueprint).unwrap(); let json = serde_json::to_string_pretty(&blueprint).unwrap();
fs::write(p.blueprint_path(), json).map_err(|error| { match out {
Error::FileIo { None => {
println!("\n{}\n", json);
Ok(())
}
Some(ref path) => fs::write(path, json).map_err(|error| Error::FileIo {
error, error,
path: p.blueprint_path(), path: p.blueprint_path(),
} }),
.into() }?;
})
eprintln!(
"{}",
" Done"
.if_supports_color(Stderr, |s| s.purple())
.if_supports_color(Stderr, |s| s.bold()),
);
Ok(())
}) })
} }