Implement blueprint schema & data validations.
This commit is contained in:
parent
ee220881b6
commit
d58ef1a079
|
@ -53,6 +53,12 @@ impl<T> Definitions<T> {
|
||||||
self.inner.remove(reference.as_key());
|
self.inner.remove(reference.as_key());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Insert a new definition
|
||||||
|
pub fn insert(&mut self, reference: &Reference, schema: T) {
|
||||||
|
self.inner
|
||||||
|
.insert(reference.as_key().to_string(), Some(schema));
|
||||||
|
}
|
||||||
|
|
||||||
/// Register a new definition only if it doesn't exist. This uses a strategy of
|
/// Register a new definition only if it doesn't exist. This uses a strategy of
|
||||||
/// mark-and-insert such that recursive definitions are only built once.
|
/// mark-and-insert such that recursive definitions are only built once.
|
||||||
pub fn register<F, E>(
|
pub fn register<F, E>(
|
||||||
|
|
|
@ -6,6 +6,7 @@ use aiken_lang::ast::Span;
|
||||||
use miette::{Diagnostic, NamedSource};
|
use miette::{Diagnostic, NamedSource};
|
||||||
use owo_colors::{OwoColorize, Stream::Stdout};
|
use owo_colors::{OwoColorize, Stream::Stdout};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use uplc::ast::{DeBruijn, Term};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Diagnostic)]
|
#[derive(Debug, thiserror::Error, Diagnostic)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
@ -65,11 +66,25 @@ pub enum Error {
|
||||||
#[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 the following:\n\n{}But I've inferred the following schema from your input:\n\n{}",
|
"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{}Which I couldn't match against the following term:\n\n{}\n\nNote that this may only represent part of a bigger whole.",
|
||||||
serde_json::to_string_pretty(&expected).unwrap().if_supports_color(Stdout, |s| s.green()),
|
serde_json::to_string_pretty(&schema).unwrap().if_supports_color(Stdout, |s| s.green()),
|
||||||
serde_json::to_string_pretty(&inferred).unwrap().if_supports_color(Stdout, |s| s.red()),
|
term.to_pretty().if_supports_color(Stdout, |s| s.red()),
|
||||||
))]
|
))]
|
||||||
SchemaMismatch { expected: Schema, inferred: Schema },
|
SchemaMismatch {
|
||||||
|
schema: Schema,
|
||||||
|
term: Term<DeBruijn>,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error(
|
||||||
|
"I discovered a discrepancy of elements between a given tuple and its declared schema."
|
||||||
|
)]
|
||||||
|
#[diagnostic(code("aiken::blueprint::apply::tuple::mismatch"))]
|
||||||
|
#[diagnostic(help(
|
||||||
|
"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}).",
|
||||||
|
expected = expected.if_supports_color(Stdout, |s| s.green()),
|
||||||
|
found = found.if_supports_color(Stdout, |s| s.red()),
|
||||||
|
))]
|
||||||
|
TupleItemsMismatch { expected: usize, found: usize },
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for Error {}
|
unsafe impl Send for Error {}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
pub mod definitions;
|
pub mod definitions;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod parameter;
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
pub mod validator;
|
pub mod validator;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,478 @@
|
||||||
|
use super::{
|
||||||
|
definitions::{Definitions, Reference},
|
||||||
|
error::Error,
|
||||||
|
schema::{Annotated, Constructor, Data, Declaration, Items, Schema},
|
||||||
|
};
|
||||||
|
use std::{iter, ops::Deref, rc::Rc};
|
||||||
|
use uplc::{
|
||||||
|
ast::{Constant, Data as UplcData, DeBruijn, Term},
|
||||||
|
PlutusData,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct Parameter {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub title: Option<String>,
|
||||||
|
|
||||||
|
pub schema: Reference,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Instance = Term<DeBruijn>;
|
||||||
|
|
||||||
|
impl From<Reference> for Parameter {
|
||||||
|
fn from(schema: Reference) -> Parameter {
|
||||||
|
Parameter {
|
||||||
|
title: None,
|
||||||
|
schema,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parameter {
|
||||||
|
pub fn validate(
|
||||||
|
&self,
|
||||||
|
definitions: &Definitions<Annotated<Schema>>,
|
||||||
|
term: &Instance,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let schema = &definitions
|
||||||
|
.lookup(&self.schema)
|
||||||
|
.map(Ok)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
Err(Error::UnresolvedSchemaReference {
|
||||||
|
reference: self.schema.clone(),
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
.annotated;
|
||||||
|
|
||||||
|
validate_schema(schema, definitions, term)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_schema(
|
||||||
|
schema: &Schema,
|
||||||
|
definitions: &Definitions<Annotated<Schema>>,
|
||||||
|
term: &Instance,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
match schema {
|
||||||
|
Schema::Data(data) => validate_data(data, definitions, term),
|
||||||
|
|
||||||
|
Schema::Unit => expect_unit(term),
|
||||||
|
|
||||||
|
Schema::Integer => expect_integer(term),
|
||||||
|
|
||||||
|
Schema::Bytes => expect_bytes(term),
|
||||||
|
|
||||||
|
Schema::String => expect_string(term),
|
||||||
|
|
||||||
|
Schema::Boolean => expect_boolean(term),
|
||||||
|
|
||||||
|
Schema::Pair(left, right) => {
|
||||||
|
let (term_left, term_right) = expect_pair(term)?;
|
||||||
|
|
||||||
|
let left =
|
||||||
|
left.schema(definitions)
|
||||||
|
.ok_or_else(|| Error::UnresolvedSchemaReference {
|
||||||
|
reference: left.reference().unwrap().clone(),
|
||||||
|
})?;
|
||||||
|
validate_schema(left, definitions, &term_left)?;
|
||||||
|
|
||||||
|
let right =
|
||||||
|
right
|
||||||
|
.schema(definitions)
|
||||||
|
.ok_or_else(|| Error::UnresolvedSchemaReference {
|
||||||
|
reference: right.reference().unwrap().clone(),
|
||||||
|
})?;
|
||||||
|
validate_schema(right, definitions, &term_right)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
Schema::List(Items::One(item)) => {
|
||||||
|
let terms = expect_list(term)?;
|
||||||
|
|
||||||
|
let item =
|
||||||
|
item.schema(definitions)
|
||||||
|
.ok_or_else(|| Error::UnresolvedSchemaReference {
|
||||||
|
reference: item.reference().unwrap().clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
for ref term in terms {
|
||||||
|
validate_schema(item, definitions, term)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
Schema::List(Items::Many(items)) => {
|
||||||
|
let terms = expect_list(term)?;
|
||||||
|
|
||||||
|
let items = items
|
||||||
|
.iter()
|
||||||
|
.map(|item| {
|
||||||
|
item.schema(definitions)
|
||||||
|
.ok_or_else(|| Error::UnresolvedSchemaReference {
|
||||||
|
reference: item.reference().unwrap().clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
if terms.len() != items.len() {
|
||||||
|
return Err(Error::TupleItemsMismatch {
|
||||||
|
expected: items.len(),
|
||||||
|
found: terms.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (item, ref term) in iter::zip(items, terms) {
|
||||||
|
validate_schema(item, definitions, term)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_data(
|
||||||
|
data: &Data,
|
||||||
|
definitions: &Definitions<Annotated<Schema>>,
|
||||||
|
term: &Instance,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
match data {
|
||||||
|
Data::Opaque => expect_data(term),
|
||||||
|
|
||||||
|
Data::Integer => expect_data_integer(term),
|
||||||
|
|
||||||
|
Data::Bytes => expect_data_bytes(term),
|
||||||
|
|
||||||
|
Data::List(Items::One(item)) => {
|
||||||
|
let terms = expect_data_list(term)?;
|
||||||
|
|
||||||
|
let item =
|
||||||
|
item.schema(definitions)
|
||||||
|
.ok_or_else(|| Error::UnresolvedSchemaReference {
|
||||||
|
reference: item.reference().unwrap().clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
for ref term in terms {
|
||||||
|
validate_data(item, definitions, term)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
Data::List(Items::Many(items)) => {
|
||||||
|
let terms = expect_data_list(term)?;
|
||||||
|
|
||||||
|
let items = items
|
||||||
|
.iter()
|
||||||
|
.map(|item| {
|
||||||
|
item.schema(definitions)
|
||||||
|
.ok_or_else(|| Error::UnresolvedSchemaReference {
|
||||||
|
reference: item.reference().unwrap().clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
if terms.len() != items.len() {
|
||||||
|
return Err(Error::TupleItemsMismatch {
|
||||||
|
expected: items.len(),
|
||||||
|
found: terms.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (item, ref term) in iter::zip(items, terms) {
|
||||||
|
validate_data(item, definitions, term)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
Data::Map(keys, values) => {
|
||||||
|
let terms = expect_data_map(term)?;
|
||||||
|
|
||||||
|
let keys =
|
||||||
|
keys.schema(definitions)
|
||||||
|
.ok_or_else(|| Error::UnresolvedSchemaReference {
|
||||||
|
reference: keys.reference().unwrap().clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let values =
|
||||||
|
values
|
||||||
|
.schema(definitions)
|
||||||
|
.ok_or_else(|| Error::UnresolvedSchemaReference {
|
||||||
|
reference: values.reference().unwrap().clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
for (ref k, ref v) in terms {
|
||||||
|
validate_data(keys, definitions, k)?;
|
||||||
|
validate_data(values, definitions, v)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
Data::AnyOf(constructors) => {
|
||||||
|
let constructors: Vec<(usize, Vec<&Data>)> = constructors
|
||||||
|
.iter()
|
||||||
|
.map(|constructor| {
|
||||||
|
constructor
|
||||||
|
.annotated
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| {
|
||||||
|
field.annotated.schema(definitions).ok_or_else(|| {
|
||||||
|
Error::UnresolvedSchemaReference {
|
||||||
|
reference: field.annotated.reference().unwrap().clone(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<_, _>>()
|
||||||
|
.map(|fields| (constructor.annotated.index, fields))
|
||||||
|
})
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
|
for (index, fields_schema) in constructors.iter() {
|
||||||
|
if let Ok(fields) = expect_data_constr(term, *index) {
|
||||||
|
if fields_schema.len() != fields.len() {
|
||||||
|
panic!("fields length different");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (instance, schema) in iter::zip(fields, fields_schema) {
|
||||||
|
validate_data(schema, definitions, &instance)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::SchemaMismatch {
|
||||||
|
schema: Schema::Data(Data::AnyOf(
|
||||||
|
constructors
|
||||||
|
.iter()
|
||||||
|
.map(|(index, fields)| {
|
||||||
|
Constructor {
|
||||||
|
index: *index,
|
||||||
|
fields: fields
|
||||||
|
.iter()
|
||||||
|
.map(|_| Declaration::Inline(Box::new(Data::Opaque)).into())
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)),
|
||||||
|
term: term.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_data(term: &Instance) -> Result<(), Error> {
|
||||||
|
if let Term::Constant(constant) = term {
|
||||||
|
if matches!(constant.deref(), Constant::Data(..)) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::SchemaMismatch {
|
||||||
|
schema: Schema::Data(Data::Opaque),
|
||||||
|
term: term.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_data_integer(term: &Instance) -> Result<(), Error> {
|
||||||
|
if let Term::Constant(constant) = term {
|
||||||
|
if let Constant::Data(data) = constant.deref() {
|
||||||
|
if matches!(data, PlutusData::BigInt(..)) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::SchemaMismatch {
|
||||||
|
schema: Schema::Data(Data::Integer),
|
||||||
|
term: term.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_data_bytes(term: &Instance) -> Result<(), Error> {
|
||||||
|
if let Term::Constant(constant) = term {
|
||||||
|
if let Constant::Data(data) = constant.deref() {
|
||||||
|
if matches!(data, PlutusData::BoundedBytes(..)) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::SchemaMismatch {
|
||||||
|
schema: Schema::Data(Data::Bytes),
|
||||||
|
term: term.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_data_list(term: &Instance) -> Result<Vec<Instance>, Error> {
|
||||||
|
if let Term::Constant(constant) = term {
|
||||||
|
if let Constant::Data(PlutusData::Array(elems)) = constant.deref() {
|
||||||
|
return Ok(elems
|
||||||
|
.iter()
|
||||||
|
.map(|elem| Term::Constant(Rc::new(Constant::Data(elem.to_owned()))))
|
||||||
|
.collect());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let inner_schema = Items::One(Declaration::Inline(Box::new(Data::Opaque)));
|
||||||
|
|
||||||
|
Err(Error::SchemaMismatch {
|
||||||
|
schema: Schema::Data(Data::List(inner_schema)),
|
||||||
|
term: term.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_data_map(term: &Instance) -> Result<Vec<(Instance, Instance)>, Error> {
|
||||||
|
if let Term::Constant(constant) = term {
|
||||||
|
if let Constant::Data(PlutusData::Map(pairs)) = constant.deref() {
|
||||||
|
return Ok(pairs
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| {
|
||||||
|
(
|
||||||
|
Term::Constant(Rc::new(Constant::Data(k.to_owned()))),
|
||||||
|
Term::Constant(Rc::new(Constant::Data(v.to_owned()))),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let key_schema = Declaration::Inline(Box::new(Data::Opaque));
|
||||||
|
let value_schema = Declaration::Inline(Box::new(Data::Opaque));
|
||||||
|
|
||||||
|
Err(Error::SchemaMismatch {
|
||||||
|
schema: Schema::Data(Data::Map(key_schema, value_schema)),
|
||||||
|
term: term.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_data_constr(term: &Instance, index: usize) -> Result<Vec<Instance>, Error> {
|
||||||
|
if let Term::Constant(constant) = term {
|
||||||
|
if let Constant::Data(PlutusData::Constr(constr)) = constant.deref() {
|
||||||
|
if let PlutusData::Constr(expected) = UplcData::constr(index as u64, vec![]) {
|
||||||
|
if expected.tag == constr.tag && expected.any_constructor == constr.any_constructor
|
||||||
|
{
|
||||||
|
return Ok(constr
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| Term::Constant(Rc::new(Constant::Data(field.to_owned()))))
|
||||||
|
.collect());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::SchemaMismatch {
|
||||||
|
schema: Schema::Data(Data::AnyOf(vec![Constructor {
|
||||||
|
index,
|
||||||
|
fields: vec![],
|
||||||
|
}
|
||||||
|
.into()])),
|
||||||
|
term: term.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_unit(term: &Instance) -> Result<(), Error> {
|
||||||
|
if let Term::Constant(constant) = term {
|
||||||
|
if matches!(constant.deref(), Constant::Unit) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::SchemaMismatch {
|
||||||
|
schema: Schema::Unit,
|
||||||
|
term: term.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_integer(term: &Instance) -> Result<(), Error> {
|
||||||
|
if let Term::Constant(constant) = term {
|
||||||
|
if matches!(constant.deref(), Constant::Integer(..)) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::SchemaMismatch {
|
||||||
|
schema: Schema::Integer,
|
||||||
|
term: term.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_bytes(term: &Instance) -> Result<(), Error> {
|
||||||
|
if let Term::Constant(constant) = term {
|
||||||
|
if matches!(constant.deref(), Constant::ByteString(..)) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::SchemaMismatch {
|
||||||
|
schema: Schema::Bytes,
|
||||||
|
term: term.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_string(term: &Instance) -> Result<(), Error> {
|
||||||
|
if let Term::Constant(constant) = term {
|
||||||
|
if matches!(constant.deref(), Constant::String(..)) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::SchemaMismatch {
|
||||||
|
schema: Schema::String,
|
||||||
|
term: term.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_boolean(term: &Instance) -> Result<(), Error> {
|
||||||
|
if let Term::Constant(constant) = term {
|
||||||
|
if matches!(constant.deref(), Constant::Bool(..)) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::SchemaMismatch {
|
||||||
|
schema: Schema::Boolean,
|
||||||
|
term: term.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_pair(term: &Instance) -> Result<(Instance, Instance), Error> {
|
||||||
|
if let Term::Constant(constant) = term {
|
||||||
|
if let Constant::ProtoPair(_, _, left, right) = constant.deref() {
|
||||||
|
return Ok((Term::Constant(left.clone()), Term::Constant(right.clone())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let left_schema = Declaration::Inline(Box::new(Schema::Data(Data::Opaque)));
|
||||||
|
let right_schema = Declaration::Inline(Box::new(Schema::Data(Data::Opaque)));
|
||||||
|
|
||||||
|
Err(Error::SchemaMismatch {
|
||||||
|
schema: Schema::Pair(left_schema, right_schema),
|
||||||
|
term: term.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_list(term: &Instance) -> Result<Vec<Instance>, Error> {
|
||||||
|
if let Term::Constant(constant) = term {
|
||||||
|
if let Constant::ProtoList(_, elems) = constant.deref() {
|
||||||
|
return Ok(elems
|
||||||
|
.iter()
|
||||||
|
.map(|elem| Term::Constant(Rc::new(elem.to_owned())))
|
||||||
|
.collect());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let inner_schema = Items::One(Declaration::Inline(Box::new(Schema::Data(Data::Opaque))));
|
||||||
|
|
||||||
|
Err(Error::SchemaMismatch {
|
||||||
|
schema: Schema::List(inner_schema),
|
||||||
|
term: term.clone(),
|
||||||
|
})
|
||||||
|
}
|
|
@ -13,7 +13,6 @@ use serde::{
|
||||||
};
|
};
|
||||||
use serde_json as json;
|
use serde_json as json;
|
||||||
use std::{collections::HashMap, fmt, ops::Deref, sync::Arc};
|
use std::{collections::HashMap, fmt, ops::Deref, sync::Arc};
|
||||||
use uplc::ast::Term;
|
|
||||||
|
|
||||||
// NOTE: Can be anything BUT 0
|
// NOTE: Can be anything BUT 0
|
||||||
pub const REDEEMER_DISCRIMINANT: usize = 1;
|
pub const REDEEMER_DISCRIMINANT: usize = 1;
|
||||||
|
@ -35,6 +34,43 @@ pub enum Declaration<T> {
|
||||||
Inline(Box<T>),
|
Inline(Box<T>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Declaration<T> {
|
||||||
|
pub fn reference(&'a self) -> Option<&'a Reference> {
|
||||||
|
match self {
|
||||||
|
Declaration::Referenced(reference) => Some(reference),
|
||||||
|
Declaration::Inline(..) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_schema(
|
||||||
|
&'a self,
|
||||||
|
definitions: &'a Definitions<Annotated<Schema>>,
|
||||||
|
cast: fn(&'a Schema) -> Option<&'a T>,
|
||||||
|
) -> Option<&'a T> {
|
||||||
|
match self {
|
||||||
|
Declaration::Inline(inner) => Some(inner.deref()),
|
||||||
|
Declaration::Referenced(reference) => definitions
|
||||||
|
.lookup(reference)
|
||||||
|
.and_then(|s| cast(&s.annotated)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Declaration<Data> {
|
||||||
|
pub fn schema(&'a self, definitions: &'a Definitions<Annotated<Schema>>) -> Option<&'a Data> {
|
||||||
|
self.try_schema(definitions, |s| match s {
|
||||||
|
Schema::Data(data) => Some(data),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Declaration<Schema> {
|
||||||
|
pub fn schema(&'a self, definitions: &'a Definitions<Annotated<Schema>>) -> Option<&'a Schema> {
|
||||||
|
self.try_schema(definitions, Some)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A schema for low-level UPLC primitives.
|
/// A schema for low-level UPLC primitives.
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum Schema {
|
pub enum Schema {
|
||||||
|
@ -71,7 +107,6 @@ pub enum Items<T> {
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Constructor {
|
pub struct Constructor {
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
// TODO: Generalize to work with either Reference or Data
|
|
||||||
pub fields: Vec<Annotated<Declaration<Data>>>,
|
pub fields: Vec<Annotated<Declaration<Data>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,50 +120,6 @@ impl<T> From<T> for Annotated<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> TryFrom<&'a Term<T>> for Schema {
|
|
||||||
type Error = &'a str;
|
|
||||||
|
|
||||||
fn try_from(term: &'a Term<T>) -> Result<Schema, Self::Error> {
|
|
||||||
use uplc::{ast::Constant, Constr, PlutusData};
|
|
||||||
|
|
||||||
match term {
|
|
||||||
Term::Constant(constant) => match constant.deref() {
|
|
||||||
Constant::Integer(..) => Ok(Schema::Integer),
|
|
||||||
Constant::Bool(..) => Ok(Schema::Boolean),
|
|
||||||
Constant::ByteString(..) => Ok(Schema::Bytes),
|
|
||||||
Constant::String(..) => Ok(Schema::String),
|
|
||||||
Constant::Unit => Ok(Schema::Unit),
|
|
||||||
Constant::ProtoList{..} => todo!("can't convert from ProtoList to Schema; note that you probably want to use a Data's list instead anyway."),
|
|
||||||
Constant::ProtoPair{..} => todo!("can't convert from ProtoPair to Schema; note that you probably want to use a Data's list instead anyway."),
|
|
||||||
Constant::Data(data) => Ok(Schema::Data(match data {
|
|
||||||
PlutusData::BigInt(..) => {
|
|
||||||
Data::Integer
|
|
||||||
}
|
|
||||||
PlutusData::BoundedBytes(..) => {
|
|
||||||
Data::Bytes
|
|
||||||
}
|
|
||||||
PlutusData::Array(elems) => {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
PlutusData::Map(keyValuePair) => {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
PlutusData::Constr(Constr{ tag, fields, any_constructor }) => {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
Term::Delay(..)
|
|
||||||
| Term::Lambda { .. }
|
|
||||||
| Term::Var(..)
|
|
||||||
| Term::Apply { .. }
|
|
||||||
| Term::Force(..)
|
|
||||||
| Term::Error
|
|
||||||
| Term::Builtin(..) => Err("not a UPLC constant"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Annotated<Schema> {
|
impl Annotated<Schema> {
|
||||||
pub fn as_wrapped_redeemer(
|
pub fn as_wrapped_redeemer(
|
||||||
definitions: &mut Definitions<Annotated<Schema>>,
|
definitions: &mut Definitions<Annotated<Schema>>,
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
use super::{
|
use super::{
|
||||||
definitions::{Definitions, Reference},
|
definitions::Definitions,
|
||||||
error::Error,
|
error::Error,
|
||||||
|
parameter::Parameter,
|
||||||
schema::{Annotated, Schema},
|
schema::{Annotated, Schema},
|
||||||
};
|
};
|
||||||
use crate::module::{CheckedModule, CheckedModules};
|
use crate::module::{CheckedModule, CheckedModules};
|
||||||
use aiken_lang::{
|
use aiken_lang::{
|
||||||
ast::{TypedArg, TypedFunction, TypedValidator},
|
ast::{TypedArg, TypedFunction, TypedValidator},
|
||||||
builtins,
|
|
||||||
gen_uplc::CodeGenerator,
|
gen_uplc::CodeGenerator,
|
||||||
};
|
};
|
||||||
use miette::NamedSource;
|
use miette::NamedSource;
|
||||||
use serde;
|
use serde;
|
||||||
use std::{borrow::Borrow, collections::HashMap};
|
use uplc::ast::{DeBruijn, Program, Term};
|
||||||
use uplc::ast::{Constant, DeBruijn, Program, Term};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct Validator {
|
pub struct Validator {
|
||||||
|
@ -22,13 +21,13 @@ pub struct Validator {
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub datum: Option<Argument>,
|
pub datum: Option<Parameter>,
|
||||||
|
|
||||||
pub redeemer: Argument,
|
pub redeemer: Parameter,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub parameters: Vec<Argument>,
|
pub parameters: Vec<Parameter>,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub program: Program<DeBruijn>,
|
pub program: Program<DeBruijn>,
|
||||||
|
@ -38,56 +37,6 @@ pub struct Validator {
|
||||||
pub definitions: Definitions<Annotated<Schema>>,
|
pub definitions: Definitions<Annotated<Schema>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub struct Argument {
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub title: Option<String>,
|
|
||||||
|
|
||||||
pub schema: Reference,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Reference> for Argument {
|
|
||||||
fn from(schema: Reference) -> Argument {
|
|
||||||
Argument {
|
|
||||||
title: None,
|
|
||||||
schema,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Argument {
|
|
||||||
pub fn validate(
|
|
||||||
&self,
|
|
||||||
definitions: &Definitions<Annotated<Schema>>,
|
|
||||||
term: &Term<DeBruijn>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let expected_schema = &definitions
|
|
||||||
.lookup(&self.schema)
|
|
||||||
.map(Ok)
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
Err(Error::UnresolvedSchemaReference {
|
|
||||||
reference: self.schema.clone(),
|
|
||||||
})
|
|
||||||
})?
|
|
||||||
.annotated;
|
|
||||||
|
|
||||||
let inferred_schema: Schema =
|
|
||||||
term.try_into()
|
|
||||||
.map_err(|hint: &str| Error::UnableToInferArgumentSchema {
|
|
||||||
hint: hint.to_owned(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if expected_schema != &inferred_schema {
|
|
||||||
Err(Error::SchemaMismatch {
|
|
||||||
expected: expected_schema.to_owned(),
|
|
||||||
inferred: inferred_schema,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Validator {
|
impl Validator {
|
||||||
pub fn from_checked_module(
|
pub fn from_checked_module(
|
||||||
modules: &CheckedModules,
|
modules: &CheckedModules,
|
||||||
|
@ -146,7 +95,7 @@ impl Validator {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|param| {
|
.map(|param| {
|
||||||
Annotated::from_type(modules.into(), ¶m.tipo, &mut definitions)
|
Annotated::from_type(modules.into(), ¶m.tipo, &mut definitions)
|
||||||
.map(|schema| Argument {
|
.map(|schema| Parameter {
|
||||||
title: Some(param.arg_name.get_label()),
|
title: Some(param.arg_name.get_label()),
|
||||||
schema,
|
schema,
|
||||||
})
|
})
|
||||||
|
@ -174,7 +123,7 @@ impl Validator {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.map(|schema| Argument {
|
.map(|schema| Parameter {
|
||||||
title: datum.map(|datum| datum.arg_name.get_label()),
|
title: datum.map(|datum| datum.arg_name.get_label()),
|
||||||
schema,
|
schema,
|
||||||
}),
|
}),
|
||||||
|
@ -187,7 +136,7 @@ impl Validator {
|
||||||
module.code.clone(),
|
module.code.clone(),
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
.map(|schema| Argument {
|
.map(|schema| Parameter {
|
||||||
title: Some(redeemer.arg_name.get_label()),
|
title: Some(redeemer.arg_name.get_label()),
|
||||||
schema: match datum {
|
schema: match datum {
|
||||||
Some(..) if is_multi_validator => Annotated::as_wrapped_redeemer(
|
Some(..) if is_multi_validator => Annotated::as_wrapped_redeemer(
|
||||||
|
@ -224,9 +173,9 @@ impl Validator {
|
||||||
mod test {
|
mod test {
|
||||||
use super::{
|
use super::{
|
||||||
super::{
|
super::{
|
||||||
definitions::Definitions,
|
definitions::{Definitions, Reference},
|
||||||
error::Error,
|
error::Error,
|
||||||
schema::{Annotated, Data, Declaration, Items, Schema},
|
schema::{Annotated, Constructor, Data, Declaration, Items, Schema},
|
||||||
},
|
},
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
|
@ -369,18 +318,63 @@ mod test {
|
||||||
fn fixture_definitions() -> Definitions<Annotated<Schema>> {
|
fn fixture_definitions() -> Definitions<Annotated<Schema>> {
|
||||||
let mut definitions = Definitions::new();
|
let mut definitions = Definitions::new();
|
||||||
|
|
||||||
|
// #/definitions/Int
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "dataType": "integer"
|
||||||
|
// }
|
||||||
definitions
|
definitions
|
||||||
.register::<_, Error>(&builtins::int(), &HashMap::new(), |_| {
|
.register::<_, Error>(&builtins::int(), &HashMap::new(), |_| {
|
||||||
Ok(Schema::Data(Data::Integer).into())
|
Ok(Schema::Data(Data::Integer).into())
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
// #/definitions/ByteArray
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "dataType": "bytes"
|
||||||
|
// }
|
||||||
definitions
|
definitions
|
||||||
.register::<_, Error>(&builtins::byte_array(), &HashMap::new(), |_| {
|
.register::<_, Error>(&builtins::byte_array(), &HashMap::new(), |_| {
|
||||||
Ok(Schema::Data(Data::Bytes).into())
|
Ok(Schema::Data(Data::Bytes).into())
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
// #/definitions/Bool
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "anyOf": [
|
||||||
|
// {
|
||||||
|
// "dataType": "constructor",
|
||||||
|
// "index": 0,
|
||||||
|
// "fields": []
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "dataType": "constructor",
|
||||||
|
// "index": 1,
|
||||||
|
// "fields": []
|
||||||
|
// },
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
definitions.insert(
|
||||||
|
&Reference::new("Bool"),
|
||||||
|
Schema::Data(Data::AnyOf(vec![
|
||||||
|
// False
|
||||||
|
Constructor {
|
||||||
|
index: 0,
|
||||||
|
fields: vec![],
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
// True
|
||||||
|
Constructor {
|
||||||
|
index: 1,
|
||||||
|
fields: vec![],
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
]))
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
definitions
|
definitions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1165,24 +1159,263 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validate_arguments_integer() {
|
fn validate_arguments_integer() {
|
||||||
let term = Term::data(uplc::Data::integer(42.into()));
|
|
||||||
let definitions = fixture_definitions();
|
let definitions = fixture_definitions();
|
||||||
let arg = Argument {
|
|
||||||
|
let term = Term::data(uplc::Data::integer(42.into()));
|
||||||
|
|
||||||
|
let param = Parameter {
|
||||||
title: None,
|
title: None,
|
||||||
schema: Reference::new("Int"),
|
schema: Reference::new("Int"),
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(matches!(arg.validate(&definitions, &term), Ok { .. }))
|
assert!(matches!(param.validate(&definitions, &term), Ok { .. }))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validate_arguments_bytestring() {
|
fn validate_arguments_bytestring() {
|
||||||
let term = Term::data(uplc::Data::bytestring(vec![102, 111, 111]));
|
|
||||||
let definitions = fixture_definitions();
|
let definitions = fixture_definitions();
|
||||||
let arg = Argument {
|
|
||||||
|
let term = Term::data(uplc::Data::bytestring(vec![102, 111, 111]));
|
||||||
|
|
||||||
|
let param = Parameter {
|
||||||
title: None,
|
title: None,
|
||||||
schema: Reference::new("ByteArray"),
|
schema: Reference::new("ByteArray"),
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(matches!(arg.validate(&definitions, &term), Ok { .. }))
|
assert!(matches!(param.validate(&definitions, &term), Ok { .. }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_arguments_list_inline() {
|
||||||
|
let schema = Reference::new("List$Int");
|
||||||
|
|
||||||
|
// #/definitions/List$Int
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "dataType": "list",
|
||||||
|
// "items": { "dataType": "integer" }
|
||||||
|
// }
|
||||||
|
let mut definitions = fixture_definitions();
|
||||||
|
definitions.insert(
|
||||||
|
&schema,
|
||||||
|
Schema::Data(Data::List(Items::One(Declaration::Inline(Box::new(
|
||||||
|
Data::Integer,
|
||||||
|
)))))
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let term = Term::data(uplc::Data::list(vec![
|
||||||
|
uplc::Data::integer(42.into()),
|
||||||
|
uplc::Data::integer(14.into()),
|
||||||
|
]));
|
||||||
|
|
||||||
|
let param: Parameter = schema.into();
|
||||||
|
|
||||||
|
assert!(matches!(param.validate(&definitions, &term), Ok { .. }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_arguments_list_ref() {
|
||||||
|
let schema = Reference::new("List$ByteArray");
|
||||||
|
|
||||||
|
// #/definitions/List$ByteArray
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "dataType": "list",
|
||||||
|
// "items": { "$ref": "#/definitions/ByteArray" }
|
||||||
|
// }
|
||||||
|
let mut definitions = fixture_definitions();
|
||||||
|
definitions.insert(
|
||||||
|
&schema,
|
||||||
|
Schema::Data(Data::List(Items::One(Declaration::Referenced(
|
||||||
|
Reference::new("ByteArray"),
|
||||||
|
))))
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let term = Term::data(uplc::Data::list(vec![uplc::Data::bytestring(vec![
|
||||||
|
102, 111, 111,
|
||||||
|
])]));
|
||||||
|
|
||||||
|
let param: Parameter = schema.into();
|
||||||
|
|
||||||
|
assert!(matches!(param.validate(&definitions, &term), Ok { .. }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_arguments_tuple() {
|
||||||
|
let schema = Reference::new("Tuple$Int_ByteArray");
|
||||||
|
|
||||||
|
// #/definitions/Tuple$Int_ByteArray
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "dataType": "list",
|
||||||
|
// "items": [
|
||||||
|
// { "$ref": "#/definitions/Int" }
|
||||||
|
// { "$ref": "#/definitions/ByteArray" }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
let mut definitions = fixture_definitions();
|
||||||
|
definitions.insert(
|
||||||
|
&schema,
|
||||||
|
Schema::Data(Data::List(Items::Many(vec![
|
||||||
|
Declaration::Referenced(Reference::new("Int")),
|
||||||
|
Declaration::Referenced(Reference::new("ByteArray")),
|
||||||
|
])))
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let term = Term::data(uplc::Data::list(vec![
|
||||||
|
uplc::Data::integer(42.into()),
|
||||||
|
uplc::Data::bytestring(vec![102, 111, 111]),
|
||||||
|
]));
|
||||||
|
|
||||||
|
let param: Parameter = schema.into();
|
||||||
|
|
||||||
|
assert!(matches!(param.validate(&definitions, &term), Ok { .. }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_arguments_dict() {
|
||||||
|
let schema = Reference::new("Dict$ByteArray_Int");
|
||||||
|
|
||||||
|
// #/definitions/Dict$Int_ByteArray
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "dataType": "map",
|
||||||
|
// "keys": { "dataType": "bytes" },
|
||||||
|
// "values": { "dataType": "integer" }
|
||||||
|
// }
|
||||||
|
let mut definitions = fixture_definitions();
|
||||||
|
definitions.insert(
|
||||||
|
&Reference::new("Dict$ByteArray_Int"),
|
||||||
|
Schema::Data(Data::Map(
|
||||||
|
Declaration::Inline(Box::new(Data::Bytes)),
|
||||||
|
Declaration::Inline(Box::new(Data::Integer)),
|
||||||
|
))
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let term = Term::data(uplc::Data::map(vec![(
|
||||||
|
uplc::Data::bytestring(vec![102, 111, 111]),
|
||||||
|
uplc::Data::integer(42.into()),
|
||||||
|
)]));
|
||||||
|
|
||||||
|
let param: Parameter = schema.into();
|
||||||
|
|
||||||
|
assert!(matches!(param.validate(&definitions, &term), Ok { .. }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_arguments_constr_nullary() {
|
||||||
|
let schema = Reference::new("Bool");
|
||||||
|
|
||||||
|
let definitions = fixture_definitions();
|
||||||
|
|
||||||
|
let term = Term::data(uplc::Data::constr(1, vec![]));
|
||||||
|
|
||||||
|
let param: Parameter = schema.into();
|
||||||
|
|
||||||
|
assert!(matches!(param.validate(&definitions, &term), Ok { .. }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_arguments_constr_n_ary() {
|
||||||
|
let schema = Reference::new("Foo");
|
||||||
|
|
||||||
|
// #/definitions/Foo
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "anyOf": [
|
||||||
|
// {
|
||||||
|
// "dataType": "constructor",
|
||||||
|
// "index": 0,
|
||||||
|
// "fields": [{
|
||||||
|
// "$ref": "#/definitions/Bool
|
||||||
|
// }]
|
||||||
|
// },
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
let mut definitions = fixture_definitions();
|
||||||
|
definitions.insert(
|
||||||
|
&schema,
|
||||||
|
Schema::Data(Data::AnyOf(vec![Constructor {
|
||||||
|
index: 0,
|
||||||
|
fields: vec![Declaration::Referenced(Reference::new("Bool")).into()],
|
||||||
|
}
|
||||||
|
.into()]))
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let term = Term::data(uplc::Data::constr(0, vec![uplc::Data::constr(0, vec![])]));
|
||||||
|
|
||||||
|
let param: Parameter = schema.into();
|
||||||
|
|
||||||
|
assert!(matches!(param.validate(&definitions, &term), Ok { .. }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_arguments_constr_recursive() {
|
||||||
|
let schema = Reference::new("LinkedList$Int");
|
||||||
|
|
||||||
|
// #/definitions/LinkedList$Int
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "anyOf": [
|
||||||
|
// {
|
||||||
|
// "dataType": "constructor",
|
||||||
|
// "index": 0,
|
||||||
|
// "fields": []
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "dataType": "constructor",
|
||||||
|
// "index": 1,
|
||||||
|
// "fields": [{
|
||||||
|
// "$ref": "#/definitions/Int
|
||||||
|
// "$ref": "#/definitions/LinkedList$Int
|
||||||
|
// }]
|
||||||
|
// },
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
let mut definitions = fixture_definitions();
|
||||||
|
definitions.insert(
|
||||||
|
&schema,
|
||||||
|
Schema::Data(Data::AnyOf(vec![
|
||||||
|
// Empty
|
||||||
|
Constructor {
|
||||||
|
index: 0,
|
||||||
|
fields: vec![],
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
// Node
|
||||||
|
Constructor {
|
||||||
|
index: 1,
|
||||||
|
fields: vec![
|
||||||
|
Declaration::Referenced(Reference::new("Int")).into(),
|
||||||
|
Declaration::Referenced(Reference::new("LinkedList$Int")).into(),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
]))
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let term = Term::data(uplc::Data::constr(
|
||||||
|
1,
|
||||||
|
vec![
|
||||||
|
uplc::Data::integer(14.into()),
|
||||||
|
uplc::Data::constr(
|
||||||
|
1,
|
||||||
|
vec![
|
||||||
|
uplc::Data::integer(42.into()),
|
||||||
|
uplc::Data::constr(0, vec![]),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
|
||||||
|
let param: Parameter = schema.into();
|
||||||
|
|
||||||
|
assert!(matches!(param.validate(&definitions, &term), Ok { .. }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue