Implement blueprint schema & data validations.

This commit is contained in:
KtorZ 2023-04-07 11:53:19 +02:00
parent ee220881b6
commit d58ef1a079
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
6 changed files with 842 additions and 118 deletions

View File

@ -53,6 +53,12 @@ impl<T> Definitions<T> {
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
/// mark-and-insert such that recursive definitions are only built once.
pub fn register<F, E>(

View File

@ -6,6 +6,7 @@ use aiken_lang::ast::Span;
use miette::{Diagnostic, NamedSource};
use owo_colors::{OwoColorize, Stream::Stdout};
use std::fmt::Debug;
use uplc::ast::{DeBruijn, Term};
#[derive(Debug, thiserror::Error, Diagnostic)]
pub enum Error {
@ -65,11 +66,25 @@ pub enum Error {
#[error("I caught a parameter application that seems off.")]
#[diagnostic(code("aiken::blueprint::apply::mismatch"))]
#[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{}",
serde_json::to_string_pretty(&expected).unwrap().if_supports_color(Stdout, |s| s.green()),
serde_json::to_string_pretty(&inferred).unwrap().if_supports_color(Stdout, |s| s.red()),
"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(&schema).unwrap().if_supports_color(Stdout, |s| s.green()),
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 {}

View File

@ -1,5 +1,6 @@
pub mod definitions;
pub mod error;
pub mod parameter;
pub mod schema;
pub mod validator;

View File

@ -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(),
})
}

View File

@ -13,7 +13,6 @@ use serde::{
};
use serde_json as json;
use std::{collections::HashMap, fmt, ops::Deref, sync::Arc};
use uplc::ast::Term;
// NOTE: Can be anything BUT 0
pub const REDEEMER_DISCRIMINANT: usize = 1;
@ -35,6 +34,43 @@ pub enum Declaration<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.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Schema {
@ -71,7 +107,6 @@ pub enum Items<T> {
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Constructor {
pub index: usize,
// TODO: Generalize to work with either Reference or 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> {
pub fn as_wrapped_redeemer(
definitions: &mut Definitions<Annotated<Schema>>,

View File

@ -1,18 +1,17 @@
use super::{
definitions::{Definitions, Reference},
definitions::Definitions,
error::Error,
parameter::Parameter,
schema::{Annotated, Schema},
};
use crate::module::{CheckedModule, CheckedModules};
use aiken_lang::{
ast::{TypedArg, TypedFunction, TypedValidator},
builtins,
gen_uplc::CodeGenerator,
};
use miette::NamedSource;
use serde;
use std::{borrow::Borrow, collections::HashMap};
use uplc::ast::{Constant, DeBruijn, Program, Term};
use uplc::ast::{DeBruijn, Program, Term};
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
pub struct Validator {
@ -22,13 +21,13 @@ pub struct Validator {
pub description: Option<String>,
#[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(default)]
pub parameters: Vec<Argument>,
pub parameters: Vec<Parameter>,
#[serde(flatten)]
pub program: Program<DeBruijn>,
@ -38,56 +37,6 @@ pub struct Validator {
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 {
pub fn from_checked_module(
modules: &CheckedModules,
@ -146,7 +95,7 @@ impl Validator {
.iter()
.map(|param| {
Annotated::from_type(modules.into(), &param.tipo, &mut definitions)
.map(|schema| Argument {
.map(|schema| Parameter {
title: Some(param.arg_name.get_label()),
schema,
})
@ -174,7 +123,7 @@ impl Validator {
)
})
.transpose()?
.map(|schema| Argument {
.map(|schema| Parameter {
title: datum.map(|datum| datum.arg_name.get_label()),
schema,
}),
@ -187,7 +136,7 @@ impl Validator {
module.code.clone(),
),
})
.map(|schema| Argument {
.map(|schema| Parameter {
title: Some(redeemer.arg_name.get_label()),
schema: match datum {
Some(..) if is_multi_validator => Annotated::as_wrapped_redeemer(
@ -224,9 +173,9 @@ impl Validator {
mod test {
use super::{
super::{
definitions::Definitions,
definitions::{Definitions, Reference},
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>> {
let mut definitions = Definitions::new();
// #/definitions/Int
//
// {
// "dataType": "integer"
// }
definitions
.register::<_, Error>(&builtins::int(), &HashMap::new(), |_| {
Ok(Schema::Data(Data::Integer).into())
})
.unwrap();
// #/definitions/ByteArray
//
// {
// "dataType": "bytes"
// }
definitions
.register::<_, Error>(&builtins::byte_array(), &HashMap::new(), |_| {
Ok(Schema::Data(Data::Bytes).into())
})
.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
}
@ -1165,24 +1159,263 @@ mod test {
#[test]
fn validate_arguments_integer() {
let term = Term::data(uplc::Data::integer(42.into()));
let definitions = fixture_definitions();
let arg = Argument {
let term = Term::data(uplc::Data::integer(42.into()));
let param = Parameter {
title: None,
schema: Reference::new("Int"),
};
assert!(matches!(arg.validate(&definitions, &term), Ok { .. }))
assert!(matches!(param.validate(&definitions, &term), Ok { .. }))
}
#[test]
fn validate_arguments_bytestring() {
let term = Term::data(uplc::Data::bytestring(vec![102, 111, 111]));
let definitions = fixture_definitions();
let arg = Argument {
let term = Term::data(uplc::Data::bytestring(vec![102, 111, 111]));
let param = Parameter {
title: None,
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 { .. }))
}
}